import { GooseUltraComputerDriver, GooseUltraBrowserDriver, GooseUltraServerDriver, Project } from '../types'; import { runVibeGuard, loadCurrentState, saveCurrentState, recordInteraction, ExecutionMode, saveSnapshot } from './ContextEngine'; import { SafeGenStreamer } from './StreamHandler'; const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); // Global model accessor - reads from localStorage to support Ollama integration // This is updated by the React orchestrator when the model changes export const getActiveModel = (): string => { try { const stored = localStorage.getItem('goose-active-model'); return stored || 'qwen-coder-plus'; } catch { return 'qwen-coder-plus'; } }; export const setActiveModel = (model: string): void => { try { localStorage.setItem('goose-active-model', model); } catch { // Ignore storage errors } }; export const CANVAS_ISOLATION_ARCHITECTURE = ` ### CANVAS ISOLATION ARCHITECTURE 1. **Base Canvas**: Existing code is immutable. Avoid touching unrelated blocks. 2. **Isolation Chamber**: Extract ONLY specific sections mentioned. 3. **Precision Merge**: Merge back using surgical anchors. 100% preservation. 4. **Boundary Mapping**: Identify exact coordinates. No cascading changes. `; // --- GEMINI 3 PRO / VIBE CODING TEMPLATE --- export const FRAMEWORK_TEMPLATE_PROMPT = ` You are an expert Frontend Engineer. Your task is to implement the User's Plan into a SINGLE, HIGH-FIDELITY HTML FILE using the REQUESTED FRAMEWORK. ### TECHNICAL REQUIREMENTS: 1. ** Single File **: Everything(HTML, CSS, JS) must be in one file. 2. ** CDNs Only **: Use reputable CDNs(cdnjs, unpkg, esm.sh) for libraries. 3. ** React **: If React is requested, use React 18 + ReactDOM 18 + Babel Standalone(for JSX). - - - - 4. ** Vue **: If Vue is requested, use Vue 3 global build. - 5. ** Aesthetics **: Unless the user specified a specific CSS framework(like Bootstrap), DEFAULT to ** TailwindCSS ** for styling to maintain "Vibe" quality. - ### CRITICAL: CLIENT - SIDE ONLY - ** NO SERVER - SIDE TEMPLATES **: DO NOT use Jinja2, Liquid, PHP, etc. - ** NO NODE.JS SPECIFIC **: No 'require', no 'process.env', no 'npm install'. - ** Mock Data **: Create dummy data arrays inside the script. ### OUTPUT FORMAT: - RETURN ** ONLY ** THE RAW HTML CODE. - DO NOT USE MARKDOWN BLOCK. - DO NOT include or display the plan / instructions in the UI.The plan is input - only. `; export const MODERN_TEMPLATE_PROMPT = ` You are an elite Frontend Architect simulating the reasoning and coding quality of Google Gemini 1.5 Pro. ### DESIGN & TECH STACK(NON - NEGOTIABLE) 1. ** Framework **: Vanilla HTML5 + JavaScript(ES6 +).NO React / Vue / Angular unless explicitly requested. 2. ** Styling **: Tailwind CSS(via CDN).Use modern utility classes(flex, grid, glassmorphism, gradients).Use slate / zinc / neutral for structural greys and vibrant indigo / violet / blue for accents. 3. ** Icons **: FontAwesome(via CDN). 4. ** Font **: 'Inter' or 'JetBrains Mono' via Google Fonts. ### ATOMIC OUTPUT PROTOCOL 1. You are strictly forbidden from 'chatting' your code. 2. You must output the code inside a standard XML block: . 3. Do not output internal tool logs(like << goose) inside this block. Example Output: ... `; export const MockComputerDriver: GooseUltraComputerDriver = { checkArmed: () => true, // In real app, check env var or system flag runAction: async (action, params) => { console.log(`[Desktop] ${action} `, params); await sleep(800); return { status: 'success', screenshot: 'https://picsum.photos/800/600' }; } }; export const MockBrowserDriver: GooseUltraBrowserDriver = { navigate: async (url) => { console.log(`[Browser] Navigate: ${url} `); await sleep(1000); }, assert: async (selector) => { console.log(`[Browser] Assert: ${selector} `); await sleep(500); return true; // Always pass in mock } }; export const MockServerDriver: GooseUltraServerDriver = { connect: async (host) => { console.log(`[Server] Connecting to ${host}...`); await sleep(1500); return true; }, runCommand: async (cmd, dryRun) => { if (dryRun) return `[DryRun] Would execute: ${cmd} `; console.log(`[Server] Executing: ${cmd} `); await sleep(1000); return `Success: ${cmd} executed.\nLogs: [System OK]`; } }; // P0-2: Task Match Gate export interface TaskMatchResult { matchesRequest: boolean; why: string; detectedAppType: string; requestType: string; } const runTaskMatchCheck = async ( plan: string, userRequest: string ): Promise => { const electron = (window as any).electron; if (!electron) return { matchesRequest: true, why: "Offline", detectedAppType: "unknown", requestType: "unknown" }; // Skip in offline mode const prompt = `You are a strict QA Gatekeeper. User Request: "${userRequest}" Proposed Plan: "${plan.substring(0, 500)}..." Analyze if the Plan matches the User Request category. - If User asked for a Game and Plan is a Dashboard -> FAIL. - If User asked for a Portfolio and Plan is a Chatbot -> FAIL. - If they vaguely match(e.g., "App" and "Dashboard"), PASS. OUTPUT ONLY JSON: { "matchesRequest": boolean, "why": "string reason", "detectedAppType": "string", "requestType": "string" } `; return new Promise((resolve) => { let buf = ''; const onChunk = (c: string) => buf += c; const onComplete = (c: string) => { electron.removeChatListeners(); const final = (c && c.length > buf.length ? c : buf).replace(/```json/gi, '').replace(/```/g, '').trim(); try { const res = JSON.parse(final); resolve(res); } catch (e) { resolve({ matchesRequest: true, why: "JSON Parse Error", detectedAppType: "unknown", requestType: "unknown" }); } }; electron.onChatChunk(onChunk); electron.onChatComplete(onComplete); electron.startChat([ { role: 'system', content: prompt } ], getActiveModel()); }); }; export const compilePlanToCode = async ( plan: string, onChunk?: (code: string) => void, projectId?: string, preferredFramework?: string | null, persona?: { name: string, prompt: string } | null, skills?: string[] | null ): Promise => { const electron = (window as any).electron; if (!electron) return "

