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.");
  }
}