|
|
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 <QApplication>\\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 <QMainWindow>\\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 --- |
|
|
`; |
|
|
|
|
|
|
|
|
|
|
|
async function callGemini(apiKey: string, prompt: string, imageParts: ImagePart[]): Promise<string> { |
|
|
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<string> { |
|
|
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<string> { |
|
|
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 ?? ''; |
|
|
} |
|
|
|
|
|
|
|
|
async function callApiStream( |
|
|
provider: Provider, |
|
|
apiKey: string, |
|
|
prompt: string, |
|
|
onChunk: (chunk: string) => void |
|
|
): Promise<string> { |
|
|
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) { } |
|
|
} |
|
|
} |
|
|
} 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() || ''; |
|
|
|
|
|
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) { } |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
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<string | void> { |
|
|
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) { |
|
|
|
|
|
const cleanedChunk = chunk.replace(/^```(python)?\n/, ''); |
|
|
onChunk(cleanedChunk); |
|
|
} else { |
|
|
|
|
|
onChunk(chunk); |
|
|
} |
|
|
}); |
|
|
|
|
|
if (isPython) { |
|
|
|
|
|
onChunk('\n```'); |
|
|
return; |
|
|
} else { |
|
|
|
|
|
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."); |
|
|
} |
|
|
} |
|
|
|