import { GoogleGenAI, GenerateContentResponse } from "@google/genai"; import { Provider, AppType } from '../types'; type ImagePart = { mimeType: string; data: string; }; const ANALYSIS_PROMPT_PYTHON = ` You are an expert UI/UX analyst. Your task is to analyze the provided UI images and user instructions and generate a detailed textual specification for a developer. This specification will be used by another AI to write Python PyQt6 code. **Analysis Steps:** 1. **Examine the UI Images:** Identify all UI components (e.g., buttons, input fields, labels, sliders, web views, menus). Note their positions, sizes, colors, and any text they contain. 2. **Infer Layout and Structure:** Describe the overall layout of the application (e.g., grid layout, vertical box layout, main window with a status bar). Use PyQt6 layout managers in your description. 3. **Determine Functionality:** Based on the components and their context, infer the application's purpose and the function of each element. 4. **Incorporate User Instructions:** You MUST integrate any provided user instructions into your specification. These instructions override any inferences from the images. For example, if an image shows a blue button but the user asks to 'make the button green', your specification must describe a green button. **Output Format:** Provide a clear, detailed, and well-structured description of the application. Do NOT write any Python code. The output should be a blueprint that a PyQt6 developer can follow precisely. `; const PYQT_CODEGEN_PROMPT = ` You are an expert Python developer specializing in PyQt6. Your task is to create a fully functional desktop application based on a detailed specification. **Instructions:** 1. **Read the Specification:** Carefully read the entire application specification provided below. 2. **Generate Code:** Write a single, complete, and executable Python script using the PyQt6 library that implements the specification precisely. 3. **Implement All Logic:** The generated code must not only replicate the visual layout but also implement all the described functionality. - For web browsers, use PyQt6.QtWebEngineWidgets. - For calculators, ensure all buttons are connected to functions that perform the correct calculations. - For text editors, implement text editing and file operations. 4. **Code Requirements:** - The script must be self-contained and runnable. - Include all necessary imports. - Define a main window class (e.g., \`QMainWindow\`). - Connect signals to slots to implement functionality. - Include the standard boilerplate to instantiate and run the \`QApplication\`. 5. **Output Format:** Provide ONLY the raw Python code. Do not include any explanations, comments about the code, or markdown fences like \`\`\`python. --- APPLICATION SPECIFICATION --- `; const ANALYSIS_PROMPT_CPP = ` You are an expert UI/UX analyst. Your task is to analyze the provided UI images and user instructions and generate a detailed textual specification for a developer. This specification will be used by another AI to write a C++ Qt application. **Analysis Steps:** 1. **Examine the UI Images:** Identify all UI components (e.g., QPushButton, QLineEdit, QLabel, QSlider, QWebEngineView, QMenu). Note their positions, sizes, colors, and any text they contain. 2. **Infer Layout and Structure:** Describe the overall layout of the application (e.g., QGridLayout, QVBoxLayout, QMainWindow with a QStatusBar). Use C++ Qt layout managers in your description. 3. **Determine Functionality:** Based on the components and their context, infer the application's purpose and the function of each element. 4. **Incorporate User Instructions:** You MUST integrate any provided user instructions into your specification. These instructions override any inferences from the images. For example, if an image shows a blue button but the user asks to 'make the button green', your specification must describe a green button. **Output Format:** Provide a clear, detailed, and well-structured description of the application. Do NOT write any C++ code. The output should be a blueprint that a C++/Qt developer can follow precisely. `; const CPP_QT_CODEGEN_PROMPT = ` You are an expert C++ developer specializing in the Qt 6 framework. Your task is to create a fully functional, multi-file desktop application based on a detailed specification. **Instructions:** 1. **Read the Specification:** Carefully read the entire application specification provided below. 2. **Generate Code Structure:** Create a complete, compilable, and executable C++/Qt application with the following file structure: - \`main.cpp\`: The main entry point for the application. It should instantiate and show the main window. - \`mainwindow.h\`: The header file for your main window class (e.g., \`MainWindow\`), which should inherit from \`QMainWindow\`. It should declare all UI elements, layouts, and slots. - \`mainwindow.cpp\`: The implementation file for your main window class. It should define the constructor (where the UI is built), and implement all slots (functionality). 3. **Implement All Logic:** The generated code must not only replicate the visual layout but also implement all the described functionality. - For web browsers, use QWebEngineView from the QtWebEngineWidgets module. - For calculators, ensure all buttons are connected to slots that perform the correct calculations. 4. **Code Requirements:** - Use C++17 or later. - Include header guards in \`.h\` files. - Include all necessary Qt headers. - Connect signals to slots using the modern \`QObject::connect\` syntax. - The code must be clean, well-organized, and ready to be compiled with a standard build system (CMake, qmake, etc.). 5. **Output Format:** - You MUST provide the output as a single, valid JSON object. - The keys of the JSON object must be the filenames (e.g., "main.cpp", "mainwindow.h", "mainwindow.cpp"). - The values must be strings containing the complete, raw source code for the corresponding file. - Do not include any explanations, comments, or markdown fences like \`\`\`json. **Example JSON Output:** { "main.cpp": "#include \\"mainwindow.h\\"\\n#include \\n\\nint main(int argc, char *argv[])\\n{\\n QApplication a(argc, argv);\\n MainWindow w;\\n w.show();\\n return a.exec();\\n}", "mainwindow.h": "#ifndef MAINWINDOW_H\\n#define MAINWINDOW_H\\n\\n#include \\n\\nclass MainWindow : public QMainWindow\\n{\\n Q_OBJECT\\n\\npublic:\\n MainWindow(QWidget *parent = nullptr);\\n ~MainWindow();\\n};\\n#endif // MAINWINDOW_H", "mainwindow.cpp": "#include \\"mainwindow.h\\"\\n\\nMainWindow::MainWindow(QWidget *parent)\\n : QMainWindow(parent)\\n{\\n // UI setup code here\\n}\\n\\nMainWindow::~MainWindow()\\n{\\n}" } --- APPLICATION SPECIFICATION --- `; // --- Non-streaming API callers for analysis step --- async function callGemini(apiKey: string, prompt: string, imageParts: ImagePart[]): Promise { const ai = new GoogleGenAI({ apiKey }); const parts = [{ text: prompt }, ...imageParts.map(img => ({ inlineData: { mimeType: img.mimeType, data: img.data } }))]; const response: GenerateContentResponse = await ai.models.generateContent({ model: 'gemini-2.5-flash-preview-04-17', contents: [{ parts }] }); return response.text; } async function callOpenAI(apiKey: string, prompt: string, imageParts: ImagePart[]): Promise { const content = [{ type: 'text', text: prompt }, ...imageParts.map(img => ({ type: 'image_url', image_url: { url: `data:${img.mimeType};base64,${img.data}` } }))]; const body = { model: 'gpt-4o', messages: [{ role: 'user', content }], max_tokens: 4096 }; const response = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); if (!response.ok) { const err = await response.json(); throw new Error(`OpenAI API Error: ${err?.error?.message || response.statusText}`); } const data = await response.json(); return data.choices?.[0]?.message?.content ?? ''; } async function callAnthropic(apiKey: string, prompt: string, imageParts: ImagePart[]): Promise { const content = [{ type: 'text', text: prompt }, ...imageParts.map(img => ({ type: 'image', source: { type: 'base64', media_type: img.mimeType, data: img.data } }))]; const body = { model: 'claude-3-sonnet-20240229', messages: [{ role: 'user', content }], max_tokens: 4096 }; const response = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers: { 'x-api-key': apiKey, 'anthropic-version': '2023-06-01', 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); if (!response.ok) { const err = await response.json(); throw new Error(`Anthropic API Error: ${err?.error?.message || response.statusText}`); } const data = await response.json(); return data.content?.[0]?.text ?? ''; } // --- Unified Streaming API caller --- async function callApiStream( provider: Provider, apiKey: string, prompt: string, onChunk: (chunk: string) => void ): Promise { const model = provider === 'gemini' ? 'gemini-2.5-flash-preview-04-17' : (provider === 'openai' ? 'gpt-4o' : 'claude-3-sonnet-20240229'); let fullResponse = ''; const processChunk = (chunk: string) => { fullResponse += chunk; onChunk(chunk); }; if (provider === 'gemini') { const ai = new GoogleGenAI({ apiKey }); const response = await ai.models.generateContentStream({ model, contents: prompt }); for await (const chunk of response) { processChunk(chunk.text); } } else if (provider === 'openai') { const body = { model, messages: [{ role: 'user', content: prompt }], max_tokens: 4096, stream: true }; const response = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); if (!response.ok || !response.body) { const err = await response.json(); throw new Error(`OpenAI API Error: ${err?.error?.message || response.statusText}`); } const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n').filter(line => line.startsWith('data: ')); for (const line of lines) { const message = line.substring(6); if (message === '[DONE]') break; try { const json = JSON.parse(message); const textChunk = json.choices[0]?.delta?.content; if (textChunk) processChunk(textChunk); } catch (e) { /* Ignore parsing errors for incomplete chunks */ } } } } else if (provider === 'anthropic') { const body = { model, messages: [{ role: 'user', content: prompt }], max_tokens: 4096, stream: true }; const response = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers: { 'x-api-key': apiKey, 'anthropic-version': '2023-06-01', 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); if (!response.ok || !response.body) { const err = await response.json(); throw new Error(`Anthropic API Error: ${err?.error?.message || response.statusText}`); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const events = buffer.split('\n\n'); buffer = events.pop() || ''; // Keep the last, possibly incomplete, event in buffer for (const event of events) { if (!event.startsWith('event: content_block_delta')) continue; const dataLine = event.split('\n').find(line => line.startsWith('data: ')); if (dataLine) { try { const jsonData = JSON.parse(dataLine.substring(6)); if (jsonData.type === 'content_block_delta' && jsonData.delta.type === 'text_delta') { processChunk(jsonData.delta.text); } } catch (e) { /* Ignore incomplete JSON */ } } } } } return fullResponse; } export async function generateCode( appType: AppType, provider: Provider, apiKey: string, imageParts: ImagePart[], instructions: string, onStatusChange: (status: string) => void, onChunk: (chunk: string) => void ): Promise { try { const isPython = appType === 'python'; const analysisPromptTemplate = isPython ? ANALYSIS_PROMPT_PYTHON : ANALYSIS_PROMPT_CPP; const codegenPromptTemplate = isPython ? PYQT_CODEGEN_PROMPT : CPP_QT_CODEGEN_PROMPT; onStatusChange(`Step 1/2: Analyzing UI with ${provider}...`); const analysisPrompt = `${analysisPromptTemplate}${instructions ? `\n\n--- USER REFINEMENT INSTRUCTIONS ---\n${instructions.trim()}` : ''}`; const callAnalysisApi = { 'gemini': callGemini, 'openai': callOpenAI, 'anthropic': callAnthropic, }[provider]; const specification = await callAnalysisApi(apiKey, analysisPrompt, imageParts); if (!specification || specification.trim() === '') { throw new Error('AI failed to generate a UI specification. The response was empty.'); } const lang = isPython ? 'Python' : 'C++'; onStatusChange(`Step 2/2: Generating ${lang} code with ${provider}...`); const codegenPrompt = `${codegenPromptTemplate}\n${specification}`; const finalResult = await callApiStream(provider, apiKey, codegenPrompt, (chunk) => { if (isPython) { // The first chunk from some models might be a markdown fence, remove it. const cleanedChunk = chunk.replace(/^```(python)?\n/, ''); onChunk(cleanedChunk); } else { // For C++, we just stream raw chunks. The final string will be parsed. onChunk(chunk); } }); if (isPython) { // Final cleanup of trailing markdown fence for Python. onChunk('\n```'); return; } else { // For C++, return the complete JSON string for parsing in the component. return finalResult; } } catch (error) { console.error(`Error during code generation with ${provider}:`, error); if (error instanceof Error) { throw new Error(`Failed to communicate with the ${provider} API. ${error.message}`); } throw new Error("An unknown error occurred while generating code."); } }