@su-record/vibe 2.6.3 → 2.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/ui-syncer.md +143 -43
- package/hooks/scripts/gemini-ui-gen.js +409 -0
- package/package.json +1 -1
package/agents/ui-syncer.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Sync UI code with design files
|
|
2
|
+
description: Sync UI code with design files (Gemini or Claude)
|
|
3
3
|
argument-hint: "design folder path"
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -7,6 +7,9 @@ argument-hint: "design folder path"
|
|
|
7
7
|
|
|
8
8
|
Analyze design files and update existing UI code to match.
|
|
9
9
|
|
|
10
|
+
- **Gemini enabled**: Gemini analyzes designs and generates code
|
|
11
|
+
- **Gemini disabled**: Claude handles directly
|
|
12
|
+
|
|
10
13
|
## Usage
|
|
11
14
|
|
|
12
15
|
```
|
|
@@ -17,11 +20,88 @@ Analyze design files and update existing UI code to match.
|
|
|
17
20
|
|
|
18
21
|
## Process
|
|
19
22
|
|
|
20
|
-
###
|
|
23
|
+
### 0. Check Gemini Status
|
|
21
24
|
|
|
22
|
-
**
|
|
25
|
+
**FIRST: Check if Gemini is available.**
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
vibe gemini status
|
|
29
|
+
```
|
|
23
30
|
|
|
24
|
-
|
|
31
|
+
Or check: `~/.config/vibe/gemini.json` or `~/.config/vibe/gemini-apikey.json`
|
|
32
|
+
|
|
33
|
+
- **Gemini available** → Use Gemini for code generation (Step 1A)
|
|
34
|
+
- **Gemini NOT available** → Claude handles directly (Step 1B)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Path A: Gemini Enabled
|
|
39
|
+
|
|
40
|
+
### 1A. Collect ALL Design Files
|
|
41
|
+
|
|
42
|
+
Scan folder and collect all files to send to Gemini:
|
|
43
|
+
|
|
44
|
+
| Format | What to Collect |
|
|
45
|
+
| ------ | --------------- |
|
|
46
|
+
| `*.html` | Full HTML content |
|
|
47
|
+
| `*.png` / `*.jpg` / `*.webp` | Image as base64 |
|
|
48
|
+
| `*.json` | Design tokens, theme config |
|
|
49
|
+
| `*.css` / `*.scss` | Style variables |
|
|
50
|
+
| `*.md` | Design guidelines |
|
|
51
|
+
| `*.svg` | Vector content |
|
|
52
|
+
|
|
53
|
+
### 2A. Send to Gemini
|
|
54
|
+
|
|
55
|
+
Use the Gemini UI Generator script:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
node hooks/scripts/gemini-ui-gen.js \
|
|
59
|
+
--design-folder ./design/ui/ \
|
|
60
|
+
--framework react \
|
|
61
|
+
--output ./src/components
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Or call Gemini API directly with all files:**
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
// Prepare multipart content
|
|
68
|
+
const parts = [
|
|
69
|
+
// Images as inline data
|
|
70
|
+
{ inlineData: { mimeType: "image/png", data: imageBase64 } },
|
|
71
|
+
// HTML/CSS/JSON as text
|
|
72
|
+
{ text: `HTML Mockup:\n${htmlContent}` },
|
|
73
|
+
{ text: `CSS Styles:\n${cssContent}` },
|
|
74
|
+
{ text: `Design Tokens:\n${jsonContent}` },
|
|
75
|
+
// Prompt
|
|
76
|
+
{ text: `
|
|
77
|
+
Analyze these design files and generate production-ready React components.
|
|
78
|
+
|
|
79
|
+
Requirements:
|
|
80
|
+
1. Match the visual design exactly
|
|
81
|
+
2. Use Tailwind CSS
|
|
82
|
+
3. TypeScript with proper types
|
|
83
|
+
4. Responsive and accessible
|
|
84
|
+
|
|
85
|
+
Output complete component code.
|
|
86
|
+
` }
|
|
87
|
+
];
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 3A. Apply Gemini's Output
|
|
91
|
+
|
|
92
|
+
1. Parse Gemini's code output
|
|
93
|
+
2. Extract component files
|
|
94
|
+
3. Compare with existing code
|
|
95
|
+
4. Show diff to user
|
|
96
|
+
5. Apply with confirmation
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Path B: Gemini NOT Available (Claude Fallback)
|
|
101
|
+
|
|
102
|
+
### 1B. Read ALL Design Files
|
|
103
|
+
|
|
104
|
+
**MANDATORY: Read every file in the design folder.**
|
|
25
105
|
|
|
26
106
|
| Format | What to Extract |
|
|
27
107
|
| ------ | --------------- |
|
|
@@ -42,7 +122,7 @@ Scan folder and read ALL supported files:
|
|
|
42
122
|
|
|
43
123
|
**CRITICAL:** Do NOT skip any file. Read images with the Read tool.
|
|
44
124
|
|
|
45
|
-
###
|
|
125
|
+
### 2B. Analyze Design Intent (Claude)
|
|
46
126
|
|
|
47
127
|
From all design files, extract:
|
|
48
128
|
|
|
@@ -63,12 +143,15 @@ From all design files, extract:
|
|
|
63
143
|
- Border radius, shadows
|
|
64
144
|
- Dark/light theme support
|
|
65
145
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
146
|
+
### 3B. Generate Code (Claude)
|
|
147
|
+
|
|
148
|
+
Claude generates the code directly based on analysis.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Common Steps (Both Paths)
|
|
70
153
|
|
|
71
|
-
###
|
|
154
|
+
### 4. Find Existing UI Code
|
|
72
155
|
|
|
73
156
|
Search for existing UI implementation:
|
|
74
157
|
|
|
@@ -87,7 +170,7 @@ Detect framework:
|
|
|
87
170
|
- Svelte: `*.svelte`
|
|
88
171
|
- Angular: `*.component.ts`
|
|
89
172
|
|
|
90
|
-
###
|
|
173
|
+
### 5. Compare Design vs Code
|
|
91
174
|
|
|
92
175
|
Create a diff report:
|
|
93
176
|
|
|
@@ -98,7 +181,7 @@ Create a diff report:
|
|
|
98
181
|
| Border radius | 12px | 8px | Update |
|
|
99
182
|
| Component X | Exists | Missing | Create |
|
|
100
183
|
|
|
101
|
-
###
|
|
184
|
+
### 6. Generate Update Plan
|
|
102
185
|
|
|
103
186
|
List all changes needed:
|
|
104
187
|
|
|
@@ -125,7 +208,7 @@ List all changes needed:
|
|
|
125
208
|
**New Components:**
|
|
126
209
|
- List components that exist in design but not in code
|
|
127
210
|
|
|
128
|
-
###
|
|
211
|
+
### 7. Execute Updates
|
|
129
212
|
|
|
130
213
|
**Ask user before making changes:**
|
|
131
214
|
|
|
@@ -147,7 +230,7 @@ Proceed with updates? [Y/n]
|
|
|
147
230
|
4. Create new components (if needed)
|
|
148
231
|
5. Update imports
|
|
149
232
|
|
|
150
|
-
###
|
|
233
|
+
### 8. Verify Changes
|
|
151
234
|
|
|
152
235
|
After updates:
|
|
153
236
|
|
|
@@ -159,6 +242,7 @@ After updates:
|
|
|
159
242
|
|
|
160
243
|
```
|
|
161
244
|
📂 Design Analysis: ./design/ui/
|
|
245
|
+
🤖 Mode: Gemini (or Claude fallback)
|
|
162
246
|
|
|
163
247
|
Files Read:
|
|
164
248
|
✓ homepage.html (structure)
|
|
@@ -190,34 +274,33 @@ Apply changes? [Y/n]
|
|
|
190
274
|
Next: Run `npm run build` to verify
|
|
191
275
|
```
|
|
192
276
|
|
|
193
|
-
## Example
|
|
277
|
+
## Example (with Gemini)
|
|
194
278
|
|
|
195
279
|
```
|
|
196
280
|
User: /vibe.utils --ui-sync ./design/dashboard/
|
|
197
281
|
|
|
198
|
-
Claude:
|
|
282
|
+
Claude: Checking Gemini status...
|
|
283
|
+
✅ Gemini: OAuth authenticated (user@example.com)
|
|
199
284
|
|
|
200
|
-
📂
|
|
285
|
+
📂 Collecting design files...
|
|
201
286
|
- dashboard.html
|
|
202
287
|
- dashboard-dark.png
|
|
203
288
|
- dashboard-light.png
|
|
204
289
|
- tokens.json
|
|
205
290
|
- components.md
|
|
206
291
|
|
|
207
|
-
|
|
208
|
-
[Extracts: header, sidebar, 3-column grid, card components]
|
|
292
|
+
🤖 Sending to Gemini for analysis...
|
|
209
293
|
|
|
210
|
-
|
|
211
|
-
|
|
294
|
+
Gemini Response:
|
|
295
|
+
Layout: Header + Sidebar + 3-column main
|
|
296
|
+
Theme: Dark (#0F172A bg, #F8FAFC text)
|
|
297
|
+
Components: Header, Sidebar, StatCard, DataTable
|
|
212
298
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
Layout: Header + Sidebar + 3-column main
|
|
219
|
-
Theme: Dark (#0F172A bg, #F8FAFC text)
|
|
220
|
-
Components: Header, Sidebar, StatCard, DataTable
|
|
299
|
+
Generated code:
|
|
300
|
+
- Header.tsx (64 lines)
|
|
301
|
+
- Sidebar.tsx (45 lines)
|
|
302
|
+
- StatCard.tsx (32 lines)
|
|
303
|
+
- DataTable.tsx (78 lines)
|
|
221
304
|
|
|
222
305
|
🔍 Comparing with existing code...
|
|
223
306
|
|
|
@@ -228,31 +311,48 @@ Missing: src/components/DataTable.tsx
|
|
|
228
311
|
Differences:
|
|
229
312
|
1. StatCard border-radius: design=16px, code=8px
|
|
230
313
|
2. Sidebar width: design=240px, code=200px
|
|
231
|
-
3.
|
|
232
|
-
4.
|
|
233
|
-
|
|
314
|
+
3. Primary color: design=#6366F1, code=#3B82F6
|
|
315
|
+
4. DataTable component: missing
|
|
316
|
+
|
|
317
|
+
Apply 3 updates + create 1 component? [Y/n]
|
|
318
|
+
```
|
|
234
319
|
|
|
235
|
-
|
|
320
|
+
## Example (Claude Fallback)
|
|
236
321
|
|
|
237
|
-
|
|
322
|
+
```
|
|
323
|
+
User: /vibe.utils --ui-sync ./design/dashboard/
|
|
238
324
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
- src/components/Sidebar.tsx (width)
|
|
242
|
-
- src/components/Header.tsx (height)
|
|
243
|
-
- tailwind.config.js (primary color)
|
|
244
|
-
- src/components/DataTable.tsx (created)
|
|
325
|
+
Claude: Checking Gemini status...
|
|
326
|
+
⚠️ Gemini not configured. Using Claude fallback.
|
|
245
327
|
|
|
246
|
-
|
|
328
|
+
📂 Reading design files...
|
|
329
|
+
|
|
330
|
+
Reading dashboard.html...
|
|
331
|
+
[Extracts: header, sidebar, 3-column grid]
|
|
332
|
+
|
|
333
|
+
Reading dashboard-dark.png...
|
|
334
|
+
[Analyzes: dark theme colors, spacing]
|
|
335
|
+
|
|
336
|
+
Reading tokens.json...
|
|
337
|
+
[Extracts: color palette, spacing scale]
|
|
338
|
+
|
|
339
|
+
📊 Design Analysis Complete (Claude):
|
|
340
|
+
|
|
341
|
+
Layout: Header + Sidebar + 3-column main
|
|
342
|
+
Theme: Dark (#0F172A bg, #F8FAFC text)
|
|
343
|
+
|
|
344
|
+
🔍 Comparing with existing code...
|
|
345
|
+
...
|
|
247
346
|
```
|
|
248
347
|
|
|
249
348
|
## Notes
|
|
250
349
|
|
|
350
|
+
- Always check Gemini status first
|
|
351
|
+
- Gemini handles image analysis better than Claude for visual matching
|
|
352
|
+
- Claude fallback works but may be less accurate for complex visuals
|
|
251
353
|
- Always read ALL files including images
|
|
252
354
|
- Ask for confirmation before making changes
|
|
253
355
|
- Preserve existing functionality while updating styles
|
|
254
|
-
- Create backup recommendation for large changes
|
|
255
|
-
- Support both CSS-in-JS and traditional CSS
|
|
256
356
|
|
|
257
357
|
---
|
|
258
358
|
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Gemini UI Code Generator
|
|
5
|
+
*
|
|
6
|
+
* 디자인 파일(이미지, HTML 등)을 분석해서 UI 코드를 생성합니다.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node gemini-ui-gen.js --image ./design.png --framework react --output ./src/components
|
|
10
|
+
* node gemini-ui-gen.js --html ./mockup.html --framework vue --output ./src/components
|
|
11
|
+
* node gemini-ui-gen.js --design-folder ./design/ --framework react --output ./src
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import os from 'os';
|
|
17
|
+
|
|
18
|
+
// ============================================
|
|
19
|
+
// Config
|
|
20
|
+
// ============================================
|
|
21
|
+
|
|
22
|
+
function getGlobalConfigDir() {
|
|
23
|
+
return process.platform === 'win32'
|
|
24
|
+
? path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'vibe')
|
|
25
|
+
: path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'vibe');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getGeminiCredentials() {
|
|
29
|
+
const configDir = getGlobalConfigDir();
|
|
30
|
+
|
|
31
|
+
// OAuth 토큰 확인
|
|
32
|
+
const tokenPath = path.join(configDir, 'gemini-token.json');
|
|
33
|
+
if (fs.existsSync(tokenPath)) {
|
|
34
|
+
const tokenData = JSON.parse(fs.readFileSync(tokenPath, 'utf-8'));
|
|
35
|
+
if (tokenData.access_token) {
|
|
36
|
+
return { type: 'oauth', accessToken: tokenData.access_token };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// API Key 확인
|
|
41
|
+
const keyPath = path.join(configDir, 'gemini-apikey.json');
|
|
42
|
+
if (fs.existsSync(keyPath)) {
|
|
43
|
+
const keyData = JSON.parse(fs.readFileSync(keyPath, 'utf-8'));
|
|
44
|
+
if (keyData.apiKey) {
|
|
45
|
+
return { type: 'apikey', apiKey: keyData.apiKey };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ============================================
|
|
53
|
+
// Gemini API with Vision
|
|
54
|
+
// ============================================
|
|
55
|
+
|
|
56
|
+
async function callGeminiWithImage(imageBase64, mimeType, prompt, creds) {
|
|
57
|
+
const model = 'gemini-2.0-flash';
|
|
58
|
+
|
|
59
|
+
const requestBody = {
|
|
60
|
+
contents: [
|
|
61
|
+
{
|
|
62
|
+
role: 'user',
|
|
63
|
+
parts: [
|
|
64
|
+
{
|
|
65
|
+
inlineData: {
|
|
66
|
+
mimeType,
|
|
67
|
+
data: imageBase64
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
text: prompt
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
generationConfig: {
|
|
77
|
+
maxOutputTokens: 8192,
|
|
78
|
+
temperature: 0.3,
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
let url;
|
|
83
|
+
let headers;
|
|
84
|
+
|
|
85
|
+
if (creds.type === 'apikey') {
|
|
86
|
+
url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${creds.apiKey}`;
|
|
87
|
+
headers = { 'Content-Type': 'application/json' };
|
|
88
|
+
} else {
|
|
89
|
+
// OAuth - Antigravity
|
|
90
|
+
url = 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:generateContent';
|
|
91
|
+
headers = {
|
|
92
|
+
'Authorization': `Bearer ${creds.accessToken}`,
|
|
93
|
+
'Content-Type': 'application/json',
|
|
94
|
+
'x-goog-api-client': 'vibe-ui-gen',
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Wrap for Antigravity
|
|
98
|
+
const wrappedBody = {
|
|
99
|
+
project: 'anthropic-api-proxy',
|
|
100
|
+
model: 'gemini-2.0-flash-001',
|
|
101
|
+
request: requestBody,
|
|
102
|
+
requestType: 'agent',
|
|
103
|
+
userAgent: 'antigravity',
|
|
104
|
+
requestId: `ui-gen-${Date.now()}`,
|
|
105
|
+
};
|
|
106
|
+
requestBody = wrappedBody;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const response = await fetch(url, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers,
|
|
112
|
+
body: JSON.stringify(creds.type === 'apikey' ? requestBody : requestBody),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
const errorText = await response.text();
|
|
117
|
+
throw new Error(`Gemini API error (${response.status}): ${errorText}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const result = await response.json();
|
|
121
|
+
const responseData = result.response || result;
|
|
122
|
+
|
|
123
|
+
if (!responseData.candidates || responseData.candidates.length === 0) {
|
|
124
|
+
throw new Error('Gemini returned empty response');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return responseData.candidates[0].content?.parts?.[0]?.text || '';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function callGeminiText(prompt, creds) {
|
|
131
|
+
const model = 'gemini-2.0-flash';
|
|
132
|
+
|
|
133
|
+
const requestBody = {
|
|
134
|
+
contents: [
|
|
135
|
+
{
|
|
136
|
+
role: 'user',
|
|
137
|
+
parts: [{ text: prompt }]
|
|
138
|
+
}
|
|
139
|
+
],
|
|
140
|
+
generationConfig: {
|
|
141
|
+
maxOutputTokens: 8192,
|
|
142
|
+
temperature: 0.3,
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
let url;
|
|
147
|
+
let headers;
|
|
148
|
+
|
|
149
|
+
if (creds.type === 'apikey') {
|
|
150
|
+
url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${creds.apiKey}`;
|
|
151
|
+
headers = { 'Content-Type': 'application/json' };
|
|
152
|
+
} else {
|
|
153
|
+
url = 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:generateContent';
|
|
154
|
+
headers = {
|
|
155
|
+
'Authorization': `Bearer ${creds.accessToken}`,
|
|
156
|
+
'Content-Type': 'application/json',
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const response = await fetch(url, {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
headers,
|
|
163
|
+
body: JSON.stringify(requestBody),
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
if (!response.ok) {
|
|
167
|
+
const errorText = await response.text();
|
|
168
|
+
throw new Error(`Gemini API error (${response.status}): ${errorText}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const result = await response.json();
|
|
172
|
+
const responseData = result.response || result;
|
|
173
|
+
|
|
174
|
+
return responseData.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ============================================
|
|
178
|
+
// UI Code Generation
|
|
179
|
+
// ============================================
|
|
180
|
+
|
|
181
|
+
function getFrameworkPrompt(framework) {
|
|
182
|
+
const prompts = {
|
|
183
|
+
react: `Generate React TypeScript components using:
|
|
184
|
+
- Functional components with hooks
|
|
185
|
+
- Tailwind CSS for styling
|
|
186
|
+
- Proper TypeScript types/interfaces
|
|
187
|
+
- Export as default`,
|
|
188
|
+
|
|
189
|
+
vue: `Generate Vue 3 components using:
|
|
190
|
+
- Composition API with <script setup>
|
|
191
|
+
- Tailwind CSS for styling
|
|
192
|
+
- TypeScript support
|
|
193
|
+
- Single File Component format`,
|
|
194
|
+
|
|
195
|
+
svelte: `Generate Svelte components using:
|
|
196
|
+
- Svelte 5 runes syntax
|
|
197
|
+
- Tailwind CSS for styling
|
|
198
|
+
- TypeScript support`,
|
|
199
|
+
|
|
200
|
+
html: `Generate semantic HTML5 with:
|
|
201
|
+
- Tailwind CSS classes
|
|
202
|
+
- Accessible markup
|
|
203
|
+
- Responsive design`,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
return prompts[framework] || prompts.react;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function generateUIFromImage(imagePath, framework, creds) {
|
|
210
|
+
const imageBuffer = fs.readFileSync(imagePath);
|
|
211
|
+
const imageBase64 = imageBuffer.toString('base64');
|
|
212
|
+
|
|
213
|
+
const ext = path.extname(imagePath).toLowerCase();
|
|
214
|
+
const mimeTypes = {
|
|
215
|
+
'.png': 'image/png',
|
|
216
|
+
'.jpg': 'image/jpeg',
|
|
217
|
+
'.jpeg': 'image/jpeg',
|
|
218
|
+
'.webp': 'image/webp',
|
|
219
|
+
'.gif': 'image/gif',
|
|
220
|
+
};
|
|
221
|
+
const mimeType = mimeTypes[ext] || 'image/png';
|
|
222
|
+
|
|
223
|
+
const prompt = `Analyze this UI design image and generate production-ready code.
|
|
224
|
+
|
|
225
|
+
${getFrameworkPrompt(framework)}
|
|
226
|
+
|
|
227
|
+
Requirements:
|
|
228
|
+
1. Match the visual design exactly (colors, spacing, typography, layout)
|
|
229
|
+
2. Extract exact colors as hex values
|
|
230
|
+
3. Use proper semantic HTML structure
|
|
231
|
+
4. Make it responsive (mobile-first)
|
|
232
|
+
5. Include hover/focus states where appropriate
|
|
233
|
+
6. Add appropriate accessibility attributes
|
|
234
|
+
|
|
235
|
+
Output format:
|
|
236
|
+
\`\`\`${framework === 'html' ? 'html' : 'tsx'}
|
|
237
|
+
// Component code here
|
|
238
|
+
\`\`\`
|
|
239
|
+
|
|
240
|
+
Also provide a summary of:
|
|
241
|
+
- Colors extracted
|
|
242
|
+
- Components identified
|
|
243
|
+
- Layout structure`;
|
|
244
|
+
|
|
245
|
+
return callGeminiWithImage(imageBase64, mimeType, prompt, creds);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function generateUIFromHTML(htmlPath, framework, creds) {
|
|
249
|
+
const htmlContent = fs.readFileSync(htmlPath, 'utf-8');
|
|
250
|
+
|
|
251
|
+
const prompt = `Convert this HTML mockup to production-ready ${framework} code.
|
|
252
|
+
|
|
253
|
+
HTML Mockup:
|
|
254
|
+
\`\`\`html
|
|
255
|
+
${htmlContent}
|
|
256
|
+
\`\`\`
|
|
257
|
+
|
|
258
|
+
${getFrameworkPrompt(framework)}
|
|
259
|
+
|
|
260
|
+
Requirements:
|
|
261
|
+
1. Preserve the exact visual appearance
|
|
262
|
+
2. Extract inline styles to Tailwind classes
|
|
263
|
+
3. Create reusable components where appropriate
|
|
264
|
+
4. Add proper TypeScript types
|
|
265
|
+
5. Make it responsive
|
|
266
|
+
|
|
267
|
+
Output the converted code in proper format.`;
|
|
268
|
+
|
|
269
|
+
return callGeminiText(prompt, creds);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function analyzeDesignFolder(folderPath, framework, creds) {
|
|
273
|
+
const files = fs.readdirSync(folderPath);
|
|
274
|
+
const results = [];
|
|
275
|
+
|
|
276
|
+
// Read all files
|
|
277
|
+
for (const file of files) {
|
|
278
|
+
const filePath = path.join(folderPath, file);
|
|
279
|
+
const ext = path.extname(file).toLowerCase();
|
|
280
|
+
|
|
281
|
+
if (['.png', '.jpg', '.jpeg', '.webp'].includes(ext)) {
|
|
282
|
+
console.log(`📷 Analyzing image: ${file}`);
|
|
283
|
+
const result = await generateUIFromImage(filePath, framework, creds);
|
|
284
|
+
results.push({ file, type: 'image', result });
|
|
285
|
+
} else if (ext === '.html') {
|
|
286
|
+
console.log(`📄 Analyzing HTML: ${file}`);
|
|
287
|
+
const result = await generateUIFromHTML(filePath, framework, creds);
|
|
288
|
+
results.push({ file, type: 'html', result });
|
|
289
|
+
} else if (ext === '.json') {
|
|
290
|
+
console.log(`📋 Reading tokens: ${file}`);
|
|
291
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
292
|
+
results.push({ file, type: 'tokens', content });
|
|
293
|
+
} else if (['.css', '.scss'].includes(ext)) {
|
|
294
|
+
console.log(`🎨 Reading styles: ${file}`);
|
|
295
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
296
|
+
results.push({ file, type: 'styles', content });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return results;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ============================================
|
|
304
|
+
// CLI
|
|
305
|
+
// ============================================
|
|
306
|
+
|
|
307
|
+
async function main() {
|
|
308
|
+
const args = process.argv.slice(2);
|
|
309
|
+
|
|
310
|
+
// Parse arguments
|
|
311
|
+
const options = {
|
|
312
|
+
image: null,
|
|
313
|
+
html: null,
|
|
314
|
+
designFolder: null,
|
|
315
|
+
framework: 'react',
|
|
316
|
+
output: './generated',
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
for (let i = 0; i < args.length; i++) {
|
|
320
|
+
switch (args[i]) {
|
|
321
|
+
case '--image':
|
|
322
|
+
options.image = args[++i];
|
|
323
|
+
break;
|
|
324
|
+
case '--html':
|
|
325
|
+
options.html = args[++i];
|
|
326
|
+
break;
|
|
327
|
+
case '--design-folder':
|
|
328
|
+
case '--folder':
|
|
329
|
+
options.designFolder = args[++i];
|
|
330
|
+
break;
|
|
331
|
+
case '--framework':
|
|
332
|
+
case '-f':
|
|
333
|
+
options.framework = args[++i];
|
|
334
|
+
break;
|
|
335
|
+
case '--output':
|
|
336
|
+
case '-o':
|
|
337
|
+
options.output = args[++i];
|
|
338
|
+
break;
|
|
339
|
+
case '--help':
|
|
340
|
+
case '-h':
|
|
341
|
+
console.log(`
|
|
342
|
+
Gemini UI Code Generator
|
|
343
|
+
|
|
344
|
+
Usage:
|
|
345
|
+
node gemini-ui-gen.js --image ./design.png --framework react
|
|
346
|
+
node gemini-ui-gen.js --html ./mockup.html --framework vue
|
|
347
|
+
node gemini-ui-gen.js --design-folder ./design/ --framework react
|
|
348
|
+
|
|
349
|
+
Options:
|
|
350
|
+
--image <path> Image file to analyze
|
|
351
|
+
--html <path> HTML mockup to convert
|
|
352
|
+
--design-folder <path> Folder with design files
|
|
353
|
+
--framework <name> Target framework (react, vue, svelte, html)
|
|
354
|
+
--output <path> Output directory
|
|
355
|
+
--help Show this help
|
|
356
|
+
`);
|
|
357
|
+
process.exit(0);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Check credentials
|
|
362
|
+
const creds = getGeminiCredentials();
|
|
363
|
+
if (!creds) {
|
|
364
|
+
console.error('❌ Gemini credentials not found. Run: vibe gemini auth');
|
|
365
|
+
process.exit(1);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
console.log(`🤖 Gemini UI Generator (${creds.type})`);
|
|
369
|
+
console.log(`📦 Framework: ${options.framework}`);
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
let result;
|
|
373
|
+
|
|
374
|
+
if (options.image) {
|
|
375
|
+
console.log(`\n📷 Analyzing: ${options.image}\n`);
|
|
376
|
+
result = await generateUIFromImage(options.image, options.framework, creds);
|
|
377
|
+
} else if (options.html) {
|
|
378
|
+
console.log(`\n📄 Converting: ${options.html}\n`);
|
|
379
|
+
result = await generateUIFromHTML(options.html, options.framework, creds);
|
|
380
|
+
} else if (options.designFolder) {
|
|
381
|
+
console.log(`\n📂 Analyzing folder: ${options.designFolder}\n`);
|
|
382
|
+
const results = await analyzeDesignFolder(options.designFolder, options.framework, creds);
|
|
383
|
+
result = JSON.stringify(results, null, 2);
|
|
384
|
+
} else {
|
|
385
|
+
console.error('❌ No input specified. Use --image, --html, or --design-folder');
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
console.log('\n' + '='.repeat(60) + '\n');
|
|
390
|
+
console.log(result);
|
|
391
|
+
console.log('\n' + '='.repeat(60));
|
|
392
|
+
|
|
393
|
+
// Output to file if specified
|
|
394
|
+
if (options.output && result) {
|
|
395
|
+
if (!fs.existsSync(options.output)) {
|
|
396
|
+
fs.mkdirSync(options.output, { recursive: true });
|
|
397
|
+
}
|
|
398
|
+
const outputFile = path.join(options.output, `generated-${Date.now()}.txt`);
|
|
399
|
+
fs.writeFileSync(outputFile, result);
|
|
400
|
+
console.log(`\n✅ Output saved to: ${outputFile}`);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
} catch (error) {
|
|
404
|
+
console.error(`\n❌ Error: ${error.message}`);
|
|
405
|
+
process.exit(1);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
main();
|