diff --git a/src/components/PromptHistory.jsx b/src/components/PromptHistory.jsx index 66ec1d6..ca8b5c8 100644 --- a/src/components/PromptHistory.jsx +++ b/src/components/PromptHistory.jsx @@ -1,110 +1,168 @@ import { useState, useEffect } from "react"; import Editor from "@monaco-editor/react"; +import { clearLocalHistory, loadLocalHistory } from "../lib/promptHistoryService"; export default function PromptHistory() { const [history, setHistory] = useState([]); + const [copyStatus, setCopyStatus] = useState({ id: null, field: "" }); - // Load from localStorage on mount useEffect(() => { - const stored = localStorage.getItem("promptHistory"); - if (stored) { - setHistory(JSON.parse(stored)); - } + setHistory(loadLocalHistory()); }, []); const handleClear = () => { setHistory([]); - localStorage.removeItem("promptHistory"); + clearLocalHistory(); + }; + + const handleCopy = async (text, id, field) => { + if (!text) return; + try { + await navigator.clipboard.writeText(text); + setCopyStatus({ id, field }); + setTimeout(() => setCopyStatus({ id: null, field: "" }), 1200); + } catch { + // ignore clipboard errors + } + }; + + const formatOutput = (value) => { + if (typeof value !== "string") { + return JSON.stringify(value, null, 2); + } + + try { + return JSON.stringify(JSON.parse(value), null, 2); + } catch { + return value; + } }; return ( -
+
-
-

Prompt History

- {history.length > 0 && ( - - )} +
+
+
+

Prompt History

+

+ Saved prompt runs from the app. Each entry includes the original input, generated output, and the action type. +

+
+
+ + {history.length} entr{history.length === 1 ? "y" : "ies"} + + {history.length > 0 && ( + + )} +
+
{history.length === 0 ? ( -
- No prompts yet. Try generating automation or migrating code. +
+

No prompts saved yet

+

Run a prompt in any feature to capture history here.

) : ( -
- {history.map((entry) => ( -
-
- - {entry.timestamp} - - {entry.type && ( - {entry.type} - )} -
+
+ {history.map((entry) => { + const outputValue = formatOutput(entry.output); + const inputValue = typeof entry.input === "string" ? entry.input : JSON.stringify(entry.input, null, 2); + const isCopiedInput = copyStatus.id === entry.id && copyStatus.field === "input"; + const isCopiedOutput = copyStatus.id === entry.id && copyStatus.field === "output"; - {/* Input */} -
-

- Input -

- {entry.input ? ( - - ) : ( -
- No input provided. + return ( +
+
+
+

{entry.type || "Prompt"}

+

{entry.timestamp}

+
+
+ + +
+
+ +
+
+
+
+

Input

+

Original prompt data

+
+
+ {inputValue ? ( + + ) : ( +

+ No input recorded for this entry. +

+ )}
- )} -
- {/* Output */} -
-

- Output -

- {entry.output ? ( - - ) : ( -
- No output generated. +
+
+
+

Output

+

Generated result

+
+
+ {outputValue ? ( + + ) : ( +

+ No output recorded for this entry. +

+ )}
- )} +
-
- ))} + ); + })}
)}
diff --git a/src/lib/promptHistoryService.js b/src/lib/promptHistoryService.js new file mode 100644 index 0000000..a64e69a --- /dev/null +++ b/src/lib/promptHistoryService.js @@ -0,0 +1,29 @@ +const STORAGE_KEY = "promptHistory"; + +export function loadLocalHistory() { + if (typeof localStorage === "undefined") return []; + try { + return JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]"); + } catch { + return []; + } +} + +export function savePromptHistory(entry) { + const history = loadLocalHistory(); + const nextEntry = { + id: entry.id ?? Date.now(), + type: entry.type ?? "Prompt", + input: entry.input ?? "", + output: entry.output ?? "", + timestamp: entry.timestamp ?? new Date().toLocaleString(), + }; + + localStorage.setItem(STORAGE_KEY, JSON.stringify([nextEntry, ...history])); + return nextEntry; +} + +export function clearLocalHistory() { + if (typeof localStorage === "undefined") return; + localStorage.removeItem(STORAGE_KEY); +} diff --git a/src/lib/supabaseClient.js b/src/lib/supabaseClient.js new file mode 100644 index 0000000..f255ec6 --- /dev/null +++ b/src/lib/supabaseClient.js @@ -0,0 +1,10 @@ +import { createClient } from "@supabase/supabase-js"; + +const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; +const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY; + +if (!supabaseUrl || !supabaseAnonKey) { + console.warn("Supabase environment variables are missing. Prompt history will still work locally."); +} + +export const supabase = createClient(supabaseUrl, supabaseAnonKey); diff --git a/src/pages/AutomationWriter.jsx b/src/pages/AutomationWriter.jsx index 9995606..a721d81 100644 --- a/src/pages/AutomationWriter.jsx +++ b/src/pages/AutomationWriter.jsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { createClient } from "@supabase/supabase-js"; import Editor from "@monaco-editor/react"; +import { savePromptHistory } from "../lib/promptHistoryService"; const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY; @@ -29,7 +30,15 @@ export default function AutomationWriter() { if (error) throw error; - setResponse(data?.automationScript || ""); + const script = data?.automationScript || ""; + setResponse(script); + savePromptHistory({ + id: Date.now(), + type: "Automation Writer", + input: useCase, + output: script, + timestamp: new Date().toLocaleString(), + }); } catch (e) { setErr(e.message || "Something went wrong"); } finally { diff --git a/src/pages/BugFixer.jsx b/src/pages/BugFixer.jsx index 5352dcc..d9fff17 100644 --- a/src/pages/BugFixer.jsx +++ b/src/pages/BugFixer.jsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { createClient } from "@supabase/supabase-js"; import Editor from "@monaco-editor/react"; +import { savePromptHistory } from "../lib/promptHistoryService"; const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY; @@ -39,6 +40,13 @@ export default function BugFixer() { if (error) throw error; setResult(data); + savePromptHistory({ + id: Date.now(), + type: "Bug Fixer", + input: inputCode, + output: data?.fixedCode || data?.summary || JSON.stringify(data), + timestamp: new Date().toLocaleString(), + }); } catch (e) { setErr(e.message || "Something went wrong"); } finally { diff --git a/src/pages/CodeMigration.jsx b/src/pages/CodeMigration.jsx index 58b98f5..db58591 100644 --- a/src/pages/CodeMigration.jsx +++ b/src/pages/CodeMigration.jsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { createClient } from "@supabase/supabase-js"; import Editor from "@monaco-editor/react"; +import { savePromptHistory } from "../lib/promptHistoryService"; const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY; @@ -40,6 +41,13 @@ export default function CodeMigration() { if (error) throw error; setMigration(data); + savePromptHistory({ + id: Date.now(), + type: "Code Migration", + input: inputCode, + output: data?.migratedCode || JSON.stringify(data, null, 2), + timestamp: new Date().toLocaleString(), + }); } catch (e) { setErr(e.message || "Something went wrong"); } finally { diff --git a/src/pages/CodeOptimisation.jsx b/src/pages/CodeOptimisation.jsx index 667ff14..26c088c 100644 --- a/src/pages/CodeOptimisation.jsx +++ b/src/pages/CodeOptimisation.jsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { createClient } from "@supabase/supabase-js"; import Editor from "@monaco-editor/react"; +import { savePromptHistory } from "../lib/promptHistoryService"; const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY; @@ -40,8 +41,6 @@ export default function CodeOptimizer() { setResult(data); - // Save to local prompt history (optional) - const history = JSON.parse(localStorage.getItem("promptHistory") || "[]"); const newEntry = { id: Date.now(), type: "Code Optimization", @@ -49,7 +48,8 @@ export default function CodeOptimizer() { output: data?.optimizedCode || "", timestamp: new Date().toLocaleString(), }; - localStorage.setItem("promptHistory", JSON.stringify([newEntry, ...history])); + + savePromptHistory(newEntry); } catch (e) { setErr(e.message || "Something went wrong"); } finally { diff --git a/src/pages/CodeReview.jsx b/src/pages/CodeReview.jsx index 9b6b25d..9cc8ff6 100644 --- a/src/pages/CodeReview.jsx +++ b/src/pages/CodeReview.jsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { createClient } from "@supabase/supabase-js"; import Editor from "@monaco-editor/react"; +import { savePromptHistory } from "../lib/promptHistoryService"; const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY; @@ -39,6 +40,13 @@ export default function CodeReview() { if (error) throw error; setReview(data); + savePromptHistory({ + id: Date.now(), + type: "Code Review", + input: inputCode, + output: JSON.stringify(data, null, 2), + timestamp: new Date().toLocaleString(), + }); } catch (e) { setErr(e.message || "Something went wrong"); } finally { diff --git a/src/pages/TestCaseGen.jsx b/src/pages/TestCaseGen.jsx index 21a2e5a..a02de63 100644 --- a/src/pages/TestCaseGen.jsx +++ b/src/pages/TestCaseGen.jsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { createClient } from "@supabase/supabase-js"; import Editor from "@monaco-editor/react"; +import { savePromptHistory } from "../lib/promptHistoryService"; const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY; diff --git a/src/pages/TestCasesFromFilesGenerator.jsx b/src/pages/TestCasesFromFilesGenerator.jsx index e54c021..dc77b1a 100644 --- a/src/pages/TestCasesFromFilesGenerator.jsx +++ b/src/pages/TestCasesFromFilesGenerator.jsx @@ -1,6 +1,7 @@ import { useState, useEffect, useRef } from "react"; import { createClient } from "@supabase/supabase-js"; import * as XLSX from "xlsx"; +import { savePromptHistory } from "../lib/promptHistoryService"; import { ChevronDown, ChevronUp } from "lucide-react"; import mammoth from "mammoth"; import { PDFDocument, rgb } from "pdf-lib"; @@ -131,6 +132,13 @@ export default function FileTestCaseGen() { if (error) throw new Error(error.message); setResponse(data); + savePromptHistory({ + id: Date.now(), + type: "File Test Case Generator", + input: file.name, + output: JSON.stringify(data, null, 2), + timestamp: new Date().toLocaleString(), + }); } catch (e) { if (e.name === "AbortError") { setErr("Generation stopped by user."); diff --git a/src/pages/TestDataGen.jsx b/src/pages/TestDataGen.jsx index 5b89d12..6f8eea1 100644 --- a/src/pages/TestDataGen.jsx +++ b/src/pages/TestDataGen.jsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { createClient } from "@supabase/supabase-js"; import Editor from "@monaco-editor/react"; +import { savePromptHistory } from "../lib/promptHistoryService"; const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY; @@ -40,6 +41,13 @@ export default function TestDataGen() { if (error) throw error; setResponse(data); + savePromptHistory({ + id: Date.now(), + type: "Test Data Generator", + input: inputCode, + output: data?.testData || JSON.stringify(data, null, 2), + timestamp: new Date().toLocaleString(), + }); } catch (e) { setErr(e.message || "Something went wrong"); } finally { diff --git a/startify b/startify new file mode 160000 index 0000000..c9673b7 --- /dev/null +++ b/startify @@ -0,0 +1 @@ +Subproject commit c9673b7025501d3d49beda01a60e79ab350e1043 diff --git a/supabase/functions/Code-Migration/index.ts b/supabase/functions/Code-Migration/index.ts new file mode 100644 index 0000000..ffc442d --- /dev/null +++ b/supabase/functions/Code-Migration/index.ts @@ -0,0 +1,124 @@ +import "jsr:@supabase/functions-js/edge-runtime.d.ts"; +const GEMINI_API_KEY = Deno.env.get("GEMINI_API_KEY") ?? ""; +const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "POST, GET, OPTIONS", + "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type" +}; +// helpers +function stripFences(s) { + if (!s) return ""; + return s.replace(/^\s*```(?:json|[a-zA-Z]+)?\s*/i, "").replace(/\s*```\s*$/i, "").trim(); +} +function safeParseJSON(s) { + try { + return JSON.parse(s); + } catch { + return null; + } +} +Deno.serve(async (req)=>{ + if (req.method === "OPTIONS") return new Response("ok", { + headers: corsHeaders + }); + try { + const { sourceLanguage, targetLanguage, code } = await req.json(); + if (!sourceLanguage || !targetLanguage || !code) { + return new Response(JSON.stringify({ + error: "Missing sourceLanguage, targetLanguage, or code" + }), { + status: 400, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } + if (!GEMINI_API_KEY) { + return new Response(JSON.stringify({ + error: "Missing GEMINI_API_KEY" + }), { + status: 500, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } + const prompt = ` +You are a senior engineer performing a **code migration**. + +TASK: +Translate the given code from ${sourceLanguage} to idiomatic ${targetLanguage}, preserving behavior and edge cases. + +OUTPUT (STRICT JSON ONLY): +{ + "migratedCode": "full translated code in ${targetLanguage}", + "mappingNotes": ["api/class/library mapping notes"], + "manualSteps": ["things the developer must do manually (libs, configs)"], + "limitations": ["known gaps or uncertainties"] +} + +RULES: +- Use idiomatic patterns for ${targetLanguage}. +- Replace libraries/APIs with closest equivalents and document them in mappingNotes. +- Keep comments meaningful if present; otherwise do not add explanations in code. +- No markdown, no fences; escaped JSON string values only. + +SOURCE CODE (${sourceLanguage}): +${code} + `; + const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${GEMINI_API_KEY}`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + contents: [ + { + role: "user", + parts: [ + { + text: prompt + } + ] + } + ] + }) + }); + if (!res.ok) { + const errText = await res.text(); + return new Response(JSON.stringify({ + error: errText + }), { + status: res.status, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } + const data = await res.json(); + let raw = (data.candidates?.[0]?.content?.parts?.[0]?.text || "").trim(); + raw = stripFences(raw); + const parsed = safeParseJSON(raw) || {}; + // extra safety: strip any stray fences inside migratedCode + if (parsed.migratedCode) parsed.migratedCode = stripFences(String(parsed.migratedCode)); + return new Response(JSON.stringify(parsed), { + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } catch (err) { + return new Response(JSON.stringify({ + error: err.message + }), { + status: 500, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } +}); diff --git a/supabase/functions/Code-Review/index.ts b/supabase/functions/Code-Review/index.ts new file mode 100644 index 0000000..0d065b1 --- /dev/null +++ b/supabase/functions/Code-Review/index.ts @@ -0,0 +1,150 @@ +import "jsr:@supabase/functions-js/edge-runtime.d.ts"; +const GEMINI_API_KEY = Deno.env.get("GEMINI_API_KEY") ?? ""; +// --- CORS headers --- +const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET,POST,OPTIONS", + "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type" +}; +// --- helpers --- +function stripFences(s) { + if (!s) return ""; + return s.replace(/^\s*```(?:json|[a-zA-Z]+)?\s*/i, "").replace(/\s*```\s*$/i, "").trim(); +} +function safeParseJSON(s) { + try { + return JSON.parse(s); + } catch { + return null; + } +} +// --- Main handler --- +Deno.serve(async (req)=>{ + // 🔹 Handle CORS preflight + if (req.method === "OPTIONS") { + return new Response(null, { + status: 204, + headers: { + ...corsHeaders + } + }); + } + try { + const { language, code } = await req.json(); + if (!language || !code) { + return new Response(JSON.stringify({ + error: "Missing language or code" + }), { + status: 400, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } + if (!GEMINI_API_KEY) { + return new Response(JSON.stringify({ + error: "Missing GEMINI_API_KEY" + }), { + status: 500, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } + // --- Build prompt for Gemini --- + const prompt = ` +You are a principal engineer conducting a rigorous code review. + +DELIVERABLE: +Return ONLY valid JSON with this schema: +{ + "summary": "2-4 sentences", + "score": { "readability": 0-10, "correctness": 0-10, "robustness": 0-10, "performance": 0-10, "security": 0-10, "maintainability": 0-10 }, + "issues": [ + { + "title": "short issue title", + "severity": "low|medium|high|critical", + "category": "correctness|security|performance|style|maintainability|testing", + "evidence": "quote or snippet", + "explanation": "why this matters", + "fix": "suggested fix snippet" + } + ], + "quickWins": ["bulleted actionable items"], + "deeperRefactors": ["bulleted larger refactors"], + "testingGaps": ["tests we should add"], + "references": ["official doc links or keywords to search"] +} + +REVIEW FOCUS: +- Catch correctness bugs, edge cases, concurrency hazards, resource leaks. +- Security: input validation, injection, authz, secrets handling. +- Performance: hot paths, N+1, memory, algorithmic complexity. +- Maintainability: cohesion, coupling, naming, dead code, duplication. +- Testing: coverage of branches, error handling, boundaries. +- If you do not find any code suggestions, say the code looks great , no imporvements. +CONTEXT: +- Language: ${language} +- Be precise but concise; do not add extra fields. + + +CODE UNDER REVIEW: +${code} + `; + // --- Call Gemini --- + const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${GEMINI_API_KEY}`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + contents: [ + { + role: "user", + parts: [ + { + text: prompt + } + ] + } + ] + }) + }); + if (!res.ok) { + const errText = await res.text(); + return new Response(JSON.stringify({ + error: errText + }), { + status: res.status, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } + const data = await res.json(); + let raw = (data.candidates?.[0]?.content?.parts?.[0]?.text || "").trim(); + raw = stripFences(raw); + const parsed = safeParseJSON(raw) || {}; + console.log(parsed, "parsed"); + return new Response(JSON.stringify(parsed), { + status: 200, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } catch (err) { + return new Response(JSON.stringify({ + error: err.message + }), { + status: 500, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } +}); diff --git a/supabase/functions/automation-writer/index.ts b/supabase/functions/automation-writer/index.ts new file mode 100644 index 0000000..19ccea3 --- /dev/null +++ b/supabase/functions/automation-writer/index.ts @@ -0,0 +1,98 @@ +import "jsr:@supabase/functions-js/edge-runtime.d.ts"; +const promptTemplate = ` +You are an expert automation engineer. + +The user has described an automation idea. +Your task is to generate a clean, runnable automation script in {{LANGUAGE}}. + +Instructions for formatting your answer: +1. Start with a short explanation in plain text (2–5 sentences). +2. Then provide the script inside a fenced code block. + - Use triple backticks with the correct language identifier (like \`\`\`{{language.toLowerCase()}}\`\`\`). +3. Do not include anything outside this format. + +Automation Idea: +"{{IDEA}}" + +if you do not see anything to generate , return failed to return anythinng in explanantion +`; +const GEMINI_API_KEY = Deno.env.get("GEMINI_API_KEY") ?? ""; +const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "POST, GET, OPTIONS", + "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type" +}; +Deno.serve(async (req)=>{ + if (req.method === "OPTIONS") { + return new Response("ok", { + headers: corsHeaders + }); + } + try { + const { language, idea } = await req.json(); + const prompt = promptTemplate.replace(/{{LANGUAGE}}/g, language).replace(/{{IDEA}}/g, idea); + // 🔹 Mock fallback if no API key + if (!GEMINI_API_KEY) { + return new Response(JSON.stringify({ + explanation: `This is a mock explanation for your ${language} automation.`, + code: `# mock ${language} script\nprint("Hello world!")`, + mock: true + }), { + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } + // 🔹 Call Gemini + const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${GEMINI_API_KEY}`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + contents: [ + { + role: "user", + parts: [ + { + text: prompt + } + ] + } + ] + }) + }); + const data = await res.json(); + let rawOutput = data; + // 🔹 Split into explanation + code + let explanation = rawOutput; + let code = ""; + console.log(code, explanation); + const codeMatch = rawOutput.match(/```(\w+)?([\s\S]*?)```/); + if (codeMatch) { + explanation = rawOutput.slice(0, codeMatch.index).trim(); + code = codeMatch[2].trim(); + } + console.log(code, explanation); + return new Response(JSON.stringify({ + explanation, + code + }), { + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } catch (err) { + return new Response(JSON.stringify({ + error: err.message + }), { + status: 400, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } +}); diff --git a/supabase/functions/bug-finder-fixer/index.ts b/supabase/functions/bug-finder-fixer/index.ts new file mode 100644 index 0000000..a46207b --- /dev/null +++ b/supabase/functions/bug-finder-fixer/index.ts @@ -0,0 +1,139 @@ +import "jsr:@supabase/functions-js/edge-runtime.d.ts"; +const GEMINI_API_KEY = Deno.env.get("GEMINI_API_KEY") ?? ""; +const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "POST, GET, OPTIONS", + "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type" +}; +// helpers +function stripFences(s) { + if (!s) return ""; + return s.replace(/^\s*```(?:json|[a-zA-Z]+)?\s*/i, "").replace(/\s*```\s*$/i, "").trim(); +} +function safeParseJSON(s) { + try { + return JSON.parse(s); + } catch { + return null; + } +} +Deno.serve(async (req)=>{ + if (req.method === "OPTIONS") return new Response("ok", { + headers: corsHeaders + }); + try { + const { language, code } = await req.json(); + if (!language || !code) { + return new Response(JSON.stringify({ + error: "Missing language or code" + }), { + status: 400, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } + if (!GEMINI_API_KEY) { + return new Response(JSON.stringify({ + error: "Missing GEMINI_API_KEY" + }), { + status: 500, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } + const prompt = ` +You are a senior engineer and debugging expert. + +TASK: +Identify **bugs, vulnerabilities, and edge-case failures**, then provide a **corrected version** of the code. + +STRICT JSON OUTPUT ONLY: +{ + "summary": "1-3 sentence overview of issues & fixes", + "bugs": [ + { + "title": "concise bug title", + "severity": "low|medium|high|critical", + "type": "logic|concurrency|security|api|performance|resource|style|other", + "evidence": "snippet or description that proves the bug", + "explanation": "why it fails and when", + "fix": "short explanation of the fix", + "patch": "unified diff or minimal code snippet showing the fix" + } + ], + "fixedCode": "full corrected code in ${language}" +} + +GUIDELINES: +- Prefer minimal, targeted fixes that fully resolve the root cause. +- Consider error handling, input validation, boundary conditions, timeouts, resource cleanup. +- Keep output deterministic; no comments outside JSON. +- No markdown fences in any field. + +ORIGINAL CODE (${language}): +${code} + `; + const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${GEMINI_API_KEY}`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + contents: [ + { + role: "user", + parts: [ + { + text: prompt + } + ] + } + ] + }) + }); + if (!res.ok) { + const errText = await res.text(); + return new Response(JSON.stringify({ + error: errText + }), { + status: res.status, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } + const data = await res.json(); + let raw = (data.candidates?.[0]?.content?.parts?.[0]?.text || "").trim(); + raw = stripFences(raw); + const parsed = safeParseJSON(raw) || {}; + // Safety: strip fences inside fixedCode or patches if present + if (parsed.fixedCode) parsed.fixedCode = stripFences(String(parsed.fixedCode)); + if (Array.isArray(parsed.bugs)) { + parsed.bugs = parsed.bugs.map((b)=>({ + ...b, + patch: b?.patch ? stripFences(String(b.patch)) : b?.patch + })); + } + return new Response(JSON.stringify(parsed), { + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } catch (err) { + return new Response(JSON.stringify({ + error: err.message + }), { + status: 500, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } +}); diff --git a/supabase/functions/code-optmisation/index.ts b/supabase/functions/code-optmisation/index.ts new file mode 100644 index 0000000..f9d6258 --- /dev/null +++ b/supabase/functions/code-optmisation/index.ts @@ -0,0 +1,133 @@ +import "jsr:@supabase/functions-js/edge-runtime.d.ts"; +const GEMINI_API_KEY = Deno.env.get("GEMINI_API_KEY") ?? ""; +const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "POST, GET, OPTIONS", + "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type" +}; +Deno.serve(async (req)=>{ + // Handle preflight (CORS) + if (req.method === "OPTIONS") { + return new Response("ok", { + headers: corsHeaders + }); + } + try { + const { language, code } = await req.json(); + if (!code || !language) { + return new Response(JSON.stringify({ + error: "Missing language or code" + }), { + status: 400, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } + if (!GEMINI_API_KEY) { + return new Response(JSON.stringify({ + error: "Missing GEMINI_API_KEY" + }), { + status: 500, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } + const prompt = ` +You are a **principal software engineer** tasked with optimizing the following code. + +DELIVERABLE: +Return ONLY valid JSON with this schema: +{ + "optimizedCode": "string - full optimized code in ${language}, no markdown fences", + "improvements": [ + "list of short bullet points explaining key improvements" + ], + "potentialTradeoffs": [ + "list of possible downsides of your optimization (if any)" + ], + "references": [ + "official docs or keywords to search for further reading" + ] +} + +REQUIREMENTS: +- Output must be valid JSON. +- "optimizedCode" must contain only the improved code, not explanations or comments. +- Focus on: + - Readability (clean naming, reduced complexity). + - Performance (remove inefficiencies, better algorithms if possible). + - Best practices (idiomatic style for ${language}). +- Preserve original logic unless unsafe or redundant. +- If the code is already optimal, return it unchanged with empty arrays for improvements/tradeoffs. + +CODE: +${code} +`; + const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${GEMINI_API_KEY}`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + contents: [ + { + role: "user", + parts: [ + { + text: prompt + } + ] + } + ] + }) + }); + if (!res.ok) { + const errText = await res.text(); + return new Response(JSON.stringify({ + error: errText + }), { + status: res.status, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } + const data = await res.json(); + // Extract the raw text from Gemini + let rawText = data.candidates?.[0]?.content?.parts?.[0]?.text?.trim() ?? ""; + // Try to parse Gemini output as JSON + let parsed; + try { + parsed = JSON.parse(rawText); + } catch { + // Fallback if Gemini adds markdown fences + rawText = rawText.replace(/```json\n?/g, "").replace(/```$/g, "").trim(); + parsed = JSON.parse(rawText); + } + // Clean up code field (strip accidental fences) + if (parsed.optimizedCode) { + parsed.optimizedCode = parsed.optimizedCode.replace(/```[a-zA-Z]*\n?/g, "").replace(/```$/g, "").trim(); + } + return new Response(JSON.stringify(parsed), { + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } catch (err) { + return new Response(JSON.stringify({ + error: err.message + }), { + status: 500, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } +}); diff --git a/supabase/functions/file-test-gen/index.ts b/supabase/functions/file-test-gen/index.ts new file mode 100644 index 0000000..ad6037f --- /dev/null +++ b/supabase/functions/file-test-gen/index.ts @@ -0,0 +1,184 @@ +import "jsr:@supabase/functions-js/edge-runtime.d.ts"; + +const GEMINI_API_KEY = Deno.env.get("GEMINI_API_KEY") ?? ""; +const GEMINI_UPLOAD_URL = + "https://generativelanguage.googleapis.com/upload/v1beta/files?key=" + GEMINI_API_KEY; +const GEMINI_MODEL_URL = + "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=" + GEMINI_API_KEY; + +const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "POST, GET, OPTIONS", + "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type", +}; + +// Safe base64 → Uint8Array +function base64ToUint8Array(base64: string) { + const raw = atob(base64); + const arr = new Uint8Array(raw.length); + for (let i = 0; i < raw.length; i++) arr[i] = raw.charCodeAt(i); + return arr; +} + +Deno.serve(async (req) => { + if (req.method === "OPTIONS") { + return new Response("ok", { headers: corsHeaders }); + } + + try { + const { fileName, fileContent, model } = await req.json(); + console.log("Received request:", { fileName, model }); + + if (!fileName || !fileContent) { + console.log("Missing fileName or fileContent"); + return new Response( + JSON.stringify({ error: "Missing fileName or fileContent" }), + { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } } + ); + } + + if (!GEMINI_API_KEY) { + console.log("Missing GEMINI_API_KEY"); + return new Response( + JSON.stringify({ error: "Missing GEMINI_API_KEY" }), + { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } } + ); + } + + // MIME detection + let mimeType = "application/octet-stream"; + const lower = fileName.toLowerCase(); + if (lower.endsWith(".pdf")) mimeType = "application/pdf"; + if (lower.endsWith(".docx")) + mimeType = + "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; + console.log("Detected MIME type:", mimeType); + + // Decode Base64 + const binary = base64ToUint8Array(fileContent); + console.log("Binary length:", binary.length); + + // Prepare FormData for upload + const form = new FormData(); + form.append( + "file", + new Blob([binary], { type: mimeType }), + fileName + ); + console.log("FormData created with file:", fileName); + + // Upload file to Gemini + const uploadRes = await fetch(GEMINI_UPLOAD_URL, { method: "POST", body: form }); + console.log("Upload response status:", uploadRes.status); + + if (!uploadRes.ok) { + const uploadErr = await uploadRes.text(); + console.log("Upload failed:", uploadErr); + return new Response( + JSON.stringify({ error: `Upload failed: ${uploadErr}` }), + { status: uploadRes.status, headers: { ...corsHeaders, "Content-Type": "application/json" } } + ); + } + + const uploadData = await uploadRes.json(); + console.log("Upload response data:", uploadData); + const fileUri = uploadData?.file?.uri; + + if (!fileUri) { + console.log("No file URI returned after upload"); + return new Response( + JSON.stringify({ error: "File upload failed, no URI returned" }), + { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } } + ); + } + + // Prompt + const prompt = ` +You are a senior QA engineer. +Generate at least 25 detailed test cases for the uploaded document (PDF or Word DOCX). +Make sure to generate at least 5 negative test cases. +Also if the uploaded document is a FSD or requirement definition file, make sure to generate 1 test case for every section / requirement. + +DELIVERABLE: +Return ONLY valid JSON with this exact structure: +{ + "testCases": [ + { + "testCaseId": "TC_01", + "title": "string", + "description": "string", + "preconditions": "string", + "steps": [ + { "stepNo": number, "action": "string", "expectedResult": "string" } + ] + } + ] +} + +File: ${fileName} +`; + + // Call Gemini + console.log("Calling Gemini model with file URI:", fileUri); + const genRes = await fetch(GEMINI_MODEL_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + contents: [ + { + role: "user", + parts: [ + { text: prompt }, + { file_data: { mime_type: mimeType, file_uri: fileUri } }, + ], + }, + ], + }), + }); + + console.log("Gemini response status:", genRes.status); + + if (!genRes.ok) { + const errText = await genRes.text(); + console.log("Gemini model failed:", errText); + return new Response( + JSON.stringify({ error: errText }), + { status: genRes.status, headers: { ...corsHeaders, "Content-Type": "application/json" } } + ); + } + + const data = await genRes.json(); + console.log("Gemini raw data:", data); + + let rawText = data.candidates?.[0]?.content?.parts?.[0]?.text?.trim() ?? ""; + rawText = rawText.replace(/```(json|text)?\n?/gi, "").replace(/```$/g, "").trim(); + + let parsed; + try { + parsed = JSON.parse(rawText); + } catch (parseErr) { + console.log("Error parsing JSON from Gemini:", parseErr, "Raw text:", rawText); + parsed = { + testCases: [ + { + testCaseId: "TC_ERROR", + title: "Error parsing test cases", + description: rawText.slice(0, 200), + preconditions: "", + steps: [], + }, + ], + }; + } + + return new Response(JSON.stringify(parsed), { + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }); + } catch (err) { + console.log("Unexpected error:", err); + return new Response( + JSON.stringify({ error: err.message }), + { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } } + ); + } +}); diff --git a/supabase/functions/test-case-gen/index.ts b/supabase/functions/test-case-gen/index.ts new file mode 100644 index 0000000..4c9fab7 --- /dev/null +++ b/supabase/functions/test-case-gen/index.ts @@ -0,0 +1,211 @@ +// import "jsr:@supabase/functions-js/edge-runtime.d.ts"; +// // Load prompt.md at runtime +// const promptTemplate = Deno.env.get("prompt") ?? ""; +// const OPENAI_API_KEY = Deno.env.get("OPENAI_API_KEY") ?? ""; +// const corsHeaders = { +// "Access-Control-Allow-Origin": "*", +// "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type" +// }; +// Deno.serve(async (req)=>{ +// if (req.method === "OPTIONS") { +// return new Response("ok", { +// headers: corsHeaders +// }); +// } +// try { +// const { language, code } = await req.json(); +// // Replace placeholders in the prompt +// const prompt = promptTemplate.replace(/{{LANGUAGE}}/g, language).replace(/{{CODE}}/g, code); +// // Mock mode if no API key +// if (!OPENAI_API_KEY) { +// return new Response(JSON.stringify({ +// testCases: JSON.stringify([ +// `Mock test case for ${language}: Verify input is valid`, +// "Verify expected output matches requirements", +// "Test edge cases and invalid inputs", +// "Ensure performance under load" +// ]), +// mock: true +// }), { +// headers: { +// ...corsHeaders, +// "Content-Type": "application/json" +// } +// }); +// } +// console.log("Calling OpenAI..."); +// const res = await fetch("https://api.openai.com/v1/chat/completions", { +// method: "POST", +// headers: { +// "Content-Type": "application/json", +// Authorization: `Bearer ${OPENAI_API_KEY}` +// }, +// body: JSON.stringify({ +// model: "gpt-5", +// max_tokens: 500, +// messages: [ +// { +// role: "user", +// content: prompt + code +// } +// ] +// }) +// }); +// if (!res.ok) { +// const errText = await res.text(); +// console.error("OpenAI error:", errText); +// return new Response(JSON.stringify({ +// error: errText +// }), { +// status: res.status, +// headers: { +// ...corsHeaders, +// "Content-Type": "application/json" +// } +// }); +// } +// const data = await res.json(); +// let rawOutput = data.choices?.[0]?.message?.content ?? "[]"; +// // Try to ensure output is always valid JSON array +// let parsed; +// try { +// parsed = JSON.parse(rawOutput); +// if (!Array.isArray(parsed)) throw new Error("Not an array"); +// } catch { +// // Fallback: wrap into array +// parsed = [ +// rawOutput +// ]; +// } +// console.log("Returning test cases:", parsed); +// return new Response(JSON.stringify({ +// testCases: JSON.stringify(parsed) +// }), { +// headers: { +// ...corsHeaders, +// "Content-Type": "application/json" +// } +// }); +// } catch (err) { +// console.error("Function error:", err); +// return new Response(JSON.stringify({ +// error: err.message +// }), { +// status: 400, +// headers: { +// ...corsHeaders, +// "Content-Type": "application/json" +// } +// }); +// } +// }); +// supabase/functions/testcase-gen/index.ts +// functions/test-cases/index.ts +import "jsr:@supabase/functions-js/edge-runtime.d.ts"; +const promptTemplate = ` +You are an expert software tester. +Generate runnable test cases for the provided code in {{LANGUAGE}}. + +STRICT RULES: +1. Return an array of strings where each element is a FULL test case. +2. Each test case MUST include a short comment at the top (e.g. "// Tests X scenario"). +3. Respond STRICTLY with valid JSON: +{ + "testCases": ["case1", "case2", "case3"] +} +4. Minimum of 5 test cases. + +Code ({{LANGUAGE}}): +{{CODE}} +`; +const GEMINI_API_KEY = Deno.env.get("GEMINI_API_KEY") ?? ""; +const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "POST, GET, OPTIONS", + "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type" +}; +Deno.serve(async (req)=>{ + if (req.method === "OPTIONS") { + return new Response("ok", { + headers: corsHeaders + }); + } + try { + const { language, code } = await req.json(); + const prompt = promptTemplate.replace(/{{LANGUAGE}}/g, language).replace(/{{CODE}}/g, code); + if (!GEMINI_API_KEY) { + return new Response(JSON.stringify({ + testCases: [ + `// Test valid input\nassert add(2,2) == 4`, + `// Test negative numbers\nassert add(-1,-3) == -4` + ], + mock: true + }), { + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } + const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${GEMINI_API_KEY}`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + contents: [ + { + role: "user", + parts: [ + { + text: prompt + } + ] + } + ] + }) + }); + if (!res.ok) { + const errText = await res.text(); + return new Response(JSON.stringify({ + error: errText + }), { + status: res.status, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } + const data = await res.json(); + let rawOutput = data.candidates?.[0]?.content?.parts?.[0]?.text ?? ""; + rawOutput = rawOutput.replace(/```(json|javascript|python|java|typescript|csharp)?/gi, "").trim(); + let parsed; + try { + const obj = JSON.parse(rawOutput); + parsed = Array.isArray(obj.testCases) ? obj.testCases : [ + rawOutput + ]; + } catch { + parsed = rawOutput.split("\n").map((line)=>line.trim()).filter(Boolean); + } + return new Response(JSON.stringify({ + testCases: parsed + }), { + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } catch (err) { + return new Response(JSON.stringify({ + error: err.message + }), { + status: 400, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } +}); diff --git a/supabase/functions/test-data-gen/index.ts b/supabase/functions/test-data-gen/index.ts new file mode 100644 index 0000000..53a47d3 --- /dev/null +++ b/supabase/functions/test-data-gen/index.ts @@ -0,0 +1,124 @@ +import "jsr:@supabase/functions-js/edge-runtime.d.ts"; +const GEMINI_API_KEY = Deno.env.get("GEMINI_API_KEY") ?? ""; +const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "POST, GET, OPTIONS", + "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type" +}; +// Helpers +function stripFences(s) { + if (!s) return ""; + return s.replace(/^\s*```(?:json|[a-zA-Z]+)?\s*/i, "").replace(/\s*```\s*$/i, "").trim(); +} +function safeParseJSON(s) { + try { + return JSON.parse(s); + } catch { + return null; + } +} +Deno.serve(async (req)=>{ + if (req.method === "OPTIONS") { + return new Response("ok", { + headers: corsHeaders + }); + } + try { + const { language, code , model } = await req.json(); + if (!language || !code) { + return new Response(JSON.stringify({ + error: "Missing language, maxLength, or code/schema" + }), { + status: 400, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } + if (!GEMINI_API_KEY) { + return new Response(JSON.stringify({ + error: "Missing GEMINI_API_KEY" + }), { + status: 500, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } + const prompt = ` +You are a senior test automation engineer. + +TASK: +Generate **realistic test data** in ${language} given this input schema or code: +${code} + +RULES: +- Output STRICT JSON only (no markdown, no fences). +- Do not exceed 2000 characters in test data. +- Use meaningful sample values (names, IDs, emails, timestamps). +- Ensure syntax is valid for ${language}. +- Include metadata: "manualSteps" and "limitations". + +OUTPUT FORMAT: +{ + "testData": "generated test data in ${language}", + "manualSteps": ["things developer must do manually (configs, libraries, etc.)"], + "limitations": ["known gaps or assumptions"] +} +`; + const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${GEMINI_API_KEY}`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + contents: [ + { + role: "user", + parts: [ + { + text: prompt + } + ] + } + ] + }) + }); + if (!res.ok) { + const errText = await res.text(); + return new Response(JSON.stringify({ + error: errText + }), { + status: res.status, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } + const data = await res.json(); + let raw = (data.candidates?.[0]?.content?.parts?.[0]?.text || "").trim(); + raw = stripFences(raw); + const parsed = safeParseJSON(raw) || {}; + // Extra cleanup safety + if (parsed.testData) parsed.testData = stripFences(String(parsed.testData)); + return new Response(JSON.stringify(parsed), { + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } catch (err) { + return new Response(JSON.stringify({ + error: err.message + }), { + status: 500, + headers: { + ...corsHeaders, + "Content-Type": "application/json" + } + }); + } +});