RosticFACE commited on
Commit
4840fb6
·
verified ·
1 Parent(s): 3ce9f19

Upload 28 files

Browse files
App.tsx ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useCallback, useEffect } from 'react';
2
+ import { AppStatus, Provider, AppType } from './types';
3
+ import { generateCode } from './services/geminiService';
4
+ import ImageUploader from './components/ImageUploader';
5
+ import CodeDisplay from './components/CodeDisplay';
6
+ import Loader from './components/Loader';
7
+ import PythonIcon from './components/icons/PythonIcon';
8
+ import CppIcon from './components/icons/CppIcon';
9
+ import SparklesIcon from './components/icons/SparklesIcon';
10
+ import ApiConfig from './components/ApiConfig';
11
+ import AppTypeSelector from './components/AppTypeSelector';
12
+
13
+ const App: React.FC = () => {
14
+ const [status, setStatus] = useState<AppStatus>(AppStatus.IDLE);
15
+ const [appType, setAppType] = useState<AppType | null>(null);
16
+ const [generatedCode, setGeneratedCode] = useState<string>('');
17
+ const [generatedFiles, setGeneratedFiles] = useState<Record<string, string> | null>(null);
18
+ const [errorMessage, setErrorMessage] = useState<string>('');
19
+ const [imageFiles, setImageFiles] = useState<File[]>([]);
20
+ const [imagePreviewUrls, setImagePreviewUrls] = useState<string[]>([]);
21
+ const [instructions, setInstructions] = useState<string>('');
22
+ const [loadingMessage, setLoadingMessage] = useState<string>('');
23
+
24
+ const [provider, setProvider] = useState<Provider>('gemini');
25
+ const [geminiApiKey, setGeminiApiKey] = useState<string>('');
26
+ const [openaiApiKey, setOpenaiApiKey] = useState<string>('');
27
+ const [anthropicApiKey, setAnthropicApiKey] = useState<string>('');
28
+
29
+ useEffect(() => {
30
+ // Load keys and provider from localStorage on initial render.
31
+ const savedGeminiKey = localStorage.getItem('geminiApiKey');
32
+ const savedOpenaiKey = localStorage.getItem('openaiApiKey');
33
+ const savedAnthropicKey = localStorage.getItem('anthropicApiKey');
34
+ const savedProvider = localStorage.getItem('provider') as Provider;
35
+
36
+ if (savedGeminiKey) setGeminiApiKey(savedGeminiKey);
37
+ if (savedOpenaiKey) setOpenaiApiKey(savedOpenaiKey);
38
+ if (savedAnthropicKey) setAnthropicApiKey(savedAnthropicKey);
39
+ if (savedProvider) setProvider(savedProvider);
40
+
41
+ // Effect to clean up object URLs
42
+ return () => {
43
+ imagePreviewUrls.forEach(url => URL.revokeObjectURL(url));
44
+ };
45
+ }, []); // Run only once on mount
46
+
47
+ const resetStateForNewRun = () => {
48
+ setGeneratedCode('');
49
+ setGeneratedFiles(null);
50
+ setErrorMessage('');
51
+ setStatus(AppStatus.IDLE);
52
+ }
53
+
54
+ const handleProviderChange = (newProvider: Provider) => {
55
+ setProvider(newProvider);
56
+ localStorage.setItem('provider', newProvider);
57
+ };
58
+
59
+ const handleApiKeyChange = (keyType: Provider, key: string) => {
60
+ switch (keyType) {
61
+ case 'gemini':
62
+ setGeminiApiKey(key);
63
+ localStorage.setItem('geminiApiKey', key);
64
+ break;
65
+ case 'openai':
66
+ setOpenaiApiKey(key);
67
+ localStorage.setItem('openaiApiKey', key);
68
+ break;
69
+ case 'anthropic':
70
+ setAnthropicApiKey(key);
71
+ localStorage.setItem('anthropicApiKey', key);
72
+ break;
73
+ }
74
+ };
75
+
76
+ const fileToBase64 = (file: File): Promise<{mimeType: string, data: string}> => {
77
+ return new Promise((resolve, reject) => {
78
+ const reader = new FileReader();
79
+ reader.readAsDataURL(file);
80
+ reader.onload = () => {
81
+ const result = reader.result as string;
82
+ const [header, data] = result.split(',');
83
+ const mimeType = header.match(/:(.*?);/)?.[1] || 'application/octet-stream';
84
+ resolve({ mimeType, data });
85
+ };
86
+ reader.onerror = error => reject(error);
87
+ });
88
+ };
89
+
90
+ const handleImageUpload = (newFiles: File[]) => {
91
+ const filesToUpload = newFiles.slice(0, 10);
92
+ setImageFiles(filesToUpload);
93
+ // Revoke old URLs before creating new ones to prevent memory leaks
94
+ imagePreviewUrls.forEach(url => URL.revokeObjectURL(url));
95
+ setImagePreviewUrls(filesToUpload.map(file => URL.createObjectURL(file)));
96
+ resetStateForNewRun();
97
+ };
98
+
99
+ const handleRemoveImage = (indexToRemove: number) => {
100
+ const updatedFiles = imageFiles.filter((_, index) => index !== indexToRemove);
101
+ URL.revokeObjectURL(imagePreviewUrls[indexToRemove]);
102
+ const updatedUrls = imagePreviewUrls.filter((_, index) => index !== indexToRemove);
103
+ setImageFiles(updatedFiles);
104
+ setImagePreviewUrls(updatedUrls);
105
+ }
106
+
107
+ const handleGenerateClick = useCallback(async () => {
108
+ if (!appType) return;
109
+
110
+ const apiKeys = { gemini: geminiApiKey, openai: openaiApiKey, anthropic: anthropicApiKey };
111
+ const apiKey = apiKeys[provider];
112
+
113
+ if (!apiKey) {
114
+ setErrorMessage(`Please provide an API key for ${provider.charAt(0).toUpperCase() + provider.slice(1)}.`);
115
+ setStatus(AppStatus.ERROR);
116
+ return;
117
+ }
118
+
119
+ if (imageFiles.length === 0) {
120
+ setErrorMessage('Please upload at least one image first.');
121
+ setStatus(AppStatus.ERROR);
122
+ return;
123
+ }
124
+
125
+ setStatus(AppStatus.PROCESSING);
126
+ setErrorMessage('');
127
+ setGeneratedCode('');
128
+ setGeneratedFiles(null);
129
+ setLoadingMessage('Preparing to generate...');
130
+
131
+ try {
132
+ const imageParts = await Promise.all(imageFiles.map(file => fileToBase64(file)));
133
+
134
+ const result = await generateCode(
135
+ appType,
136
+ provider,
137
+ apiKey,
138
+ imageParts,
139
+ instructions,
140
+ (statusMsg) => setLoadingMessage(statusMsg),
141
+ (chunk) => {
142
+ if (appType === 'python') {
143
+ setGeneratedCode(prev => prev + chunk);
144
+ } else {
145
+ // For C++, we accumulate the JSON string representation
146
+ setGeneratedCode(prev => prev + chunk);
147
+ }
148
+ }
149
+ );
150
+
151
+ if(appType === 'c++') {
152
+ try {
153
+ const finalJsonString = result as string;
154
+ // Clean potential markdown fences
155
+ const fenceRegex = /^```(json)?\s*\n?(.*?)\n?\s*```$/s;
156
+ const match = finalJsonString.match(fenceRegex);
157
+ const cleanedJson = match ? match[2] : finalJsonString;
158
+ const files = JSON.parse(cleanedJson);
159
+ setGeneratedFiles(files);
160
+ setGeneratedCode(''); // Clear the raw string
161
+ } catch(e) {
162
+ console.error("Failed to parse C++ file JSON:", e);
163
+ throw new Error("AI returned an invalid format for C++ files. Please try again.");
164
+ }
165
+ }
166
+
167
+ setStatus(AppStatus.SUCCESS);
168
+ } catch (error) {
169
+ console.error(error);
170
+ setErrorMessage(error instanceof Error ? error.message : 'An unknown error occurred.');
171
+ setStatus(AppStatus.ERROR);
172
+ }
173
+ }, [appType, imageFiles, instructions, provider, geminiApiKey, openaiApiKey, anthropicApiKey]);
174
+
175
+ if (!appType) {
176
+ return <AppTypeSelector onSelect={setAppType} />;
177
+ }
178
+
179
+ const OutputArea: React.FC = () => {
180
+ switch (status) {
181
+ case AppStatus.PROCESSING:
182
+ return (
183
+ <div className="flex flex-col items-center justify-center h-full text-center p-4">
184
+ <Loader />
185
+ <p className="mt-4 text-lg text-gray-300">{loadingMessage || 'Initializing...'}</p>
186
+ <p className="text-sm text-gray-500 capitalize">Using {provider} to analyze UI and generate code.</p>
187
+ { appType === 'python' && generatedCode &&
188
+ <pre className="mt-4 w-full max-h-64 overflow-y-auto bg-gray-900/50 p-4 rounded-lg text-left">
189
+ <code className="language-python text-xs font-code whitespace-pre-wrap text-gray-400">
190
+ {generatedCode}
191
+ </code>
192
+ </pre>
193
+ }
194
+ </div>
195
+ );
196
+ case AppStatus.SUCCESS:
197
+ return <CodeDisplay
198
+ appType={appType}
199
+ pythonCode={generatedCode}
200
+ cppFiles={generatedFiles}
201
+ status={status} />;
202
+ case AppStatus.ERROR:
203
+ return (
204
+ <div className="flex items-center justify-center h-full">
205
+ <div className="text-center bg-red-900/20 border border-red-600 p-6 rounded-lg">
206
+ <h3 className="text-2xl font-bold text-red-400">Generation Failed</h3>
207
+ <p className="mt-2 text-red-300">{errorMessage}</p>
208
+ </div>
209
+ </div>
210
+ );
211
+ case AppStatus.IDLE:
212
+ default:
213
+ return (
214
+ <div className="flex flex-col items-center justify-center h-full text-center text-gray-500 p-8">
215
+ {appType === 'python' ? <PythonIcon className="w-24 h-24 mb-4 opacity-20" /> : <CppIcon className="w-24 h-24 mb-4 opacity-20" />}
216
+ <h3 className="text-2xl font-semibold text-gray-400">Functional Code Output</h3>
217
+ <p className="mt-2 max-w-sm">Configure your API, upload UI images, and provide instructions to generate a functional {appType === 'python' ? 'Python' : 'C++'} script.</p>
218
+ </div>
219
+ );
220
+ }
221
+ };
222
+
223
+ const isApiKeyMissing = !( (provider === 'gemini' && geminiApiKey) || (provider === 'openai' && openaiApiKey) || (provider === 'anthropic' && anthropicApiKey) );
224
+
225
+ return (
226
+ <div className="min-h-screen bg-gray-900 text-white flex flex-col p-4 sm:p-6 lg:p-8">
227
+ <header className="text-center mb-8">
228
+ <button onClick={() => setAppType(null)} className="text-gray-400 hover:text-white mb-4 text-sm">&larr; Back to selection</button>
229
+ <h1 className="text-4xl sm:text-5xl font-bold bg-gradient-to-r from-green-300 via-blue-400 to-purple-500 text-transparent bg-clip-text">
230
+ Functional {appType === 'python' ? 'PyQt' : 'C++ Qt'} App Generator
231
+ </h1>
232
+ <p className="text-gray-400 mt-2 text-lg">Turn UI Images into Functional {appType === 'python' ? 'Python' : 'C++'} Apps with AI</p>
233
+ </header>
234
+
235
+ <main className="flex-grow grid grid-cols-1 lg:grid-cols-2 gap-8">
236
+ <div className="bg-gray-800/50 border border-gray-700 rounded-2xl p-6 flex flex-col shadow-2xl shadow-black/20">
237
+ <div className="flex-grow">
238
+ <ApiConfig
239
+ provider={provider}
240
+ setProvider={handleProviderChange}
241
+ apiKeys={{ gemini: geminiApiKey, openai: openaiApiKey, anthropic: anthropicApiKey }}
242
+ setApiKey={handleApiKeyChange}
243
+ isProcessing={status === AppStatus.PROCESSING}
244
+ />
245
+
246
+ <div>
247
+ <h2 className="text-2xl font-semibold mb-4 text-gray-200">1. Upload UI Images</h2>
248
+ <ImageUploader onImageUpload={handleImageUpload} isProcessing={status === AppStatus.PROCESSING} />
249
+ {imagePreviewUrls.length > 0 && (
250
+ <div className="mt-6 w-full">
251
+ <p className="text-center mb-3 font-semibold text-gray-300">Image Previews ({imagePreviewUrls.length}/10)</p>
252
+ <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-3 xl:grid-cols-4 gap-4 p-4 border-2 border-dashed border-gray-600 rounded-xl bg-gray-900/50 max-h-80 overflow-y-auto">
253
+ {imagePreviewUrls.map((url, index) => (
254
+ <div key={url} className="relative group aspect-square">
255
+ <img src={url} alt={`UI Preview ${index + 1}`} className="w-full h-full object-contain rounded-lg bg-gray-800" />
256
+ <button
257
+ onClick={() => handleRemoveImage(index)}
258
+ disabled={status === AppStatus.PROCESSING}
259
+ className="absolute -top-2 -right-2 bg-red-600 text-white rounded-full w-6 h-6 flex items-center justify-center text-sm font-bold opacity-0 group-hover:opacity-100 transition-opacity duration-200 hover:bg-red-700 disabled:opacity-50 disabled:cursor-not-allowed"
260
+ aria-label={`Remove image ${index + 1}`}
261
+ >
262
+ &times;
263
+ </button>
264
+ </div>
265
+ ))}
266
+ </div>
267
+ </div>
268
+ )}
269
+ </div>
270
+ <div className="mt-8">
271
+ <h2 className="text-2xl font-semibold mb-4 text-gray-200">2. Add Instructions (Optional)</h2>
272
+ <textarea
273
+ value={instructions}
274
+ onChange={(e) => setInstructions(e.target.value)}
275
+ disabled={status === AppStatus.PROCESSING}
276
+ placeholder="e.g., 'Add a dark mode toggle', 'Make the primary button green'"
277
+ className="w-full h-28 p-3 bg-gray-900/70 border-2 border-gray-600 rounded-xl text-gray-300 placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 resize-none"
278
+ aria-label="Refinement instructions"
279
+ />
280
+ </div>
281
+ </div>
282
+ <button
283
+ onClick={handleGenerateClick}
284
+ disabled={imageFiles.length === 0 || status === AppStatus.PROCESSING || isApiKeyMissing}
285
+ className="w-full mt-6 py-4 px-6 rounded-xl bg-gradient-to-r from-blue-500 to-purple-600 text-white font-bold text-lg shadow-lg hover:shadow-xl hover:from-blue-600 hover:to-purple-700 transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed disabled:shadow-none flex items-center justify-center gap-3"
286
+ >
287
+ <SparklesIcon className="w-6 h-6"/>
288
+ {status === AppStatus.PROCESSING ? 'Generating...' : '3. Generate Code'}
289
+ </button>
290
+ </div>
291
+
292
+ <div className="bg-gray-800/50 border border-gray-700 rounded-2xl flex flex-col shadow-2xl shadow-black/20 overflow-hidden">
293
+ <h2 className="text-2xl font-semibold p-6 text-gray-200 border-b border-gray-700">Get Your {appType === 'python' ? 'Python' : 'C++'} Code</h2>
294
+ <div className="flex-grow bg-gray-900/70 overflow-y-auto">
295
+ <OutputArea />
296
+ </div>
297
+ </div>
298
+ </main>
299
+ <footer className="text-center mt-8 text-gray-500 text-sm">
300
+ <p>Powered by AI. Generated code may require adjustments.</p>
301
+ </footer>
302
+ </div>
303
+ );
304
+ };
305
+
306
+ export default App;
README.md ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: UI Generator From Image
3
+ emoji: 🐠
4
+ colorFrom: indigo
5
+ colorTo: red
6
+ sdk: static
7
+ pinned: false
8
+ app_build_command: npm run build
9
+ app_file: build/index.html
10
+ license: mit
11
+ ---
12
+
13
+ # AI-Powered UI to Code Generator
14
+
15
+ This application allows you to generate functional desktop application code directly from user interface images. It uses a powerful AI process where you can choose your provider (Google Gemini, OpenAI, or Anthropic) and your target language (**Python/PyQt6** or **C++/Qt**). The selected provider will analyze your UI images and then write the complete source code based on that analysis.
16
+
17
+ ## How to Use
18
+
19
+ 1. **Select Technology:** Choose between generating a **Python PyQt App** or a **C++ Qt App**.
20
+ 2. **Select Provider & Configure API:** Choose your preferred AI provider (Google Gemini, OpenAI, or Anthropic) and enter the corresponding API key. Your key is stored securely in your browser's local storage.
21
+ 3. **Upload Images:** Drag and drop or select one or more images (PNG, JPG, WEBP) of the UI you want to create.
22
+ 4. **Add Instructions (Optional):** Provide text prompts to guide the AI, such as "make the button green" or "the title should be 'My App'".
23
+ 5. **Generate Code:** Click the "Generate Code" button. The code will be generated by the AI.
24
+ 6. **Review, Download & Run:**
25
+ - **For Python:** The generated Python code will appear on the right with syntax highlighting. You can copy it or download it as a single `.py` file. Run it locally with `pip install PyQt6 PyQtWebEngine`.
26
+ - **For C++:** A multi-file viewer will appear. You can inspect the generated `.h` and `.cpp` files. Before downloading, choose a build system (**CMake**, **Makefile**, or **Meson**). Then, download the complete project as a **ZIP file**, ready to be compiled and run.
27
+
28
+ ## Features
29
+
30
+ - **Dual Language Support:** Generate code for both **Python (PyQt6)** and **C++ (Qt)**.
31
+ - **Multi-Provider Support:** Choose between Google Gemini, OpenAI (GPT-4o), and Anthropic (Claude 3).
32
+ - **Streaming Output (Python):** Python code is generated and displayed in real-time.
33
+ - **Multi-File Project Generation (C++):** Creates a complete, structured C++ project (`.h` and `.cpp` files).
34
+ - **Integrated Build Systems (C++):** Download a ready-to-compile ZIP archive with your choice of `Makefile`, `CMakeLists.txt`, or `meson.build`.
35
+ - **Syntax Highlighting:** Generated code is properly highlighted for readability.
36
+ - **Multi-Image Analysis:** Combine multiple screenshots to build a more complex UI.
37
+ - **Secure API Key Storage:** Keys are saved locally in your browser's storage.
38
+
39
+ ## Technology
40
+
41
+ This is a static web application built with:
42
+
43
+ - React & TypeScript
44
+ - Tailwind CSS (via CDN)
45
+ - **AI Providers**: Google Gemini, OpenAI, and Anthropic APIs
46
+ - **Code Generation Targets**: Python/PyQt6, C++/Qt
47
+ - **ZIP Archiving**: JSZip
48
+ - **Syntax Highlighting**: highlight.js
49
+ - Dependencies served via `esm.sh`
components/ApiConfig.d.ts ADDED
File without changes
components/ApiConfig.tsx ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import KeyIcon from './icons/KeyIcon';
3
+ import GoogleIcon from './icons/GoogleIcon';
4
+ import OpenAIIcon from './icons/OpenAIIcon';
5
+ import AnthropicIcon from './icons/AnthropicIcon';
6
+ import { Provider } from '../types';
7
+
8
+ interface ApiConfigProps {
9
+ provider: Provider;
10
+ setProvider: (provider: Provider) => void;
11
+ apiKeys: { gemini: string; openai: string; anthropic: string; };
12
+ setApiKey: (provider: Provider, key: string) => void;
13
+ isProcessing: boolean;
14
+ }
15
+
16
+ const ProviderTab: React.FC<{
17
+ providerName: Provider;
18
+ activeProvider: Provider;
19
+ setProvider: (provider: Provider) => void;
20
+ isProcessing: boolean;
21
+ children: React.ReactNode;
22
+ }> = ({ providerName, activeProvider, setProvider, isProcessing, children }) => {
23
+ const isActive = providerName === activeProvider;
24
+ const activeClasses = 'border-blue-400 text-white bg-gray-700/50';
25
+ const inactiveClasses = 'border-transparent text-gray-400 hover:bg-gray-700/30 hover:text-gray-200';
26
+
27
+ return (
28
+ <button
29
+ onClick={() => setProvider(providerName)}
30
+ disabled={isProcessing}
31
+ className={`flex-1 flex items-center justify-center gap-2 px-4 py-3 border-b-2 font-semibold transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-60 ${isActive ? activeClasses : inactiveClasses}`}
32
+ role="tab"
33
+ aria-selected={isActive}
34
+ >
35
+ {children}
36
+ </button>
37
+ );
38
+ };
39
+
40
+ const ApiConfig: React.FC<ApiConfigProps> = ({
41
+ provider,
42
+ setProvider,
43
+ apiKeys,
44
+ setApiKey,
45
+ isProcessing,
46
+ }) => {
47
+ const providerDetails = {
48
+ gemini: {
49
+ name: "Google Gemini",
50
+ model: "gemini-2.5-flash-preview-04-17",
51
+ url: "https://aistudio.google.com/app/apikey",
52
+ urlText: "Google AI Studio",
53
+ focusColor: "focus:ring-blue-500 focus:border-blue-500",
54
+ },
55
+ openai: {
56
+ name: "OpenAI",
57
+ model: "gpt-4o",
58
+ url: "https://platform.openai.com/api-keys",
59
+ urlText: "OpenAI Dashboard",
60
+ focusColor: "focus:ring-green-500 focus:border-green-500",
61
+ },
62
+ anthropic: {
63
+ name: "Anthropic",
64
+ model: "claude-3-sonnet-20240229",
65
+ url: "https://console.anthropic.com/settings/keys",
66
+ urlText: "Anthropic Console",
67
+ focusColor: "focus:ring-purple-500 focus:border-purple-500",
68
+ }
69
+ };
70
+
71
+ const currentProvider = providerDetails[provider];
72
+
73
+ return (
74
+ <div className="mb-8">
75
+ <h2 className="text-2xl font-semibold mb-4 text-gray-200 flex items-center gap-3">
76
+ <KeyIcon className="w-6 h-6" />
77
+ AI Provider Configuration
78
+ </h2>
79
+
80
+ <div className="flex border-b border-gray-700 mb-6" role="tablist">
81
+ <ProviderTab providerName="gemini" activeProvider={provider} setProvider={setProvider} isProcessing={isProcessing}>
82
+ <GoogleIcon className="w-5 h-5" /> Gemini
83
+ </ProviderTab>
84
+ <ProviderTab providerName="openai" activeProvider={provider} setProvider={setProvider} isProcessing={isProcessing}>
85
+ <OpenAIIcon className="w-5 h-5" /> OpenAI
86
+ </ProviderTab>
87
+ <ProviderTab providerName="anthropic" activeProvider={provider} setProvider={setProvider} isProcessing={isProcessing}>
88
+ <AnthropicIcon className="w-5 h-5" /> Anthropic
89
+ </ProviderTab>
90
+ </div>
91
+
92
+ <div className="space-y-4">
93
+ <div>
94
+ <label htmlFor="api-key" className="block text-sm font-medium text-gray-400 mb-1">
95
+ {currentProvider.name} API Key
96
+ </label>
97
+ <input
98
+ id="api-key"
99
+ type="password"
100
+ value={apiKeys[provider]}
101
+ onChange={(e) => setApiKey(provider, e.target.value)}
102
+ disabled={isProcessing}
103
+ placeholder={`Enter your ${currentProvider.name} API Key`}
104
+ className={`w-full p-3 bg-gray-900/70 border-2 border-gray-600 rounded-xl text-gray-300 placeholder-gray-500 transition-all duration-200 ${currentProvider.focusColor}`}
105
+ />
106
+ <p className="text-xs text-gray-500 mt-1">
107
+ Using model: <code className="bg-gray-700 px-1 rounded">{currentProvider.model}</code>. Get your key from <a href={currentProvider.url} target="_blank" rel="noopener noreferrer" className="underline hover:text-blue-400">{currentProvider.urlText}</a>.
108
+ </p>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ );
113
+ };
114
+
115
+ export default ApiConfig;
components/AppTypeSelector.tsx ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { AppType } from '../types';
3
+ import PythonIcon from './icons/PythonIcon';
4
+ import CppIcon from './icons/CppIcon';
5
+
6
+ interface AppTypeSelectorProps {
7
+ onSelect: (appType: AppType) => void;
8
+ }
9
+
10
+ const SelectionCard: React.FC<{
11
+ onClick: () => void;
12
+ icon: React.ReactNode;
13
+ title: string;
14
+ description: string;
15
+ }> = ({ onClick, icon, title, description }) => (
16
+ <button
17
+ onClick={onClick}
18
+ className="bg-gray-800/50 border border-gray-700 rounded-2xl p-8 flex flex-col items-center text-center w-full max-w-sm hover:bg-gray-700/50 hover:border-blue-500 transition-all duration-300 transform hover:-translate-y-2 shadow-lg hover:shadow-2xl"
19
+ >
20
+ <div className="w-24 h-24 mb-6 text-gray-300">{icon}</div>
21
+ <h3 className="text-3xl font-bold text-white mb-3">{title}</h3>
22
+ <p className="text-gray-400">{description}</p>
23
+ </button>
24
+ );
25
+
26
+
27
+ const AppTypeSelector: React.FC<AppTypeSelectorProps> = ({ onSelect }) => {
28
+ return (
29
+ <div className="min-h-screen bg-gray-900 text-white flex flex-col items-center justify-center p-4">
30
+ <div className="text-center mb-12">
31
+ <h1 className="text-5xl md:text-6xl font-bold bg-gradient-to-r from-green-300 via-blue-400 to-purple-500 text-transparent bg-clip-text">
32
+ UI-to-Code Generator
33
+ </h1>
34
+ <p className="text-gray-400 mt-4 text-xl">Choose your target technology to begin.</p>
35
+ </div>
36
+ <div className="flex flex-col md:flex-row gap-8">
37
+ <SelectionCard
38
+ onClick={() => onSelect('python')}
39
+ icon={<PythonIcon />}
40
+ title="Python PyQt App"
41
+ description="Generate a single-file, functional Python application using the PyQt6 library. Ideal for rapid prototyping and desktop tools."
42
+ />
43
+ <SelectionCard
44
+ onClick={() => onSelect('c++')}
45
+ icon={<CppIcon className="p-2" />}
46
+ title="C++ Qt App"
47
+ description="Generate a complete, multi-file C++ project using the Qt framework. Includes build files for CMake, Makefile, or Meson."
48
+ />
49
+ </div>
50
+ <footer className="absolute bottom-8 text-center text-gray-500 text-sm">
51
+ <p>Powered by AI. Select a technology to start turning UI images into functional apps.</p>
52
+ </footer>
53
+ </div>
54
+ );
55
+ };
56
+
57
+ export default AppTypeSelector;
components/CodeDisplay.tsx ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import JSZip from 'jszip';
3
+ import CopyIcon from './icons/CopyIcon';
4
+ import DownloadIcon from './icons/DownloadIcon';
5
+ import { AppStatus, AppType } from '../types';
6
+
7
+ interface CodeDisplayProps {
8
+ appType: AppType;
9
+ pythonCode?: string;
10
+ cppFiles?: Record<string, string> | null;
11
+ status: AppStatus;
12
+ }
13
+
14
+ const PythonCodeView: React.FC<{ code: string }> = ({ code }) => {
15
+ const [copyText, setCopyText] = useState('Copy Code');
16
+ const codeRef = useRef<HTMLElement>(null);
17
+ const finalCode = code.replace(/\n```$/, '').trim();
18
+
19
+ useEffect(() => {
20
+ // @ts-ignore
21
+ if (window.hljs && codeRef.current) {
22
+ // @ts-ignore
23
+ window.hljs.highlightElement(codeRef.current);
24
+ }
25
+ }, [finalCode]);
26
+
27
+ const handleCopy = () => {
28
+ navigator.clipboard.writeText(finalCode).then(() => {
29
+ setCopyText('Copied!');
30
+ setTimeout(() => setCopyText('Copy Code'), 2000);
31
+ });
32
+ };
33
+
34
+ const handleDownload = () => {
35
+ const blob = new Blob([finalCode], { type: 'text/python' });
36
+ const url = URL.createObjectURL(blob);
37
+ const a = document.createElement('a');
38
+ a.href = url;
39
+ a.download = 'generated_app.py';
40
+ document.body.appendChild(a);
41
+ a.click();
42
+ document.body.removeChild(a);
43
+ URL.revokeObjectURL(url);
44
+ };
45
+
46
+ return (
47
+ <div className="relative h-full">
48
+ <div className="absolute top-4 right-4 flex items-center gap-2 z-10">
49
+ <button onClick={handleDownload} className="flex items-center gap-2 bg-gray-700 text-white py-2 px-4 rounded-lg hover:bg-gray-600 transition-all duration-200 text-sm font-semibold" aria-label="Download code">
50
+ <DownloadIcon className="w-5 h-5" />
51
+ Download
52
+ </button>
53
+ <button onClick={handleCopy} className="flex items-center gap-2 bg-gray-700 text-white py-2 px-4 rounded-lg hover:bg-gray-600 transition-all duration-200 text-sm font-semibold" aria-label="Copy code">
54
+ <CopyIcon className="w-5 h-5" />
55
+ {copyText}
56
+ </button>
57
+ </div>
58
+ <pre className="h-full w-full overflow-auto p-6 pt-20">
59
+ <code ref={codeRef} className="language-python text-sm font-code whitespace-pre-wrap text-gray-300">
60
+ {finalCode}
61
+ </code>
62
+ </pre>
63
+ </div>
64
+ );
65
+ };
66
+
67
+ type BuildSystem = 'makefile' | 'cmake' | 'meson';
68
+
69
+ const CppCodeView: React.FC<{ files: Record<string, string> }> = ({ files }) => {
70
+ const [activeFile, setActiveFile] = useState(Object.keys(files)[0] || '');
71
+ const [buildSystem, setBuildSystem] = useState<BuildSystem>('cmake');
72
+ const [isDownloading, setIsDownloading] = useState(false);
73
+ const codeRef = useRef<HTMLElement>(null);
74
+
75
+ useEffect(() => {
76
+ // @ts-ignore
77
+ if (window.hljs && codeRef.current) {
78
+ // @ts-ignore
79
+ window.hljs.highlightElement(codeRef.current);
80
+ }
81
+ }, [activeFile, files]);
82
+
83
+ const getBuildFileContent = (): { filename: string, content: string } => {
84
+ const targetName = "MyApp";
85
+ const sources = Object.keys(files).filter(f => f.endsWith('.cpp')).join(' ');
86
+ const headers = Object.keys(files).filter(f => f.endsWith('.h')).join(' ');
87
+
88
+ switch(buildSystem) {
89
+ case 'makefile':
90
+ return {
91
+ filename: 'Makefile',
92
+ content: `
93
+ CXX = g++
94
+ CXXFLAGS = -std=c++17 -fPIC \`pkg-config --cflags Qt6Widgets Qt6Gui Qt6Core Qt6WebEngineWidgets\`
95
+ LDFLAGS = \`pkg-config --libs Qt6Widgets Qt6Gui Qt6Core Qt6WebEngineWidgets\`
96
+ TARGET = ${targetName}
97
+ SOURCES = ${sources}
98
+ OBJECTS = $(SOURCES:.cpp=.o)
99
+
100
+ .PHONY: all clean
101
+
102
+ all: $(TARGET)
103
+
104
+ $(TARGET): $(OBJECTS)
105
+ \t$(CXX) $(OBJECTS) -o $(TARGET) $(LDFLAGS)
106
+
107
+ %.o: %.cpp ${headers}
108
+ \t$(CXX) $(CXXFLAGS) -c $< -o $@
109
+
110
+ clean:
111
+ \trm -f $(OBJECTS) $(TARGET)
112
+ `.trim()
113
+ };
114
+ case 'cmake':
115
+ return {
116
+ filename: 'CMakeLists.txt',
117
+ content: `
118
+ cmake_minimum_required(VERSION 3.16)
119
+ project(${targetName} VERSION 1.0)
120
+
121
+ set(CMAKE_CXX_STANDARD 17)
122
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
123
+ set(CMAKE_AUTOMOC ON)
124
+ set(CMAKE_AUTORCC ON)
125
+ set(CMAKE_AUTOUIC ON)
126
+
127
+ find_package(Qt6 COMPONENTS Widgets Gui Core WebEngineWidgets REQUIRED)
128
+
129
+ add_executable(\${PROJECT_NAME}
130
+ ${sources.split(' ').join('\n ')}
131
+ ${headers.split(' ').join('\n ')}
132
+ )
133
+
134
+ target_link_libraries(\${PROJECT_NAME} PRIVATE Qt6::Widgets Qt6::Gui Qt6::Core Qt6::WebEngineWidgets)
135
+ `.trim()
136
+ };
137
+ case 'meson':
138
+ return {
139
+ filename: 'meson.build',
140
+ content: `
141
+ project('${targetName}', 'cpp', version : '1.0', default_options : ['cpp_std=c++17'])
142
+
143
+ qt6 = dependency('qt6', modules: ['Core', 'Gui', 'Widgets', 'WebEngineWidgets'], required: true)
144
+
145
+ executable(
146
+ '${targetName}',
147
+ files(${sources.split(' ').map(s=>`'${s}'`).join(', ')}),
148
+ dependencies: [qt6],
149
+ install: true
150
+ )
151
+ `.trim()
152
+ };
153
+ }
154
+ }
155
+
156
+ const handleDownloadZip = async () => {
157
+ setIsDownloading(true);
158
+ const zip = new JSZip();
159
+
160
+ Object.entries(files).forEach(([name, content]) => {
161
+ zip.file(name, content);
162
+ });
163
+
164
+ const buildFile = getBuildFileContent();
165
+ zip.file(buildFile.filename, buildFile.content);
166
+
167
+ try {
168
+ const content = await zip.generateAsync({type:"blob"});
169
+ const url = URL.createObjectURL(content);
170
+ const a = document.createElement('a');
171
+ a.href = url;
172
+ a.download = 'qt_app.zip';
173
+ document.body.appendChild(a);
174
+ a.click();
175
+ document.body.removeChild(a);
176
+ URL.revokeObjectURL(url);
177
+ } catch (e) {
178
+ console.error("Failed to generate ZIP", e);
179
+ } finally {
180
+ setIsDownloading(false);
181
+ }
182
+ };
183
+
184
+ if (!activeFile) return <div className="p-4 text-gray-400">No files were generated.</div>
185
+
186
+ return (
187
+ <div className="h-full flex flex-col">
188
+ <div className="flex-shrink-0 flex items-center justify-between border-b border-gray-700 p-2 gap-2 flex-wrap">
189
+ <div className="flex items-center gap-1 flex-wrap">
190
+ {Object.keys(files).map(name => (
191
+ <button key={name} onClick={() => setActiveFile(name)} className={`px-3 py-2 text-sm rounded-md transition-colors ${activeFile === name ? 'bg-blue-600 text-white font-semibold' : 'bg-gray-800 hover:bg-gray-700 text-gray-300'}`}>
192
+ {name}
193
+ </button>
194
+ ))}
195
+ </div>
196
+ </div>
197
+ <div className="flex-shrink-0 flex items-center justify-end border-b border-gray-700 p-2 gap-2 flex-wrap bg-gray-800/50">
198
+ <div className="flex items-center gap-2">
199
+ <label htmlFor="build-system" className="text-sm font-medium text-gray-300">Build System:</label>
200
+ <select id="build-system" value={buildSystem} onChange={e => setBuildSystem(e.target.value as BuildSystem)} className="bg-gray-700 text-white py-2 px-3 rounded-lg text-sm font-semibold border-0 focus:ring-2 focus:ring-blue-500">
201
+ <option value="cmake">CMake</option>
202
+ <option value="makefile">Makefile</option>
203
+ <option value="meson">Meson</option>
204
+ </select>
205
+ </div>
206
+ <button onClick={handleDownloadZip} disabled={isDownloading} className="flex items-center gap-2 bg-green-600 text-white py-2 px-4 rounded-lg hover:bg-green-500 transition-all duration-200 text-sm font-semibold disabled:bg-gray-500">
207
+ <DownloadIcon className="w-5 h-5" />
208
+ {isDownloading ? 'Zipping...' : 'Download ZIP'}
209
+ </button>
210
+ </div>
211
+ <div className="relative flex-grow overflow-hidden">
212
+ <pre className="h-full w-full overflow-auto p-6">
213
+ <code ref={codeRef} className="language-cpp text-sm font-code whitespace-pre-wrap text-gray-300">
214
+ {files[activeFile]}
215
+ </code>
216
+ </pre>
217
+ </div>
218
+ </div>
219
+ );
220
+ };
221
+
222
+
223
+ const CodeDisplay: React.FC<CodeDisplayProps> = ({ appType, pythonCode, cppFiles, status }) => {
224
+ if (status !== AppStatus.SUCCESS) {
225
+ return <div className="p-4">Generating...</div>;
226
+ }
227
+
228
+ return (
229
+ <div className="h-full flex flex-col bg-gray-900 rounded-b-2xl">
230
+ {appType === 'python' && pythonCode ? (
231
+ <PythonCodeView code={pythonCode} />
232
+ ) : appType === 'c++' && cppFiles ? (
233
+ <CppCodeView files={cppFiles} />
234
+ ) : (
235
+ <div className="p-4 text-gray-400">Code generation complete, but no output received.</div>
236
+ )}
237
+ </div>
238
+ );
239
+ };
240
+
241
+ export default CodeDisplay;
components/ImageUploader.tsx ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useRef } from 'react';
2
+ import UploadIcon from './icons/UploadIcon';
3
+
4
+ interface ImageUploaderProps {
5
+ onImageUpload: (files: File[]) => void;
6
+ isProcessing: boolean;
7
+ }
8
+
9
+ const ImageUploader: React.FC<ImageUploaderProps> = ({ onImageUpload, isProcessing }) => {
10
+ const fileInputRef = useRef<HTMLInputElement>(null);
11
+
12
+ const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
13
+ if (event.target.files) {
14
+ const files = Array.from(event.target.files);
15
+ if (files.length > 0) {
16
+ onImageUpload(files);
17
+ }
18
+ }
19
+ };
20
+
21
+ const handleClick = () => {
22
+ fileInputRef.current?.click();
23
+ };
24
+
25
+ const handleDrop = (event: React.DragEvent<HTMLLabelElement>) => {
26
+ event.preventDefault();
27
+ event.stopPropagation();
28
+ if (isProcessing) return;
29
+ if (event.dataTransfer.files) {
30
+ const files = Array.from(event.dataTransfer.files);
31
+ if (files.length > 0) {
32
+ onImageUpload(files);
33
+ }
34
+ }
35
+ };
36
+
37
+ const handleDragOver = (event: React.DragEvent<HTMLLabelElement>) => {
38
+ event.preventDefault();
39
+ event.stopPropagation();
40
+ };
41
+
42
+ return (
43
+ <div className="w-full">
44
+ <label
45
+ onDrop={handleDrop}
46
+ onDragOver={handleDragOver}
47
+ className={`w-full flex flex-col items-center justify-center p-8 border-4 border-dashed rounded-2xl transition-colors duration-300 ${isProcessing ? 'border-gray-600 cursor-not-allowed' : 'border-gray-500 hover:border-blue-500 hover:bg-gray-800 cursor-pointer'}`}
48
+ >
49
+ <UploadIcon className="w-16 h-16 mb-4 text-gray-500" />
50
+ <span className="text-xl font-semibold text-gray-300">Drag & Drop or Click to Upload</span>
51
+ <span className="mt-1 text-gray-400">Upload up to 10 images (PNG, JPG, WEBP)</span>
52
+ <input
53
+ ref={fileInputRef}
54
+ type="file"
55
+ accept="image/png, image/jpeg, image/webp"
56
+ className="hidden"
57
+ onChange={handleFileChange}
58
+ onClick={(e) => { (e.target as HTMLInputElement).value = '' }} // Allow re-uploading the same file
59
+ disabled={isProcessing}
60
+ multiple
61
+ />
62
+ </label>
63
+ <button
64
+ onClick={handleClick}
65
+ disabled={isProcessing}
66
+ className="w-full mt-4 py-3 px-4 bg-gray-700 rounded-lg font-semibold hover:bg-gray-600 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
67
+ >
68
+ Or Select File(s)
69
+ </button>
70
+ </div>
71
+ );
72
+ };
73
+
74
+ export default ImageUploader;
components/Loader.tsx ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ const Loader: React.FC = () => {
4
+ return (
5
+ <svg
6
+ className="w-20 h-20 animate-spin text-purple-400"
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ fill="none"
9
+ viewBox="0 0 24 24"
10
+ >
11
+ <circle
12
+ className="opacity-25"
13
+ cx="12"
14
+ cy="12"
15
+ r="10"
16
+ stroke="currentColor"
17
+ strokeWidth="4"
18
+ ></circle>
19
+ <path
20
+ className="opacity-75"
21
+ fill="currentColor"
22
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
23
+ ></path>
24
+ </svg>
25
+ );
26
+ };
27
+
28
+ export default Loader;
components/icons/AnthropicIcon.tsx ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ const AnthropicIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
4
+ <svg
5
+ viewBox="0 0 24 24"
6
+ fill="currentColor"
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ {...props}
9
+ >
10
+ <path
11
+ d="M17.5 22.4286H6.5C5.83696 22.4286 5.20107 22.1672 4.73223 21.6983C4.26339 21.2295 4 20.5936 4 19.9286V4.5C4 3.83696 4.26339 3.20107 4.73223 2.73223C5.20107 2.26339 5.83696 2 6.5 2H17.5C18.163 2 18.7989 2.26339 19.2678 2.73223C19.7366 3.20107 20 3.83696 20 4.5V19.9286C20 20.5936 19.7366 21.2295 19.2678 21.6983C18.7989 22.1672 18.163 22.4286 17.5 22.4286ZM12 8.21429C11.129 8.21429 10.2941 8.56021 9.68153 9.17277C9.06897 9.78534 8.72305 10.6202 8.72305 11.4912V16.6072H11.1159V11.4912C11.1159 11.2334 11.2163 10.9859 11.3967 10.8055C11.5772 10.6251 11.8247 10.5247 12.0824 10.5247H12.277V8.21429H12Z"
12
+ />
13
+ </svg>
14
+ );
15
+
16
+ export default AnthropicIcon;
components/icons/CopyIcon.tsx ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ const CopyIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
4
+ <svg
5
+ xmlns="http://www.w3.org/2000/svg"
6
+ width="24"
7
+ height="24"
8
+ viewBox="0 0 24 24"
9
+ fill="none"
10
+ stroke="currentColor"
11
+ strokeWidth="2"
12
+ strokeLinecap="round"
13
+ strokeLinejoin="round"
14
+ {...props}
15
+ >
16
+ <rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect>
17
+ <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path>
18
+ </svg>
19
+ );
20
+
21
+ export default CopyIcon;
components/icons/CppIcon.tsx ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ const CppIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
4
+ <svg viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg" {...props}>
5
+ <g fill="none" stroke="#FFF" strokeWidth="8">
6
+ <path d="M52 28l-32 36 32 36" strokeLinecap="round" strokeLinejoin="round" />
7
+ <path d="M76 28l32 36-32 36" strokeLinecap="round" strokeLinejoin="round" />
8
+ </g>
9
+ <path fill="#FFF" d="M76.5 61.5h-25v-25h-6v25h-25v6h25v25h6v-25h25z" />
10
+ <path fill="#FFF" d="M124.5 61.5h-25v-25h-6v25h-25v6h25v25h6v-25h25z" />
11
+ </svg>
12
+ );
13
+
14
+ export default CppIcon;
components/icons/DownloadIcon.tsx ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ const DownloadIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
4
+ <svg
5
+ xmlns="http://www.w3.org/2000/svg"
6
+ width="24"
7
+ height="24"
8
+ viewBox="0 0 24 24"
9
+ fill="none"
10
+ stroke="currentColor"
11
+ strokeWidth="2"
12
+ strokeLinecap="round"
13
+ strokeLinejoin="round"
14
+ {...props}
15
+ >
16
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
17
+ <polyline points="7 10 12 15 17 10" />
18
+ <line x1="12" y1="15" x2="12" y2="3" />
19
+ </svg>
20
+ );
21
+
22
+ export default DownloadIcon;
components/icons/GoogleIcon.tsx ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ const GoogleIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
4
+ <svg
5
+ viewBox="0 0 24 24"
6
+ fill="none"
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ {...props}
9
+ >
10
+ <path
11
+ d="M21.35,11.1H12.18V13.83H18.68C18.43,15.63 17.22,17.91 14.88,17.91C12.38,17.91 10.33,15.91 10.33,13.33C10.33,10.75 12.38,8.75 14.88,8.75C16.03,8.75 16.93,9.18 17.58,9.8L19.78,7.6C18.08,5.98 16.35,5 14.88,5C10.63,5 6.98,8.38 6.98,13.33C6.98,18.28 10.63,21.66 14.88,21.66C19.13,21.66 21.6,18.68 21.6,13.58C21.6,12.7 21.5,11.85 21.35,11.1Z"
12
+ fill="#4285F4"
13
+ />
14
+ </svg>
15
+ );
16
+
17
+ export default GoogleIcon;
components/icons/KeyIcon.d.ts ADDED
File without changes
components/icons/KeyIcon.tsx ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ const KeyIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
4
+ <svg
5
+ xmlns="http://www.w3.org/2000/svg"
6
+ width="24"
7
+ height="24"
8
+ viewBox="0 0 24 24"
9
+ fill="none"
10
+ stroke="currentColor"
11
+ strokeWidth="2"
12
+ strokeLinecap="round"
13
+ strokeLinejoin="round"
14
+ {...props}
15
+ >
16
+ <path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4" />
17
+ </svg>
18
+ );
19
+
20
+ export default KeyIcon;
components/icons/OpenAIIcon.tsx ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ const OpenAIIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
4
+ <svg
5
+ xmlns="http://www.w3.org/2000/svg"
6
+ viewBox="0 0 24 24"
7
+ fill="none"
8
+ stroke="currentColor"
9
+ strokeWidth="1.5"
10
+ {...props}
11
+ >
12
+ <path
13
+ d="M21.233 14.153l-3.326 1.92a.5.5 0 01-.715-.434v-3.84a.5.5 0 01.715-.434l3.326 1.92a.5.5 0 010 .868zM2.767 9.847l3.326-1.92a.5.5 0 01.715.434v3.84a.5.5 0 01-.715.434l-3.326-1.92a.5.5 0 010-.868zM8.56 3.017l1.92 3.326a.5.5 0 01-.434.715H6.206a.5.5 0 01-.434-.715L7.692 3.017a.5.5 0 01.868 0zM15.44 20.983l-1.92-3.326a.5.5 0 01.434-.715h3.84a.5.5 0 01.434.715l-1.92 3.326a.5.5 0 01-.868 0zM8.994 20.549l-3.59-6.218a.5.5 0 01.434-.715h7.178a.5.5 0 01.434.715l-3.59 6.218a.5.5 0 01-.868 0zM15.006 3.451l3.59 6.218a.5.5 0 01-.434.715H10.994a.5.5 0 01-.434-.715l3.59-6.218a.5.5 0 01.868 0z"
14
+ ></path>
15
+ </svg>
16
+ );
17
+
18
+ export default OpenAIIcon;
components/icons/PythonIcon.tsx ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ const PythonIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
4
+ <svg viewBox="0 0 24 24" fill="currentColor" {...props}>
5
+ <path d="M12.5 22a.5.5 0 0 1-.5-.5v-4.01a1 1 0 0 0-1-1H7.83a1 1 0 0 1-.7-1.71l4.08-4.08a1 1 0 0 0 0-1.42L7.13 5.21a1 1 0 0 1 .7-1.71H11a1 1 0 0 0 1-1V2.5a.5.5 0 0 1 1 0v1.17a1 1 0 0 1-1.71.7l-4.08 4.08a1 1 0 0 0 0 1.42l4.08 4.08a1 1 0 0 1 1.71.7V21.5a.5.5 0 0 1-.5.5zm-1-10.42L8.91 9.01H7v2h2.5a.5.5 0 0 1 .35.15l2.29 2.29-2.29 2.29A.5.5 0 0 1 12.5 16H9v2h1.91l2.57-2.57zM11.5 2a.5.5 0 0 1 .5.5v4.01a1 1 0 0 0 1 1h3.17a1 1 0 0 1 .7 1.71l-4.08 4.08a1 1 0 0 0 0 1.42l4.08 4.08a1 1 0 0 1-.7 1.71H13a1 1 0 0 0-1 1v4.01a.5.5 0 0 1-1 0v-1.17a1 1 0 0 1 1.71-.7l4.08-4.08a1 1 0 0 0 0-1.42l-4.08-4.08a1 1 0 0 1-1.71-.7V2.5a.5.5 0 0 1 .5-.5zm1 10.42L15.09 15H17v-2h-2.5a.5.5 0 0 1-.35-.15L11.86 10.5l2.29-2.29A.5.5 0 0 1 14.5 8H17V6h-1.91l-2.57 2.57z" />
6
+ </svg>
7
+ );
8
+
9
+ export default PythonIcon;
components/icons/SparklesIcon.tsx ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ const SparklesIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
4
+ <svg
5
+ xmlns="http://www.w3.org/2000/svg"
6
+ width="24"
7
+ height="24"
8
+ viewBox="0 0 24 24"
9
+ fill="none"
10
+ stroke="currentColor"
11
+ strokeWidth="2"
12
+ strokeLinecap="round"
13
+ strokeLinejoin="round"
14
+ {...props}
15
+ >
16
+ <path d="M12 3L9.5 8.5L4 11L9.5 13.5L12 19L14.5 13.5L20 11L14.5 8.5L12 3Z"/>
17
+ <path d="M5 3v4"/>
18
+ <path d="M19 17v4"/>
19
+ <path d="M3 5h4"/>
20
+ <path d="M17 19h4"/>
21
+ </svg>
22
+ );
23
+
24
+ export default SparklesIcon;
components/icons/UploadIcon.tsx ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ const UploadIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
4
+ <svg
5
+ xmlns="http://www.w3.org/2000/svg"
6
+ width="24"
7
+ height="24"
8
+ viewBox="0 0 24 24"
9
+ fill="none"
10
+ stroke="currentColor"
11
+ strokeWidth="2"
12
+ strokeLinecap="round"
13
+ strokeLinejoin="round"
14
+ {...props}
15
+ >
16
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
17
+ <polyline points="17 8 12 3 7 8"></polyline>
18
+ <line x1="12" x2="12" y1="3" y2="15"></line>
19
+ </svg>
20
+ );
21
+
22
+ export default UploadIcon;
index.html ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>PyQt UI Generator from Image</title>
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <style>
10
+ body {
11
+ font-family: 'Inter', sans-serif;
12
+ }
13
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&family=Roboto+Mono&display=swap');
14
+ .font-code {
15
+ font-family: 'Roboto Mono', monospace;
16
+ }
17
+ </style>
18
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
19
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
20
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/python.min.js"></script>
21
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/cpp.min.js"></script>
22
+ <script type="importmap">
23
+ {
24
+ "imports": {
25
+ "react": "https://esm.sh/react@^19.1.0",
26
+ "react/": "https://esm.sh/react@^19.1.0/",
27
+ "react-dom/": "https://esm.sh/react-dom@^19.1.0/",
28
+ "@google/genai": "https://esm.sh/@google/genai@^1.7.0",
29
+ "jszip": "https://esm.sh/jszip@3.10.1"
30
+ }
31
+ }
32
+ </script>
33
+ <link rel="stylesheet" href="/index.css">
34
+ </head>
35
+ <body class="bg-gray-900">
36
+ <div id="root"></div>
37
+ <script type="module" src="/index.tsx"></script>
38
+ </body>
39
+ </html>
index.tsx ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import App from './App';
4
+
5
+ const rootElement = document.getElementById('root');
6
+ if (!rootElement) {
7
+ throw new Error("Could not find root element to mount to");
8
+ }
9
+
10
+ const root = ReactDOM.createRoot(rootElement);
11
+ root.render(
12
+ <React.StrictMode>
13
+ <App />
14
+ </React.StrictMode>
15
+ );
metadata.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "name": "PyQt UI Generator from Image",
3
+ "description": "An AI-powered tool that analyzes an image of a user interface and generates the corresponding Python code using the PyQt6 library.",
4
+ "requestFramePermissions": [],
5
+ "prompt": ""
6
+ }
package-lock.json ADDED
@@ -0,0 +1,1417 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "pyqt-ui-generator-from-image",
3
+ "version": "0.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "pyqt-ui-generator-from-image",
9
+ "version": "0.0.0",
10
+ "dependencies": {
11
+ "@google/genai": "^1.7.0",
12
+ "jszip": "3.10.1",
13
+ "react": "^19.1.0",
14
+ "react-dom": "^19.1.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/node": "^22.14.0",
18
+ "typescript": "~5.7.2",
19
+ "vite": "^6.2.0"
20
+ }
21
+ },
22
+ "node_modules/@esbuild/aix-ppc64": {
23
+ "version": "0.25.5",
24
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
25
+ "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
26
+ "cpu": [
27
+ "ppc64"
28
+ ],
29
+ "dev": true,
30
+ "optional": true,
31
+ "os": [
32
+ "aix"
33
+ ],
34
+ "engines": {
35
+ "node": ">=18"
36
+ }
37
+ },
38
+ "node_modules/@esbuild/android-arm": {
39
+ "version": "0.25.5",
40
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
41
+ "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
42
+ "cpu": [
43
+ "arm"
44
+ ],
45
+ "dev": true,
46
+ "optional": true,
47
+ "os": [
48
+ "android"
49
+ ],
50
+ "engines": {
51
+ "node": ">=18"
52
+ }
53
+ },
54
+ "node_modules/@esbuild/android-arm64": {
55
+ "version": "0.25.5",
56
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
57
+ "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
58
+ "cpu": [
59
+ "arm64"
60
+ ],
61
+ "dev": true,
62
+ "optional": true,
63
+ "os": [
64
+ "android"
65
+ ],
66
+ "engines": {
67
+ "node": ">=18"
68
+ }
69
+ },
70
+ "node_modules/@esbuild/android-x64": {
71
+ "version": "0.25.5",
72
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
73
+ "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
74
+ "cpu": [
75
+ "x64"
76
+ ],
77
+ "dev": true,
78
+ "optional": true,
79
+ "os": [
80
+ "android"
81
+ ],
82
+ "engines": {
83
+ "node": ">=18"
84
+ }
85
+ },
86
+ "node_modules/@esbuild/darwin-arm64": {
87
+ "version": "0.25.5",
88
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
89
+ "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
90
+ "cpu": [
91
+ "arm64"
92
+ ],
93
+ "dev": true,
94
+ "optional": true,
95
+ "os": [
96
+ "darwin"
97
+ ],
98
+ "engines": {
99
+ "node": ">=18"
100
+ }
101
+ },
102
+ "node_modules/@esbuild/darwin-x64": {
103
+ "version": "0.25.5",
104
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
105
+ "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
106
+ "cpu": [
107
+ "x64"
108
+ ],
109
+ "dev": true,
110
+ "optional": true,
111
+ "os": [
112
+ "darwin"
113
+ ],
114
+ "engines": {
115
+ "node": ">=18"
116
+ }
117
+ },
118
+ "node_modules/@esbuild/freebsd-arm64": {
119
+ "version": "0.25.5",
120
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
121
+ "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
122
+ "cpu": [
123
+ "arm64"
124
+ ],
125
+ "dev": true,
126
+ "optional": true,
127
+ "os": [
128
+ "freebsd"
129
+ ],
130
+ "engines": {
131
+ "node": ">=18"
132
+ }
133
+ },
134
+ "node_modules/@esbuild/freebsd-x64": {
135
+ "version": "0.25.5",
136
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
137
+ "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
138
+ "cpu": [
139
+ "x64"
140
+ ],
141
+ "dev": true,
142
+ "optional": true,
143
+ "os": [
144
+ "freebsd"
145
+ ],
146
+ "engines": {
147
+ "node": ">=18"
148
+ }
149
+ },
150
+ "node_modules/@esbuild/linux-arm": {
151
+ "version": "0.25.5",
152
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
153
+ "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
154
+ "cpu": [
155
+ "arm"
156
+ ],
157
+ "dev": true,
158
+ "optional": true,
159
+ "os": [
160
+ "linux"
161
+ ],
162
+ "engines": {
163
+ "node": ">=18"
164
+ }
165
+ },
166
+ "node_modules/@esbuild/linux-arm64": {
167
+ "version": "0.25.5",
168
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
169
+ "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
170
+ "cpu": [
171
+ "arm64"
172
+ ],
173
+ "dev": true,
174
+ "optional": true,
175
+ "os": [
176
+ "linux"
177
+ ],
178
+ "engines": {
179
+ "node": ">=18"
180
+ }
181
+ },
182
+ "node_modules/@esbuild/linux-ia32": {
183
+ "version": "0.25.5",
184
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
185
+ "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
186
+ "cpu": [
187
+ "ia32"
188
+ ],
189
+ "dev": true,
190
+ "optional": true,
191
+ "os": [
192
+ "linux"
193
+ ],
194
+ "engines": {
195
+ "node": ">=18"
196
+ }
197
+ },
198
+ "node_modules/@esbuild/linux-loong64": {
199
+ "version": "0.25.5",
200
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
201
+ "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
202
+ "cpu": [
203
+ "loong64"
204
+ ],
205
+ "dev": true,
206
+ "optional": true,
207
+ "os": [
208
+ "linux"
209
+ ],
210
+ "engines": {
211
+ "node": ">=18"
212
+ }
213
+ },
214
+ "node_modules/@esbuild/linux-mips64el": {
215
+ "version": "0.25.5",
216
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
217
+ "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
218
+ "cpu": [
219
+ "mips64el"
220
+ ],
221
+ "dev": true,
222
+ "optional": true,
223
+ "os": [
224
+ "linux"
225
+ ],
226
+ "engines": {
227
+ "node": ">=18"
228
+ }
229
+ },
230
+ "node_modules/@esbuild/linux-ppc64": {
231
+ "version": "0.25.5",
232
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
233
+ "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
234
+ "cpu": [
235
+ "ppc64"
236
+ ],
237
+ "dev": true,
238
+ "optional": true,
239
+ "os": [
240
+ "linux"
241
+ ],
242
+ "engines": {
243
+ "node": ">=18"
244
+ }
245
+ },
246
+ "node_modules/@esbuild/linux-riscv64": {
247
+ "version": "0.25.5",
248
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
249
+ "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
250
+ "cpu": [
251
+ "riscv64"
252
+ ],
253
+ "dev": true,
254
+ "optional": true,
255
+ "os": [
256
+ "linux"
257
+ ],
258
+ "engines": {
259
+ "node": ">=18"
260
+ }
261
+ },
262
+ "node_modules/@esbuild/linux-s390x": {
263
+ "version": "0.25.5",
264
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
265
+ "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
266
+ "cpu": [
267
+ "s390x"
268
+ ],
269
+ "dev": true,
270
+ "optional": true,
271
+ "os": [
272
+ "linux"
273
+ ],
274
+ "engines": {
275
+ "node": ">=18"
276
+ }
277
+ },
278
+ "node_modules/@esbuild/linux-x64": {
279
+ "version": "0.25.5",
280
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
281
+ "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==",
282
+ "cpu": [
283
+ "x64"
284
+ ],
285
+ "dev": true,
286
+ "optional": true,
287
+ "os": [
288
+ "linux"
289
+ ],
290
+ "engines": {
291
+ "node": ">=18"
292
+ }
293
+ },
294
+ "node_modules/@esbuild/netbsd-arm64": {
295
+ "version": "0.25.5",
296
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz",
297
+ "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
298
+ "cpu": [
299
+ "arm64"
300
+ ],
301
+ "dev": true,
302
+ "optional": true,
303
+ "os": [
304
+ "netbsd"
305
+ ],
306
+ "engines": {
307
+ "node": ">=18"
308
+ }
309
+ },
310
+ "node_modules/@esbuild/netbsd-x64": {
311
+ "version": "0.25.5",
312
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
313
+ "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
314
+ "cpu": [
315
+ "x64"
316
+ ],
317
+ "dev": true,
318
+ "optional": true,
319
+ "os": [
320
+ "netbsd"
321
+ ],
322
+ "engines": {
323
+ "node": ">=18"
324
+ }
325
+ },
326
+ "node_modules/@esbuild/openbsd-arm64": {
327
+ "version": "0.25.5",
328
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
329
+ "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
330
+ "cpu": [
331
+ "arm64"
332
+ ],
333
+ "dev": true,
334
+ "optional": true,
335
+ "os": [
336
+ "openbsd"
337
+ ],
338
+ "engines": {
339
+ "node": ">=18"
340
+ }
341
+ },
342
+ "node_modules/@esbuild/openbsd-x64": {
343
+ "version": "0.25.5",
344
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
345
+ "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
346
+ "cpu": [
347
+ "x64"
348
+ ],
349
+ "dev": true,
350
+ "optional": true,
351
+ "os": [
352
+ "openbsd"
353
+ ],
354
+ "engines": {
355
+ "node": ">=18"
356
+ }
357
+ },
358
+ "node_modules/@esbuild/sunos-x64": {
359
+ "version": "0.25.5",
360
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
361
+ "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
362
+ "cpu": [
363
+ "x64"
364
+ ],
365
+ "dev": true,
366
+ "optional": true,
367
+ "os": [
368
+ "sunos"
369
+ ],
370
+ "engines": {
371
+ "node": ">=18"
372
+ }
373
+ },
374
+ "node_modules/@esbuild/win32-arm64": {
375
+ "version": "0.25.5",
376
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
377
+ "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
378
+ "cpu": [
379
+ "arm64"
380
+ ],
381
+ "dev": true,
382
+ "optional": true,
383
+ "os": [
384
+ "win32"
385
+ ],
386
+ "engines": {
387
+ "node": ">=18"
388
+ }
389
+ },
390
+ "node_modules/@esbuild/win32-ia32": {
391
+ "version": "0.25.5",
392
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
393
+ "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
394
+ "cpu": [
395
+ "ia32"
396
+ ],
397
+ "dev": true,
398
+ "optional": true,
399
+ "os": [
400
+ "win32"
401
+ ],
402
+ "engines": {
403
+ "node": ">=18"
404
+ }
405
+ },
406
+ "node_modules/@esbuild/win32-x64": {
407
+ "version": "0.25.5",
408
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
409
+ "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
410
+ "cpu": [
411
+ "x64"
412
+ ],
413
+ "dev": true,
414
+ "optional": true,
415
+ "os": [
416
+ "win32"
417
+ ],
418
+ "engines": {
419
+ "node": ">=18"
420
+ }
421
+ },
422
+ "node_modules/@google/genai": {
423
+ "version": "1.7.0",
424
+ "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.7.0.tgz",
425
+ "integrity": "sha512-s/OZLkrIfBwc+SFFaZoKdEogkw4in0YRTGc4Q483jnfchNBWzrNe560eZEfGJHQRPn6YfzJgECCx0sqEOMWvYw==",
426
+ "dependencies": {
427
+ "google-auth-library": "^9.14.2",
428
+ "ws": "^8.18.0",
429
+ "zod": "^3.22.4",
430
+ "zod-to-json-schema": "^3.22.4"
431
+ },
432
+ "engines": {
433
+ "node": ">=20.0.0"
434
+ },
435
+ "peerDependencies": {
436
+ "@modelcontextprotocol/sdk": "^1.11.0"
437
+ },
438
+ "peerDependenciesMeta": {
439
+ "@modelcontextprotocol/sdk": {
440
+ "optional": true
441
+ }
442
+ }
443
+ },
444
+ "node_modules/@rollup/rollup-android-arm-eabi": {
445
+ "version": "4.44.1",
446
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz",
447
+ "integrity": "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==",
448
+ "cpu": [
449
+ "arm"
450
+ ],
451
+ "dev": true,
452
+ "optional": true,
453
+ "os": [
454
+ "android"
455
+ ]
456
+ },
457
+ "node_modules/@rollup/rollup-android-arm64": {
458
+ "version": "4.44.1",
459
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz",
460
+ "integrity": "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==",
461
+ "cpu": [
462
+ "arm64"
463
+ ],
464
+ "dev": true,
465
+ "optional": true,
466
+ "os": [
467
+ "android"
468
+ ]
469
+ },
470
+ "node_modules/@rollup/rollup-darwin-arm64": {
471
+ "version": "4.44.1",
472
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz",
473
+ "integrity": "sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==",
474
+ "cpu": [
475
+ "arm64"
476
+ ],
477
+ "dev": true,
478
+ "optional": true,
479
+ "os": [
480
+ "darwin"
481
+ ]
482
+ },
483
+ "node_modules/@rollup/rollup-darwin-x64": {
484
+ "version": "4.44.1",
485
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz",
486
+ "integrity": "sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==",
487
+ "cpu": [
488
+ "x64"
489
+ ],
490
+ "dev": true,
491
+ "optional": true,
492
+ "os": [
493
+ "darwin"
494
+ ]
495
+ },
496
+ "node_modules/@rollup/rollup-freebsd-arm64": {
497
+ "version": "4.44.1",
498
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz",
499
+ "integrity": "sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==",
500
+ "cpu": [
501
+ "arm64"
502
+ ],
503
+ "dev": true,
504
+ "optional": true,
505
+ "os": [
506
+ "freebsd"
507
+ ]
508
+ },
509
+ "node_modules/@rollup/rollup-freebsd-x64": {
510
+ "version": "4.44.1",
511
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz",
512
+ "integrity": "sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==",
513
+ "cpu": [
514
+ "x64"
515
+ ],
516
+ "dev": true,
517
+ "optional": true,
518
+ "os": [
519
+ "freebsd"
520
+ ]
521
+ },
522
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
523
+ "version": "4.44.1",
524
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz",
525
+ "integrity": "sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==",
526
+ "cpu": [
527
+ "arm"
528
+ ],
529
+ "dev": true,
530
+ "optional": true,
531
+ "os": [
532
+ "linux"
533
+ ]
534
+ },
535
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
536
+ "version": "4.44.1",
537
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz",
538
+ "integrity": "sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==",
539
+ "cpu": [
540
+ "arm"
541
+ ],
542
+ "dev": true,
543
+ "optional": true,
544
+ "os": [
545
+ "linux"
546
+ ]
547
+ },
548
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
549
+ "version": "4.44.1",
550
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz",
551
+ "integrity": "sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==",
552
+ "cpu": [
553
+ "arm64"
554
+ ],
555
+ "dev": true,
556
+ "optional": true,
557
+ "os": [
558
+ "linux"
559
+ ]
560
+ },
561
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
562
+ "version": "4.44.1",
563
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.1.tgz",
564
+ "integrity": "sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==",
565
+ "cpu": [
566
+ "arm64"
567
+ ],
568
+ "dev": true,
569
+ "optional": true,
570
+ "os": [
571
+ "linux"
572
+ ]
573
+ },
574
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
575
+ "version": "4.44.1",
576
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz",
577
+ "integrity": "sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==",
578
+ "cpu": [
579
+ "loong64"
580
+ ],
581
+ "dev": true,
582
+ "optional": true,
583
+ "os": [
584
+ "linux"
585
+ ]
586
+ },
587
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
588
+ "version": "4.44.1",
589
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz",
590
+ "integrity": "sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==",
591
+ "cpu": [
592
+ "ppc64"
593
+ ],
594
+ "dev": true,
595
+ "optional": true,
596
+ "os": [
597
+ "linux"
598
+ ]
599
+ },
600
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
601
+ "version": "4.44.1",
602
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz",
603
+ "integrity": "sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==",
604
+ "cpu": [
605
+ "riscv64"
606
+ ],
607
+ "dev": true,
608
+ "optional": true,
609
+ "os": [
610
+ "linux"
611
+ ]
612
+ },
613
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
614
+ "version": "4.44.1",
615
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz",
616
+ "integrity": "sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==",
617
+ "cpu": [
618
+ "riscv64"
619
+ ],
620
+ "dev": true,
621
+ "optional": true,
622
+ "os": [
623
+ "linux"
624
+ ]
625
+ },
626
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
627
+ "version": "4.44.1",
628
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz",
629
+ "integrity": "sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==",
630
+ "cpu": [
631
+ "s390x"
632
+ ],
633
+ "dev": true,
634
+ "optional": true,
635
+ "os": [
636
+ "linux"
637
+ ]
638
+ },
639
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
640
+ "version": "4.44.1",
641
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz",
642
+ "integrity": "sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==",
643
+ "cpu": [
644
+ "x64"
645
+ ],
646
+ "dev": true,
647
+ "optional": true,
648
+ "os": [
649
+ "linux"
650
+ ]
651
+ },
652
+ "node_modules/@rollup/rollup-linux-x64-musl": {
653
+ "version": "4.44.1",
654
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.1.tgz",
655
+ "integrity": "sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==",
656
+ "cpu": [
657
+ "x64"
658
+ ],
659
+ "dev": true,
660
+ "optional": true,
661
+ "os": [
662
+ "linux"
663
+ ]
664
+ },
665
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
666
+ "version": "4.44.1",
667
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz",
668
+ "integrity": "sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==",
669
+ "cpu": [
670
+ "arm64"
671
+ ],
672
+ "dev": true,
673
+ "optional": true,
674
+ "os": [
675
+ "win32"
676
+ ]
677
+ },
678
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
679
+ "version": "4.44.1",
680
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz",
681
+ "integrity": "sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==",
682
+ "cpu": [
683
+ "ia32"
684
+ ],
685
+ "dev": true,
686
+ "optional": true,
687
+ "os": [
688
+ "win32"
689
+ ]
690
+ },
691
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
692
+ "version": "4.44.1",
693
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz",
694
+ "integrity": "sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==",
695
+ "cpu": [
696
+ "x64"
697
+ ],
698
+ "dev": true,
699
+ "optional": true,
700
+ "os": [
701
+ "win32"
702
+ ]
703
+ },
704
+ "node_modules/@types/estree": {
705
+ "version": "1.0.8",
706
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
707
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
708
+ "dev": true
709
+ },
710
+ "node_modules/@types/node": {
711
+ "version": "22.15.34",
712
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.34.tgz",
713
+ "integrity": "sha512-8Y6E5WUupYy1Dd0II32BsWAx5MWdcnRd8L84Oys3veg1YrYtNtzgO4CFhiBg6MDSjk7Ay36HYOnU7/tuOzIzcw==",
714
+ "dev": true,
715
+ "dependencies": {
716
+ "undici-types": "~6.21.0"
717
+ }
718
+ },
719
+ "node_modules/agent-base": {
720
+ "version": "7.1.3",
721
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
722
+ "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
723
+ "engines": {
724
+ "node": ">= 14"
725
+ }
726
+ },
727
+ "node_modules/base64-js": {
728
+ "version": "1.5.1",
729
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
730
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
731
+ "funding": [
732
+ {
733
+ "type": "github",
734
+ "url": "https://github.com/sponsors/feross"
735
+ },
736
+ {
737
+ "type": "patreon",
738
+ "url": "https://www.patreon.com/feross"
739
+ },
740
+ {
741
+ "type": "consulting",
742
+ "url": "https://feross.org/support"
743
+ }
744
+ ]
745
+ },
746
+ "node_modules/bignumber.js": {
747
+ "version": "9.3.0",
748
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.0.tgz",
749
+ "integrity": "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==",
750
+ "engines": {
751
+ "node": "*"
752
+ }
753
+ },
754
+ "node_modules/buffer-equal-constant-time": {
755
+ "version": "1.0.1",
756
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
757
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
758
+ },
759
+ "node_modules/core-util-is": {
760
+ "version": "1.0.3",
761
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
762
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
763
+ },
764
+ "node_modules/debug": {
765
+ "version": "4.4.1",
766
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
767
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
768
+ "dependencies": {
769
+ "ms": "^2.1.3"
770
+ },
771
+ "engines": {
772
+ "node": ">=6.0"
773
+ },
774
+ "peerDependenciesMeta": {
775
+ "supports-color": {
776
+ "optional": true
777
+ }
778
+ }
779
+ },
780
+ "node_modules/ecdsa-sig-formatter": {
781
+ "version": "1.0.11",
782
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
783
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
784
+ "dependencies": {
785
+ "safe-buffer": "^5.0.1"
786
+ }
787
+ },
788
+ "node_modules/esbuild": {
789
+ "version": "0.25.5",
790
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
791
+ "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
792
+ "dev": true,
793
+ "hasInstallScript": true,
794
+ "bin": {
795
+ "esbuild": "bin/esbuild"
796
+ },
797
+ "engines": {
798
+ "node": ">=18"
799
+ },
800
+ "optionalDependencies": {
801
+ "@esbuild/aix-ppc64": "0.25.5",
802
+ "@esbuild/android-arm": "0.25.5",
803
+ "@esbuild/android-arm64": "0.25.5",
804
+ "@esbuild/android-x64": "0.25.5",
805
+ "@esbuild/darwin-arm64": "0.25.5",
806
+ "@esbuild/darwin-x64": "0.25.5",
807
+ "@esbuild/freebsd-arm64": "0.25.5",
808
+ "@esbuild/freebsd-x64": "0.25.5",
809
+ "@esbuild/linux-arm": "0.25.5",
810
+ "@esbuild/linux-arm64": "0.25.5",
811
+ "@esbuild/linux-ia32": "0.25.5",
812
+ "@esbuild/linux-loong64": "0.25.5",
813
+ "@esbuild/linux-mips64el": "0.25.5",
814
+ "@esbuild/linux-ppc64": "0.25.5",
815
+ "@esbuild/linux-riscv64": "0.25.5",
816
+ "@esbuild/linux-s390x": "0.25.5",
817
+ "@esbuild/linux-x64": "0.25.5",
818
+ "@esbuild/netbsd-arm64": "0.25.5",
819
+ "@esbuild/netbsd-x64": "0.25.5",
820
+ "@esbuild/openbsd-arm64": "0.25.5",
821
+ "@esbuild/openbsd-x64": "0.25.5",
822
+ "@esbuild/sunos-x64": "0.25.5",
823
+ "@esbuild/win32-arm64": "0.25.5",
824
+ "@esbuild/win32-ia32": "0.25.5",
825
+ "@esbuild/win32-x64": "0.25.5"
826
+ }
827
+ },
828
+ "node_modules/extend": {
829
+ "version": "3.0.2",
830
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
831
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
832
+ },
833
+ "node_modules/fdir": {
834
+ "version": "6.4.6",
835
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
836
+ "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
837
+ "dev": true,
838
+ "peerDependencies": {
839
+ "picomatch": "^3 || ^4"
840
+ },
841
+ "peerDependenciesMeta": {
842
+ "picomatch": {
843
+ "optional": true
844
+ }
845
+ }
846
+ },
847
+ "node_modules/fsevents": {
848
+ "version": "2.3.3",
849
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
850
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
851
+ "dev": true,
852
+ "hasInstallScript": true,
853
+ "optional": true,
854
+ "os": [
855
+ "darwin"
856
+ ],
857
+ "engines": {
858
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
859
+ }
860
+ },
861
+ "node_modules/gaxios": {
862
+ "version": "6.7.1",
863
+ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz",
864
+ "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==",
865
+ "dependencies": {
866
+ "extend": "^3.0.2",
867
+ "https-proxy-agent": "^7.0.1",
868
+ "is-stream": "^2.0.0",
869
+ "node-fetch": "^2.6.9",
870
+ "uuid": "^9.0.1"
871
+ },
872
+ "engines": {
873
+ "node": ">=14"
874
+ }
875
+ },
876
+ "node_modules/gcp-metadata": {
877
+ "version": "6.1.1",
878
+ "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz",
879
+ "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==",
880
+ "dependencies": {
881
+ "gaxios": "^6.1.1",
882
+ "google-logging-utils": "^0.0.2",
883
+ "json-bigint": "^1.0.0"
884
+ },
885
+ "engines": {
886
+ "node": ">=14"
887
+ }
888
+ },
889
+ "node_modules/google-auth-library": {
890
+ "version": "9.15.1",
891
+ "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz",
892
+ "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==",
893
+ "dependencies": {
894
+ "base64-js": "^1.3.0",
895
+ "ecdsa-sig-formatter": "^1.0.11",
896
+ "gaxios": "^6.1.1",
897
+ "gcp-metadata": "^6.1.0",
898
+ "gtoken": "^7.0.0",
899
+ "jws": "^4.0.0"
900
+ },
901
+ "engines": {
902
+ "node": ">=14"
903
+ }
904
+ },
905
+ "node_modules/google-logging-utils": {
906
+ "version": "0.0.2",
907
+ "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz",
908
+ "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==",
909
+ "engines": {
910
+ "node": ">=14"
911
+ }
912
+ },
913
+ "node_modules/gtoken": {
914
+ "version": "7.1.0",
915
+ "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
916
+ "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==",
917
+ "dependencies": {
918
+ "gaxios": "^6.0.0",
919
+ "jws": "^4.0.0"
920
+ },
921
+ "engines": {
922
+ "node": ">=14.0.0"
923
+ }
924
+ },
925
+ "node_modules/https-proxy-agent": {
926
+ "version": "7.0.6",
927
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
928
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
929
+ "dependencies": {
930
+ "agent-base": "^7.1.2",
931
+ "debug": "4"
932
+ },
933
+ "engines": {
934
+ "node": ">= 14"
935
+ }
936
+ },
937
+ "node_modules/immediate": {
938
+ "version": "3.0.6",
939
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
940
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
941
+ },
942
+ "node_modules/inherits": {
943
+ "version": "2.0.4",
944
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
945
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
946
+ },
947
+ "node_modules/is-stream": {
948
+ "version": "2.0.1",
949
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
950
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
951
+ "engines": {
952
+ "node": ">=8"
953
+ },
954
+ "funding": {
955
+ "url": "https://github.com/sponsors/sindresorhus"
956
+ }
957
+ },
958
+ "node_modules/isarray": {
959
+ "version": "1.0.0",
960
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
961
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
962
+ },
963
+ "node_modules/json-bigint": {
964
+ "version": "1.0.0",
965
+ "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
966
+ "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
967
+ "dependencies": {
968
+ "bignumber.js": "^9.0.0"
969
+ }
970
+ },
971
+ "node_modules/jszip": {
972
+ "version": "3.10.1",
973
+ "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
974
+ "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
975
+ "dependencies": {
976
+ "lie": "~3.3.0",
977
+ "pako": "~1.0.2",
978
+ "readable-stream": "~2.3.6",
979
+ "setimmediate": "^1.0.5"
980
+ }
981
+ },
982
+ "node_modules/jwa": {
983
+ "version": "2.0.1",
984
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
985
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
986
+ "dependencies": {
987
+ "buffer-equal-constant-time": "^1.0.1",
988
+ "ecdsa-sig-formatter": "1.0.11",
989
+ "safe-buffer": "^5.0.1"
990
+ }
991
+ },
992
+ "node_modules/jws": {
993
+ "version": "4.0.0",
994
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
995
+ "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
996
+ "dependencies": {
997
+ "jwa": "^2.0.0",
998
+ "safe-buffer": "^5.0.1"
999
+ }
1000
+ },
1001
+ "node_modules/lie": {
1002
+ "version": "3.3.0",
1003
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
1004
+ "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
1005
+ "dependencies": {
1006
+ "immediate": "~3.0.5"
1007
+ }
1008
+ },
1009
+ "node_modules/ms": {
1010
+ "version": "2.1.3",
1011
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1012
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
1013
+ },
1014
+ "node_modules/nanoid": {
1015
+ "version": "3.3.11",
1016
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
1017
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
1018
+ "dev": true,
1019
+ "funding": [
1020
+ {
1021
+ "type": "github",
1022
+ "url": "https://github.com/sponsors/ai"
1023
+ }
1024
+ ],
1025
+ "bin": {
1026
+ "nanoid": "bin/nanoid.cjs"
1027
+ },
1028
+ "engines": {
1029
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1030
+ }
1031
+ },
1032
+ "node_modules/node-fetch": {
1033
+ "version": "2.7.0",
1034
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
1035
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
1036
+ "dependencies": {
1037
+ "whatwg-url": "^5.0.0"
1038
+ },
1039
+ "engines": {
1040
+ "node": "4.x || >=6.0.0"
1041
+ },
1042
+ "peerDependencies": {
1043
+ "encoding": "^0.1.0"
1044
+ },
1045
+ "peerDependenciesMeta": {
1046
+ "encoding": {
1047
+ "optional": true
1048
+ }
1049
+ }
1050
+ },
1051
+ "node_modules/pako": {
1052
+ "version": "1.0.11",
1053
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
1054
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
1055
+ },
1056
+ "node_modules/picocolors": {
1057
+ "version": "1.1.1",
1058
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1059
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
1060
+ "dev": true
1061
+ },
1062
+ "node_modules/picomatch": {
1063
+ "version": "4.0.2",
1064
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
1065
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
1066
+ "dev": true,
1067
+ "engines": {
1068
+ "node": ">=12"
1069
+ },
1070
+ "funding": {
1071
+ "url": "https://github.com/sponsors/jonschlinkert"
1072
+ }
1073
+ },
1074
+ "node_modules/postcss": {
1075
+ "version": "8.5.6",
1076
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
1077
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
1078
+ "dev": true,
1079
+ "funding": [
1080
+ {
1081
+ "type": "opencollective",
1082
+ "url": "https://opencollective.com/postcss/"
1083
+ },
1084
+ {
1085
+ "type": "tidelift",
1086
+ "url": "https://tidelift.com/funding/github/npm/postcss"
1087
+ },
1088
+ {
1089
+ "type": "github",
1090
+ "url": "https://github.com/sponsors/ai"
1091
+ }
1092
+ ],
1093
+ "dependencies": {
1094
+ "nanoid": "^3.3.11",
1095
+ "picocolors": "^1.1.1",
1096
+ "source-map-js": "^1.2.1"
1097
+ },
1098
+ "engines": {
1099
+ "node": "^10 || ^12 || >=14"
1100
+ }
1101
+ },
1102
+ "node_modules/process-nextick-args": {
1103
+ "version": "2.0.1",
1104
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
1105
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
1106
+ },
1107
+ "node_modules/react": {
1108
+ "version": "19.1.0",
1109
+ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
1110
+ "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
1111
+ "engines": {
1112
+ "node": ">=0.10.0"
1113
+ }
1114
+ },
1115
+ "node_modules/react-dom": {
1116
+ "version": "19.1.0",
1117
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
1118
+ "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
1119
+ "dependencies": {
1120
+ "scheduler": "^0.26.0"
1121
+ },
1122
+ "peerDependencies": {
1123
+ "react": "^19.1.0"
1124
+ }
1125
+ },
1126
+ "node_modules/readable-stream": {
1127
+ "version": "2.3.8",
1128
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
1129
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
1130
+ "dependencies": {
1131
+ "core-util-is": "~1.0.0",
1132
+ "inherits": "~2.0.3",
1133
+ "isarray": "~1.0.0",
1134
+ "process-nextick-args": "~2.0.0",
1135
+ "safe-buffer": "~5.1.1",
1136
+ "string_decoder": "~1.1.1",
1137
+ "util-deprecate": "~1.0.1"
1138
+ }
1139
+ },
1140
+ "node_modules/readable-stream/node_modules/safe-buffer": {
1141
+ "version": "5.1.2",
1142
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
1143
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
1144
+ },
1145
+ "node_modules/rollup": {
1146
+ "version": "4.44.1",
1147
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz",
1148
+ "integrity": "sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==",
1149
+ "dev": true,
1150
+ "dependencies": {
1151
+ "@types/estree": "1.0.8"
1152
+ },
1153
+ "bin": {
1154
+ "rollup": "dist/bin/rollup"
1155
+ },
1156
+ "engines": {
1157
+ "node": ">=18.0.0",
1158
+ "npm": ">=8.0.0"
1159
+ },
1160
+ "optionalDependencies": {
1161
+ "@rollup/rollup-android-arm-eabi": "4.44.1",
1162
+ "@rollup/rollup-android-arm64": "4.44.1",
1163
+ "@rollup/rollup-darwin-arm64": "4.44.1",
1164
+ "@rollup/rollup-darwin-x64": "4.44.1",
1165
+ "@rollup/rollup-freebsd-arm64": "4.44.1",
1166
+ "@rollup/rollup-freebsd-x64": "4.44.1",
1167
+ "@rollup/rollup-linux-arm-gnueabihf": "4.44.1",
1168
+ "@rollup/rollup-linux-arm-musleabihf": "4.44.1",
1169
+ "@rollup/rollup-linux-arm64-gnu": "4.44.1",
1170
+ "@rollup/rollup-linux-arm64-musl": "4.44.1",
1171
+ "@rollup/rollup-linux-loongarch64-gnu": "4.44.1",
1172
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.44.1",
1173
+ "@rollup/rollup-linux-riscv64-gnu": "4.44.1",
1174
+ "@rollup/rollup-linux-riscv64-musl": "4.44.1",
1175
+ "@rollup/rollup-linux-s390x-gnu": "4.44.1",
1176
+ "@rollup/rollup-linux-x64-gnu": "4.44.1",
1177
+ "@rollup/rollup-linux-x64-musl": "4.44.1",
1178
+ "@rollup/rollup-win32-arm64-msvc": "4.44.1",
1179
+ "@rollup/rollup-win32-ia32-msvc": "4.44.1",
1180
+ "@rollup/rollup-win32-x64-msvc": "4.44.1",
1181
+ "fsevents": "~2.3.2"
1182
+ }
1183
+ },
1184
+ "node_modules/safe-buffer": {
1185
+ "version": "5.2.1",
1186
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1187
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1188
+ "funding": [
1189
+ {
1190
+ "type": "github",
1191
+ "url": "https://github.com/sponsors/feross"
1192
+ },
1193
+ {
1194
+ "type": "patreon",
1195
+ "url": "https://www.patreon.com/feross"
1196
+ },
1197
+ {
1198
+ "type": "consulting",
1199
+ "url": "https://feross.org/support"
1200
+ }
1201
+ ]
1202
+ },
1203
+ "node_modules/scheduler": {
1204
+ "version": "0.26.0",
1205
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
1206
+ "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="
1207
+ },
1208
+ "node_modules/setimmediate": {
1209
+ "version": "1.0.5",
1210
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
1211
+ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
1212
+ },
1213
+ "node_modules/source-map-js": {
1214
+ "version": "1.2.1",
1215
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
1216
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
1217
+ "dev": true,
1218
+ "engines": {
1219
+ "node": ">=0.10.0"
1220
+ }
1221
+ },
1222
+ "node_modules/string_decoder": {
1223
+ "version": "1.1.1",
1224
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
1225
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
1226
+ "dependencies": {
1227
+ "safe-buffer": "~5.1.0"
1228
+ }
1229
+ },
1230
+ "node_modules/string_decoder/node_modules/safe-buffer": {
1231
+ "version": "5.1.2",
1232
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
1233
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
1234
+ },
1235
+ "node_modules/tinyglobby": {
1236
+ "version": "0.2.14",
1237
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
1238
+ "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
1239
+ "dev": true,
1240
+ "dependencies": {
1241
+ "fdir": "^6.4.4",
1242
+ "picomatch": "^4.0.2"
1243
+ },
1244
+ "engines": {
1245
+ "node": ">=12.0.0"
1246
+ },
1247
+ "funding": {
1248
+ "url": "https://github.com/sponsors/SuperchupuDev"
1249
+ }
1250
+ },
1251
+ "node_modules/tr46": {
1252
+ "version": "0.0.3",
1253
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
1254
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
1255
+ },
1256
+ "node_modules/typescript": {
1257
+ "version": "5.7.3",
1258
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
1259
+ "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
1260
+ "dev": true,
1261
+ "bin": {
1262
+ "tsc": "bin/tsc",
1263
+ "tsserver": "bin/tsserver"
1264
+ },
1265
+ "engines": {
1266
+ "node": ">=14.17"
1267
+ }
1268
+ },
1269
+ "node_modules/undici-types": {
1270
+ "version": "6.21.0",
1271
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
1272
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
1273
+ "dev": true
1274
+ },
1275
+ "node_modules/util-deprecate": {
1276
+ "version": "1.0.2",
1277
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
1278
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
1279
+ },
1280
+ "node_modules/uuid": {
1281
+ "version": "9.0.1",
1282
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
1283
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
1284
+ "funding": [
1285
+ "https://github.com/sponsors/broofa",
1286
+ "https://github.com/sponsors/ctavan"
1287
+ ],
1288
+ "bin": {
1289
+ "uuid": "dist/bin/uuid"
1290
+ }
1291
+ },
1292
+ "node_modules/vite": {
1293
+ "version": "6.3.5",
1294
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
1295
+ "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
1296
+ "dev": true,
1297
+ "dependencies": {
1298
+ "esbuild": "^0.25.0",
1299
+ "fdir": "^6.4.4",
1300
+ "picomatch": "^4.0.2",
1301
+ "postcss": "^8.5.3",
1302
+ "rollup": "^4.34.9",
1303
+ "tinyglobby": "^0.2.13"
1304
+ },
1305
+ "bin": {
1306
+ "vite": "bin/vite.js"
1307
+ },
1308
+ "engines": {
1309
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
1310
+ },
1311
+ "funding": {
1312
+ "url": "https://github.com/vitejs/vite?sponsor=1"
1313
+ },
1314
+ "optionalDependencies": {
1315
+ "fsevents": "~2.3.3"
1316
+ },
1317
+ "peerDependencies": {
1318
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
1319
+ "jiti": ">=1.21.0",
1320
+ "less": "*",
1321
+ "lightningcss": "^1.21.0",
1322
+ "sass": "*",
1323
+ "sass-embedded": "*",
1324
+ "stylus": "*",
1325
+ "sugarss": "*",
1326
+ "terser": "^5.16.0",
1327
+ "tsx": "^4.8.1",
1328
+ "yaml": "^2.4.2"
1329
+ },
1330
+ "peerDependenciesMeta": {
1331
+ "@types/node": {
1332
+ "optional": true
1333
+ },
1334
+ "jiti": {
1335
+ "optional": true
1336
+ },
1337
+ "less": {
1338
+ "optional": true
1339
+ },
1340
+ "lightningcss": {
1341
+ "optional": true
1342
+ },
1343
+ "sass": {
1344
+ "optional": true
1345
+ },
1346
+ "sass-embedded": {
1347
+ "optional": true
1348
+ },
1349
+ "stylus": {
1350
+ "optional": true
1351
+ },
1352
+ "sugarss": {
1353
+ "optional": true
1354
+ },
1355
+ "terser": {
1356
+ "optional": true
1357
+ },
1358
+ "tsx": {
1359
+ "optional": true
1360
+ },
1361
+ "yaml": {
1362
+ "optional": true
1363
+ }
1364
+ }
1365
+ },
1366
+ "node_modules/webidl-conversions": {
1367
+ "version": "3.0.1",
1368
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
1369
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
1370
+ },
1371
+ "node_modules/whatwg-url": {
1372
+ "version": "5.0.0",
1373
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
1374
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
1375
+ "dependencies": {
1376
+ "tr46": "~0.0.3",
1377
+ "webidl-conversions": "^3.0.0"
1378
+ }
1379
+ },
1380
+ "node_modules/ws": {
1381
+ "version": "8.18.3",
1382
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
1383
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
1384
+ "engines": {
1385
+ "node": ">=10.0.0"
1386
+ },
1387
+ "peerDependencies": {
1388
+ "bufferutil": "^4.0.1",
1389
+ "utf-8-validate": ">=5.0.2"
1390
+ },
1391
+ "peerDependenciesMeta": {
1392
+ "bufferutil": {
1393
+ "optional": true
1394
+ },
1395
+ "utf-8-validate": {
1396
+ "optional": true
1397
+ }
1398
+ }
1399
+ },
1400
+ "node_modules/zod": {
1401
+ "version": "3.25.67",
1402
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz",
1403
+ "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==",
1404
+ "funding": {
1405
+ "url": "https://github.com/sponsors/colinhacks"
1406
+ }
1407
+ },
1408
+ "node_modules/zod-to-json-schema": {
1409
+ "version": "3.24.6",
1410
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz",
1411
+ "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==",
1412
+ "peerDependencies": {
1413
+ "zod": "^3.24.1"
1414
+ }
1415
+ }
1416
+ }
1417
+ }
package.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "pyqt-ui-generator-from-image",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "react": "^19.1.0",
13
+ "react-dom": "^19.1.0",
14
+ "@google/genai": "^1.7.0",
15
+ "jszip": "3.10.1"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^22.14.0",
19
+ "typescript": "~5.7.2",
20
+ "vite": "^6.2.0"
21
+ }
22
+ }
services/geminiService.ts ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { GoogleGenAI, GenerateContentResponse } from "@google/genai";
2
+ import { Provider, AppType } from '../types';
3
+
4
+ type ImagePart = {
5
+ mimeType: string;
6
+ data: string;
7
+ };
8
+
9
+ const ANALYSIS_PROMPT_PYTHON = `
10
+ 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.
11
+ This specification will be used by another AI to write Python PyQt6 code.
12
+
13
+ **Analysis Steps:**
14
+ 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.
15
+ 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.
16
+ 3. **Determine Functionality:** Based on the components and their context, infer the application's purpose and the function of each element.
17
+ 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.
18
+
19
+ **Output Format:**
20
+ 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.
21
+ `;
22
+
23
+ const PYQT_CODEGEN_PROMPT = `
24
+ You are an expert Python developer specializing in PyQt6. Your task is to create a fully functional desktop application based on a detailed specification.
25
+
26
+ **Instructions:**
27
+ 1. **Read the Specification:** Carefully read the entire application specification provided below.
28
+ 2. **Generate Code:** Write a single, complete, and executable Python script using the PyQt6 library that implements the specification precisely.
29
+ 3. **Implement All Logic:** The generated code must not only replicate the visual layout but also implement all the described functionality.
30
+ - For web browsers, use PyQt6.QtWebEngineWidgets.
31
+ - For calculators, ensure all buttons are connected to functions that perform the correct calculations.
32
+ - For text editors, implement text editing and file operations.
33
+ 4. **Code Requirements:**
34
+ - The script must be self-contained and runnable.
35
+ - Include all necessary imports.
36
+ - Define a main window class (e.g., \`QMainWindow\`).
37
+ - Connect signals to slots to implement functionality.
38
+ - Include the standard boilerplate to instantiate and run the \`QApplication\`.
39
+ 5. **Output Format:** Provide ONLY the raw Python code. Do not include any explanations, comments about the code, or markdown fences like \`\`\`python.
40
+
41
+ --- APPLICATION SPECIFICATION ---
42
+ `;
43
+
44
+ const ANALYSIS_PROMPT_CPP = `
45
+ 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.
46
+ This specification will be used by another AI to write a C++ Qt application.
47
+
48
+ **Analysis Steps:**
49
+ 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.
50
+ 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.
51
+ 3. **Determine Functionality:** Based on the components and their context, infer the application's purpose and the function of each element.
52
+ 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.
53
+
54
+ **Output Format:**
55
+ 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.
56
+ `;
57
+
58
+ const CPP_QT_CODEGEN_PROMPT = `
59
+ 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.
60
+
61
+ **Instructions:**
62
+ 1. **Read the Specification:** Carefully read the entire application specification provided below.
63
+ 2. **Generate Code Structure:** Create a complete, compilable, and executable C++/Qt application with the following file structure:
64
+ - \`main.cpp\`: The main entry point for the application. It should instantiate and show the main window.
65
+ - \`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.
66
+ - \`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).
67
+ 3. **Implement All Logic:** The generated code must not only replicate the visual layout but also implement all the described functionality.
68
+ - For web browsers, use QWebEngineView from the QtWebEngineWidgets module.
69
+ - For calculators, ensure all buttons are connected to slots that perform the correct calculations.
70
+ 4. **Code Requirements:**
71
+ - Use C++17 or later.
72
+ - Include header guards in \`.h\` files.
73
+ - Include all necessary Qt headers.
74
+ - Connect signals to slots using the modern \`QObject::connect\` syntax.
75
+ - The code must be clean, well-organized, and ready to be compiled with a standard build system (CMake, qmake, etc.).
76
+ 5. **Output Format:**
77
+ - You MUST provide the output as a single, valid JSON object.
78
+ - The keys of the JSON object must be the filenames (e.g., "main.cpp", "mainwindow.h", "mainwindow.cpp").
79
+ - The values must be strings containing the complete, raw source code for the corresponding file.
80
+ - Do not include any explanations, comments, or markdown fences like \`\`\`json.
81
+
82
+ **Example JSON Output:**
83
+ {
84
+ "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}",
85
+ "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",
86
+ "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}"
87
+ }
88
+
89
+ --- APPLICATION SPECIFICATION ---
90
+ `;
91
+
92
+
93
+ // --- Non-streaming API callers for analysis step ---
94
+ async function callGemini(apiKey: string, prompt: string, imageParts: ImagePart[]): Promise<string> {
95
+ const ai = new GoogleGenAI({ apiKey });
96
+ const parts = [{ text: prompt }, ...imageParts.map(img => ({ inlineData: { mimeType: img.mimeType, data: img.data } }))];
97
+ const response: GenerateContentResponse = await ai.models.generateContent({ model: 'gemini-2.5-flash-preview-04-17', contents: [{ parts }] });
98
+ return response.text;
99
+ }
100
+
101
+ async function callOpenAI(apiKey: string, prompt: string, imageParts: ImagePart[]): Promise<string> {
102
+ const content = [{ type: 'text', text: prompt }, ...imageParts.map(img => ({ type: 'image_url', image_url: { url: `data:${img.mimeType};base64,${img.data}` } }))];
103
+ const body = { model: 'gpt-4o', messages: [{ role: 'user', content }], max_tokens: 4096 };
104
+ 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) });
105
+ if (!response.ok) { const err = await response.json(); throw new Error(`OpenAI API Error: ${err?.error?.message || response.statusText}`); }
106
+ const data = await response.json();
107
+ return data.choices?.[0]?.message?.content ?? '';
108
+ }
109
+
110
+ async function callAnthropic(apiKey: string, prompt: string, imageParts: ImagePart[]): Promise<string> {
111
+ const content = [{ type: 'text', text: prompt }, ...imageParts.map(img => ({ type: 'image', source: { type: 'base64', media_type: img.mimeType, data: img.data } }))];
112
+ const body = { model: 'claude-3-sonnet-20240229', messages: [{ role: 'user', content }], max_tokens: 4096 };
113
+ 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) });
114
+ if (!response.ok) { const err = await response.json(); throw new Error(`Anthropic API Error: ${err?.error?.message || response.statusText}`); }
115
+ const data = await response.json();
116
+ return data.content?.[0]?.text ?? '';
117
+ }
118
+
119
+ // --- Unified Streaming API caller ---
120
+ async function callApiStream(
121
+ provider: Provider,
122
+ apiKey: string,
123
+ prompt: string,
124
+ onChunk: (chunk: string) => void
125
+ ): Promise<string> {
126
+ const model = provider === 'gemini' ? 'gemini-2.5-flash-preview-04-17' : (provider === 'openai' ? 'gpt-4o' : 'claude-3-sonnet-20240229');
127
+ let fullResponse = '';
128
+
129
+ const processChunk = (chunk: string) => {
130
+ fullResponse += chunk;
131
+ onChunk(chunk);
132
+ };
133
+
134
+ if (provider === 'gemini') {
135
+ const ai = new GoogleGenAI({ apiKey });
136
+ const response = await ai.models.generateContentStream({ model, contents: prompt });
137
+ for await (const chunk of response) {
138
+ processChunk(chunk.text);
139
+ }
140
+ } else if (provider === 'openai') {
141
+ const body = { model, messages: [{ role: 'user', content: prompt }], max_tokens: 4096, stream: true };
142
+ 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) });
143
+ if (!response.ok || !response.body) { const err = await response.json(); throw new Error(`OpenAI API Error: ${err?.error?.message || response.statusText}`); }
144
+
145
+ const reader = response.body.getReader();
146
+ const decoder = new TextDecoder();
147
+ while (true) {
148
+ const { done, value } = await reader.read();
149
+ if (done) break;
150
+ const chunk = decoder.decode(value);
151
+ const lines = chunk.split('\n').filter(line => line.startsWith('data: '));
152
+ for (const line of lines) {
153
+ const message = line.substring(6);
154
+ if (message === '[DONE]') break;
155
+ try {
156
+ const json = JSON.parse(message);
157
+ const textChunk = json.choices[0]?.delta?.content;
158
+ if (textChunk) processChunk(textChunk);
159
+ } catch (e) { /* Ignore parsing errors for incomplete chunks */ }
160
+ }
161
+ }
162
+ } else if (provider === 'anthropic') {
163
+ const body = { model, messages: [{ role: 'user', content: prompt }], max_tokens: 4096, stream: true };
164
+ 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) });
165
+ if (!response.ok || !response.body) { const err = await response.json(); throw new Error(`Anthropic API Error: ${err?.error?.message || response.statusText}`); }
166
+
167
+ const reader = response.body.getReader();
168
+ const decoder = new TextDecoder();
169
+ let buffer = '';
170
+ while (true) {
171
+ const { done, value } = await reader.read();
172
+ if (done) break;
173
+ buffer += decoder.decode(value, { stream: true });
174
+ const events = buffer.split('\n\n');
175
+ buffer = events.pop() || ''; // Keep the last, possibly incomplete, event in buffer
176
+
177
+ for (const event of events) {
178
+ if (!event.startsWith('event: content_block_delta')) continue;
179
+ const dataLine = event.split('\n').find(line => line.startsWith('data: '));
180
+ if (dataLine) {
181
+ try {
182
+ const jsonData = JSON.parse(dataLine.substring(6));
183
+ if (jsonData.type === 'content_block_delta' && jsonData.delta.type === 'text_delta') {
184
+ processChunk(jsonData.delta.text);
185
+ }
186
+ } catch (e) { /* Ignore incomplete JSON */ }
187
+ }
188
+ }
189
+ }
190
+ }
191
+ return fullResponse;
192
+ }
193
+
194
+
195
+ export async function generateCode(
196
+ appType: AppType,
197
+ provider: Provider,
198
+ apiKey: string,
199
+ imageParts: ImagePart[],
200
+ instructions: string,
201
+ onStatusChange: (status: string) => void,
202
+ onChunk: (chunk: string) => void
203
+ ): Promise<string | void> {
204
+ try {
205
+ const isPython = appType === 'python';
206
+ const analysisPromptTemplate = isPython ? ANALYSIS_PROMPT_PYTHON : ANALYSIS_PROMPT_CPP;
207
+ const codegenPromptTemplate = isPython ? PYQT_CODEGEN_PROMPT : CPP_QT_CODEGEN_PROMPT;
208
+
209
+ onStatusChange(`Step 1/2: Analyzing UI with ${provider}...`);
210
+ const analysisPrompt = `${analysisPromptTemplate}${instructions ? `\n\n--- USER REFINEMENT INSTRUCTIONS ---\n${instructions.trim()}` : ''}`;
211
+
212
+ const callAnalysisApi = {
213
+ 'gemini': callGemini,
214
+ 'openai': callOpenAI,
215
+ 'anthropic': callAnthropic,
216
+ }[provider];
217
+
218
+ const specification = await callAnalysisApi(apiKey, analysisPrompt, imageParts);
219
+ if (!specification || specification.trim() === '') {
220
+ throw new Error('AI failed to generate a UI specification. The response was empty.');
221
+ }
222
+
223
+ const lang = isPython ? 'Python' : 'C++';
224
+ onStatusChange(`Step 2/2: Generating ${lang} code with ${provider}...`);
225
+ const codegenPrompt = `${codegenPromptTemplate}\n${specification}`;
226
+
227
+ const finalResult = await callApiStream(provider, apiKey, codegenPrompt, (chunk) => {
228
+ if (isPython) {
229
+ // The first chunk from some models might be a markdown fence, remove it.
230
+ const cleanedChunk = chunk.replace(/^```(python)?\n/, '');
231
+ onChunk(cleanedChunk);
232
+ } else {
233
+ // For C++, we just stream raw chunks. The final string will be parsed.
234
+ onChunk(chunk);
235
+ }
236
+ });
237
+
238
+ if (isPython) {
239
+ // Final cleanup of trailing markdown fence for Python.
240
+ onChunk('\n```');
241
+ return;
242
+ } else {
243
+ // For C++, return the complete JSON string for parsing in the component.
244
+ return finalResult;
245
+ }
246
+
247
+ } catch (error) {
248
+ console.error(`Error during code generation with ${provider}:`, error);
249
+ if (error instanceof Error) {
250
+ throw new Error(`Failed to communicate with the ${provider} API. ${error.message}`);
251
+ }
252
+ throw new Error("An unknown error occurred while generating code.");
253
+ }
254
+ }
tsconfig.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "experimentalDecorators": true,
5
+ "useDefineForClassFields": false,
6
+ "module": "ESNext",
7
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "isolatedModules": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+ "allowJs": true,
17
+ "jsx": "react-jsx",
18
+
19
+ /* Linting */
20
+ "strict": true,
21
+ "noUnusedLocals": true,
22
+ "noUnusedParameters": true,
23
+ "noFallthroughCasesInSwitch": true,
24
+ "noUncheckedSideEffectImports": true,
25
+
26
+ "paths": {
27
+ "@/*" : ["./*"]
28
+ }
29
+ }
30
+ }
types.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ export enum AppStatus {
2
+ IDLE,
3
+ PROCESSING,
4
+ SUCCESS,
5
+ ERROR,
6
+ }
7
+
8
+ export type Provider = 'gemini' | 'openai' | 'anthropic';
9
+
10
+ export type AppType = 'python' | 'c++';
vite.config.ts ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import path from 'path';
2
+ import { defineConfig, loadEnv } from 'vite';
3
+
4
+ export default defineConfig(({ mode }) => {
5
+ const env = loadEnv(mode, '.', '');
6
+ return {
7
+ define: {
8
+ 'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
9
+ 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
10
+ },
11
+ resolve: {
12
+ alias: {
13
+ '@': path.resolve(__dirname, '.'),
14
+ }
15
+ }
16
+ };
17
+ });