File size: 15,285 Bytes
4840fb6 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
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 ---
`;
// --- Non-streaming API callers for analysis step ---
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 ?? '';
}
// --- Unified Streaming API caller ---
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) { /* 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<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) {
// 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.");
}
}
|