Offline Mode

"; // M1: Load Project Memory let memoryContext = ""; if (projectId) { const memories = await loadProjectMemories(projectId); // M3: Retrieve Top-K const relevant = retrieveRelevantMemories(memories, plan, 5); // Top-5 relevant to the plan memoryContext = formatMemoriesForPrompt(relevant); } const MAX_RETRIES = 3; let attempt = 0; let lastQaReport: QualityReport | null = null; // P0-2: Run Task Match Check before generating code (Pass 1) // We infer the original request from the plan title or first few lines if not explicitly passed. // Ideally, we passed the request explicitly, but for now we extract it from the plan header. if (onChunk) onChunk("// Verifying Plan Alignment with User Request..."); // VISUAL FEEDBACK const userRequestInferred = plan.split('\n')[0].replace('#', '').trim() || "App Build"; // Add Timeout to prevent stuck state const matchResultPromise = runTaskMatchCheck(plan, userRequestInferred); const timeoutPromise = new Promise(resolve => setTimeout(() => resolve({ matchesRequest: true, why: "Timeout (Fail Open)", detectedAppType: "unknown", requestType: "unknown" }), 5000) ); const matchResult = await Promise.race([matchResultPromise, timeoutPromise]); if (!matchResult.matchesRequest) { console.error("[AutoService] BLOCKED: Plan matches " + matchResult.detectedAppType + " but User asked for " + matchResult.requestType); // We could throw here, but the contract says "Auto-retry once with stronger instructions". // We will inject this failure into the prompt. } while (attempt < MAX_RETRIES) { attempt++; console.log(`[AutoService] Compiling Plan to Code... Attempt ${attempt}/${MAX_RETRIES}`); // VISUAL FEEDBACK if (onChunk) onChunk(`// Initializing Build Engine (Attempt ${attempt})...`); try { const result = await new Promise((resolve, reject) => { let fullResponse = ''; // Ensure clean state BEFORE attaching new listeners electron.removeChatListeners(); let lastSafeCode = ''; const streamer = new SafeGenStreamer(); // ... (handlers definition) const onChunkHandler = (c: string) => { const safeUpdate = streamer.processChunk(c); if (safeUpdate) { if (!safeUpdate.startsWith(""); } }; const onErrorHandler = (e: any) => { cleanup(); reject(e); }; const cleanup = () => { electron.removeChatListeners(); }; electron.onChatChunk(onChunkHandler); electron.onChatComplete(onCompleteHandler); electron.onChatError(onErrorHandler); // ... (Prompt Construction) // Construct Prompt (F5: XML Artifact Bundle) let systemPrompt = `You are a strict Build Engine. ${CANVAS_ISOLATION_ARCHITECTURE} 1. FIRST, output a block. Discuss strategy/design (e.g. "Using dark theme..."). 2. THEN, output the files using tags. SCHEMA: ... ... body { ... } RULES: 1. Do NOT escape content (no need for \\n or \\"). 2. Do NOT use markdown code blocks (\`\`\`) inside the tags. 3. index.html MUST be a complete file. 4. Output MUST start with . 5. DO NOT generate a "Plan" or "Documentation" page. Generate the ACTUAL APP requested. `; let userContent = "IMPLEMENTATION PLAN (input-only):\n" + plan; if (memoryContext) { systemPrompt += "\n" + memoryContext; } if (preferredFramework) { systemPrompt += `\n\n[USER PREFERENCE]: Use "${preferredFramework}".`; } // --- INJECT PERSONA & SKILLS --- if (persona) { systemPrompt += `\n\n[ACTIVE PERSONA]: You are acting as "${persona.name}".\n${persona.prompt}`; } if (skills && skills.length > 0) { systemPrompt += `\n\n[AVAILABLE SKILLS]: You have the following capabilities/skills available: ${skills.join(', ')}. Ensure you leverage them if relevant to the implementation.`; } // P0-2: Task Mismatch Handling (Inject into Prompt) if (!matchResult.matchesRequest && attempt === 1) { systemPrompt += `\n\n⚠️ CRITICAL WARNING: Previous plan drifted. User asked for ${matchResult.requestType}. BUILD THAT.`; } // Retry Logic with Repair Prompt if (attempt > 1 && lastQaReport) { // We just reuse strict mode instructions systemPrompt += `\n\n[REPAIR MODE] Fix these errors:\n${lastQaReport.gates.filter(g => !g.passed).map(g => g.errors.join(', ')).join('\n')}`; } // Just start chat - listeners are already attached (and cleaned before that) electron.startChat([ { role: 'system', content: systemPrompt }, { role: 'user', content: userContent } ], getActiveModel()); }); // F5: Parse XML Bundle for QA let filesForQa: Record = {}; try { // Strip thinking block for cleaner processing (optional, regex handles it but good for debug) const regex = /([\s\S]*?)<\/goose_file>/g; let m; while ((m = regex.exec(result)) !== null) { if (m[1] && m[2]) { filesForQa[m[1]] = m[2].trim(); } } // Fallback: If no tags found, try legacy detection (plain HTML) if (Object.keys(filesForQa).length === 0) { if (result.includes(' !g.passed).map(g => g.gate)); lastQaReport = qaReport; if (attempt >= MAX_RETRIES) { console.error("[AutoService] QA failed after max retries. Returning best effort."); return result; } await sleep(1000); } } catch (e) { if (attempt >= MAX_RETRIES) throw e; console.error(`[AutoService] Attempt ${attempt} error:`, e); await sleep(1000); } } throw new Error("Code generation failed unexpectedly."); }; const PLAN_TAG_RE = /\[+\s*plan\s*\]+/gi; const stripPlanTag = (text: string) => text.replace(PLAN_TAG_RE, '').replace(/^\s*plan\s*:\s*/i, '').trim(); // --- Fingerprint Helpers for Redesign Detection --- const computeDomSignature = (html: string): string => { // Extract tag sequence from body content const bodyMatch = html.match(/]*>([\s\S]*?)<\/body>/i); const body = bodyMatch ? bodyMatch[1] : html; const tags = body.match(/<(\w+)[^>]*>/g) || []; const tagNames = tags.slice(0, 50).map(t => t.match(/<(\w+)/)?.[1] || '').join(','); // Simple hash let hash = 0; for (let i = 0; i < tagNames.length; i++) { hash = ((hash << 5) - hash) + tagNames.charCodeAt(i); hash |= 0; } return hash.toString(16); }; const computeCssSignature = (html: string): string => { // Extract color tokens and font-family const colors = html.match(/(#[0-9a-fA-F]{3,8}|rgb\([^)]+\)|hsl\([^)]+\))/g) || []; const fonts = html.match(/font-family:\s*([^;]+)/g) || []; const signature = [...colors.slice(0, 10), ...fonts.slice(0, 3)].join('|'); let hash = 0; for (let i = 0; i < signature.length; i++) { hash = ((hash << 5) - hash) + signature.charCodeAt(i); hash |= 0; } return hash.toString(16); }; const computeLayoutSignature = (html: string): string => { // Count major structural elements const hasNav = /]*hero|class="[^"]*hero/i.test(html); const hasFooter = /