skema-core 0.1.0
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/README.md +116 -0
- package/dist/cli.js +37 -0
- package/dist/index.d.mts +356 -0
- package/dist/index.d.ts +356 -0
- package/dist/index.js +2334 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2307 -0
- package/dist/index.mjs.map +1 -0
- package/dist/server.d.mts +260 -0
- package/dist/server.d.ts +260 -0
- package/dist/server.js +476 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +465 -0
- package/dist/server.mjs.map +1 -0
- package/package.json +76 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var child_process = require('child_process');
|
|
4
|
+
var generativeAi = require('@google/generative-ai');
|
|
5
|
+
|
|
6
|
+
// src/server/gemini-cli.ts
|
|
7
|
+
|
|
8
|
+
// src/lib/utils.ts
|
|
9
|
+
function getGridCellReference(x, y, gridSize = 100) {
|
|
10
|
+
const col = Math.floor(x / gridSize);
|
|
11
|
+
const row = Math.floor(y / gridSize);
|
|
12
|
+
const colLabel = String.fromCharCode(65 + col);
|
|
13
|
+
return `${colLabel}${row}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/server/gemini-cli.ts
|
|
17
|
+
var annotationSnapshots = /* @__PURE__ */ new Map();
|
|
18
|
+
function createSnapshot(annotationId, cwd) {
|
|
19
|
+
try {
|
|
20
|
+
const stashRef = child_process.execSync("git stash create", { cwd, encoding: "utf-8" }).trim();
|
|
21
|
+
if (stashRef) {
|
|
22
|
+
annotationSnapshots.set(annotationId, stashRef);
|
|
23
|
+
console.log(`[Skema] Created snapshot ${stashRef.slice(0, 7)} for annotation ${annotationId}`);
|
|
24
|
+
return stashRef;
|
|
25
|
+
}
|
|
26
|
+
const headRef = child_process.execSync("git rev-parse HEAD", { cwd, encoding: "utf-8" }).trim();
|
|
27
|
+
annotationSnapshots.set(annotationId, headRef);
|
|
28
|
+
console.log(`[Skema] Using HEAD ${headRef.slice(0, 7)} for annotation ${annotationId}`);
|
|
29
|
+
return headRef;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error("[Skema] Failed to create snapshot:", error);
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function revertAnnotation(annotationId, cwd = process.cwd()) {
|
|
36
|
+
const snapshotRef = annotationSnapshots.get(annotationId);
|
|
37
|
+
if (!snapshotRef) {
|
|
38
|
+
return { success: false, message: `No snapshot found for annotation ${annotationId}` };
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const changedFiles = child_process.execSync(`git diff --name-only ${snapshotRef}`, { cwd, encoding: "utf-8" }).trim().split("\n").filter(Boolean);
|
|
42
|
+
if (changedFiles.length === 0) {
|
|
43
|
+
annotationSnapshots.delete(annotationId);
|
|
44
|
+
return { success: true, message: "No changes to revert" };
|
|
45
|
+
}
|
|
46
|
+
for (const file of changedFiles) {
|
|
47
|
+
try {
|
|
48
|
+
child_process.execSync(`git checkout ${snapshotRef} -- "${file}"`, { cwd, encoding: "utf-8" });
|
|
49
|
+
console.log(`[Skema] Reverted: ${file}`);
|
|
50
|
+
} catch {
|
|
51
|
+
try {
|
|
52
|
+
child_process.execSync(`git checkout HEAD -- "${file}"`, { cwd, encoding: "utf-8" });
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
annotationSnapshots.delete(annotationId);
|
|
58
|
+
console.log(`[Skema] Reverted ${changedFiles.length} file(s) for annotation ${annotationId}`);
|
|
59
|
+
return { success: true, message: `Reverted ${changedFiles.length} file(s)` };
|
|
60
|
+
} catch (error) {
|
|
61
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
62
|
+
console.error("[Skema] Failed to revert:", message);
|
|
63
|
+
return { success: false, message };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function getTrackedAnnotations() {
|
|
67
|
+
return Array.from(annotationSnapshots.keys());
|
|
68
|
+
}
|
|
69
|
+
function buildPromptFromAnnotation(annotation, projectContext, options) {
|
|
70
|
+
const fastMode = options?.fastMode ?? true;
|
|
71
|
+
if (annotation.type === "drawing") {
|
|
72
|
+
return buildDrawingPrompt(annotation, projectContext, options?.visionDescription);
|
|
73
|
+
}
|
|
74
|
+
if (fastMode) {
|
|
75
|
+
const selector = annotation.selector || "";
|
|
76
|
+
const text = annotation.text || "";
|
|
77
|
+
const tag = annotation.tagName?.toLowerCase() || "";
|
|
78
|
+
let target = "";
|
|
79
|
+
if (text) {
|
|
80
|
+
target = `"${text.slice(0, 50)}"`;
|
|
81
|
+
} else if (selector) {
|
|
82
|
+
target = `\`${selector}\``;
|
|
83
|
+
} else if (tag) {
|
|
84
|
+
target = `<${tag}>`;
|
|
85
|
+
}
|
|
86
|
+
return `${annotation.comment}${target ? ` (target: ${target})` : ""}. Make the change directly, no explanation needed. IMPORTANT: Do NOT modify the import { SkemaWrapper } from "@/components/skema-wrapper" line.`;
|
|
87
|
+
}
|
|
88
|
+
let prompt = `Make this code change: "${annotation.comment || "No specific comment provided"}"
|
|
89
|
+
|
|
90
|
+
Element: `;
|
|
91
|
+
if (annotation.type === "dom_selection") {
|
|
92
|
+
const domAnnotation = annotation;
|
|
93
|
+
prompt += `<${domAnnotation.tagName?.toLowerCase() || "unknown"}>`;
|
|
94
|
+
if (domAnnotation.selector) prompt += ` | selector: ${domAnnotation.selector}`;
|
|
95
|
+
if (domAnnotation.text) prompt += ` | text: "${domAnnotation.text.slice(0, 100)}"`;
|
|
96
|
+
if (domAnnotation.elements && domAnnotation.elements.length > 1) {
|
|
97
|
+
prompt += `
|
|
98
|
+
${domAnnotation.elements.length} elements selected`;
|
|
99
|
+
}
|
|
100
|
+
} else if (annotation.type === "gesture") {
|
|
101
|
+
const gestureAnnotation = annotation;
|
|
102
|
+
prompt += `gesture: ${gestureAnnotation.gesture || "unknown"} at (${gestureAnnotation.boundingBox?.x}, ${gestureAnnotation.boundingBox?.y})`;
|
|
103
|
+
} else {
|
|
104
|
+
prompt += `annotation at (${annotation.boundingBox?.x}, ${annotation.boundingBox?.y})`;
|
|
105
|
+
}
|
|
106
|
+
prompt += `
|
|
107
|
+
|
|
108
|
+
Make minimal changes. IMPORTANT: Do NOT modify the import { SkemaWrapper } from "@/components/skema-wrapper" line.`;
|
|
109
|
+
return prompt;
|
|
110
|
+
}
|
|
111
|
+
function buildDrawingPrompt(annotation, projectContext, visionDescription) {
|
|
112
|
+
const drawingAnnotation = annotation;
|
|
113
|
+
const bbox = drawingAnnotation.boundingBox;
|
|
114
|
+
const nearbyElements = drawingAnnotation.nearbyElements || [];
|
|
115
|
+
const extractedText = drawingAnnotation.extractedText;
|
|
116
|
+
const comment = drawingAnnotation.comment || "Create a component based on this drawing";
|
|
117
|
+
const hasImage = !!drawingAnnotation.drawingImage;
|
|
118
|
+
const gridSize = drawingAnnotation.gridConfig?.size || 100;
|
|
119
|
+
let gridCellRef = "";
|
|
120
|
+
if (bbox) {
|
|
121
|
+
gridCellRef = getGridCellReference(bbox.x, bbox.y, gridSize);
|
|
122
|
+
}
|
|
123
|
+
let positionContext = "";
|
|
124
|
+
if (bbox && drawingAnnotation.viewport) {
|
|
125
|
+
const vp = drawingAnnotation.viewport;
|
|
126
|
+
const relX = (bbox.x / vp.width * 100).toFixed(1);
|
|
127
|
+
const relY = (bbox.y / vp.height * 100).toFixed(1);
|
|
128
|
+
positionContext = `**Drawing Location:** Approximately ${relX}% from left, ${relY}% from top of viewport`;
|
|
129
|
+
if (gridCellRef) {
|
|
130
|
+
positionContext += ` (grid cell ${gridCellRef})`;
|
|
131
|
+
}
|
|
132
|
+
} else if (bbox) {
|
|
133
|
+
positionContext = `**Drawing Area:** ${Math.round(bbox.width)}\xD7${Math.round(bbox.height)}px`;
|
|
134
|
+
}
|
|
135
|
+
let nearbyContext = "";
|
|
136
|
+
if (nearbyElements.length > 0) {
|
|
137
|
+
const elementList = nearbyElements.slice(0, 5).map((el) => {
|
|
138
|
+
let desc = `- <${el.tagName.toLowerCase()}>`;
|
|
139
|
+
if (el.text) desc += `: "${el.text.slice(0, 50)}"`;
|
|
140
|
+
if (el.className) desc += ` (class: ${el.className.slice(0, 50)})`;
|
|
141
|
+
desc += ` (${el.selector})`;
|
|
142
|
+
return desc;
|
|
143
|
+
}).join("\n");
|
|
144
|
+
nearbyContext = `
|
|
145
|
+
**Nearby DOM Elements (for placement reference):**
|
|
146
|
+
${elementList}`;
|
|
147
|
+
}
|
|
148
|
+
let textContext = "";
|
|
149
|
+
if (extractedText && extractedText.trim()) {
|
|
150
|
+
textContext = `
|
|
151
|
+
**Text found in drawing (use as reference if hard to read):**
|
|
152
|
+
${extractedText}`;
|
|
153
|
+
}
|
|
154
|
+
let imageNote = hasImage ? "\n**[Drawing image provided as base64 PNG with labeled grid overlay]**" : "";
|
|
155
|
+
if (visionDescription) {
|
|
156
|
+
imageNote += `
|
|
157
|
+
|
|
158
|
+
## Visual Analysis of Drawing
|
|
159
|
+
${visionDescription}`;
|
|
160
|
+
}
|
|
161
|
+
const prompt = `Your task is to interpret a user's sketch/wireframe and turn it into code that is integrated in the codebase.
|
|
162
|
+
|
|
163
|
+
## User's Request
|
|
164
|
+
"${comment}"
|
|
165
|
+
|
|
166
|
+
## Drawing Context
|
|
167
|
+
${positionContext}${textContext}${nearbyContext}${imageNote}
|
|
168
|
+
|
|
169
|
+
## Your Process
|
|
170
|
+
1. **Analyze the Sketch:** Understand the visual intent\u2014what UI component does the user want?
|
|
171
|
+
2. **Interpret, Don't Transcribe:** Elevate the low-fidelity drawing into a high-fidelity component. Choose appropriate spacing, colors, and typography that match modern design standards.
|
|
172
|
+
3. **Infer Missing Details:** If something is underspecified, use your expertise to make the best choice. An informed decision is better than an incomplete component.
|
|
173
|
+
|
|
174
|
+
## Implementation Guidelines
|
|
175
|
+
- Create a React component with inline styles or Tailwind CSS classes
|
|
176
|
+
- Do NOT use hardcoded pixel positions or absolute coordinates - integrate naturally with existing page flow
|
|
177
|
+
- Use flexbox, grid, or relative positioning to place the component appropriately
|
|
178
|
+
- If the sketch shows:
|
|
179
|
+
- **Rectangle/box:** Card, container, button, or input field depending on context
|
|
180
|
+
- **Text elements:** Headings, paragraphs, or labels with appropriate hierarchy
|
|
181
|
+
- **Form layout:** Input fields with labels, proper spacing
|
|
182
|
+
- **Icons/shapes:** Use appropriate icons from lucide-react or inline SVGs
|
|
183
|
+
- **Navigation:** Nav links, menus, or breadcrumbs
|
|
184
|
+
- **Lists:** Ordered/unordered lists or grid layouts
|
|
185
|
+
- Make the component fit naturally with the existing page design
|
|
186
|
+
- Style it nicely and according to the existing codebase
|
|
187
|
+
- Use semantic HTML and ARIA attributes where appropriate
|
|
188
|
+
- The component should integrate well with the existing codebase structure
|
|
189
|
+
|
|
190
|
+
## Annotations
|
|
191
|
+
- Any **red marks** in the drawing are instructions\u2014follow them but don't render them
|
|
192
|
+
- Text annotations describe intent or constraints
|
|
193
|
+
|
|
194
|
+
Make the changes directly. Insert the component at the appropriate location in the page. No explanation needed.
|
|
195
|
+
|
|
196
|
+
IMPORTANT: Do NOT modify the import { SkemaWrapper } from "@/components/skema-wrapper" line or the SkemaWrapper component itself.`;
|
|
197
|
+
return prompt;
|
|
198
|
+
}
|
|
199
|
+
function spawnGeminiCLI(prompt, options = {}) {
|
|
200
|
+
const {
|
|
201
|
+
cwd = process.cwd(),
|
|
202
|
+
apiKey = process.env.GEMINI_API_KEY,
|
|
203
|
+
yolo = true,
|
|
204
|
+
outputFormat = "stream-json",
|
|
205
|
+
model = "gemini-2.5-flash"
|
|
206
|
+
} = options;
|
|
207
|
+
const args = ["-p", prompt];
|
|
208
|
+
if (yolo) {
|
|
209
|
+
args.push("--yolo");
|
|
210
|
+
}
|
|
211
|
+
args.push("--output-format", outputFormat);
|
|
212
|
+
args.push("-m", model);
|
|
213
|
+
const gemini = child_process.spawn("gemini", args, {
|
|
214
|
+
cwd,
|
|
215
|
+
env: {
|
|
216
|
+
...process.env,
|
|
217
|
+
...apiKey ? { GEMINI_API_KEY: apiKey } : {}
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
const events = {
|
|
221
|
+
[Symbol.asyncIterator]() {
|
|
222
|
+
let buffer = "";
|
|
223
|
+
let done = false;
|
|
224
|
+
const queue = [];
|
|
225
|
+
let resolveNext = null;
|
|
226
|
+
const pushEvent = (event) => {
|
|
227
|
+
if (resolveNext) {
|
|
228
|
+
resolveNext({ value: event, done: false });
|
|
229
|
+
resolveNext = null;
|
|
230
|
+
} else {
|
|
231
|
+
queue.push(event);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
gemini.stdout.on("data", (data) => {
|
|
235
|
+
buffer += data.toString();
|
|
236
|
+
const lines = buffer.split("\n");
|
|
237
|
+
buffer = lines.pop() || "";
|
|
238
|
+
for (const line of lines) {
|
|
239
|
+
if (line.trim()) {
|
|
240
|
+
try {
|
|
241
|
+
const event = JSON.parse(line);
|
|
242
|
+
pushEvent(event);
|
|
243
|
+
} catch {
|
|
244
|
+
pushEvent({ type: "message", content: line });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
gemini.stderr.on("data", (data) => {
|
|
250
|
+
pushEvent({ type: "error", content: data.toString() });
|
|
251
|
+
});
|
|
252
|
+
gemini.on("close", (code) => {
|
|
253
|
+
if (buffer.trim()) {
|
|
254
|
+
try {
|
|
255
|
+
const event = JSON.parse(buffer);
|
|
256
|
+
pushEvent(event);
|
|
257
|
+
} catch {
|
|
258
|
+
pushEvent({ type: "message", content: buffer });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
pushEvent({ type: "done", code: code ?? 0 });
|
|
262
|
+
done = true;
|
|
263
|
+
if (resolveNext) {
|
|
264
|
+
resolveNext({ value: void 0, done: true });
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
gemini.on("error", (err) => {
|
|
268
|
+
pushEvent({ type: "error", content: err.message });
|
|
269
|
+
done = true;
|
|
270
|
+
if (resolveNext) {
|
|
271
|
+
resolveNext({ value: void 0, done: true });
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
return {
|
|
275
|
+
next() {
|
|
276
|
+
if (queue.length > 0) {
|
|
277
|
+
return Promise.resolve({ value: queue.shift(), done: false });
|
|
278
|
+
}
|
|
279
|
+
if (done) {
|
|
280
|
+
return Promise.resolve({ value: void 0, done: true });
|
|
281
|
+
}
|
|
282
|
+
return new Promise((resolve) => {
|
|
283
|
+
resolveNext = resolve;
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
return { process: gemini, events };
|
|
290
|
+
}
|
|
291
|
+
async function analyzeImageWithGemini(apiKey, base64Image, modelName = "gemini-2.5-flash") {
|
|
292
|
+
try {
|
|
293
|
+
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
294
|
+
const model = genAI.getGenerativeModel({ model: modelName });
|
|
295
|
+
const imageParts = [
|
|
296
|
+
{
|
|
297
|
+
inlineData: {
|
|
298
|
+
data: base64Image.replace(/^data:image\/\w+;base64,/, ""),
|
|
299
|
+
mimeType: "image/png"
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
];
|
|
303
|
+
const imageAnalysisPrompt = `
|
|
304
|
+
Analyze this UI wireframe sketch in extreme detail for a front-end developer.
|
|
305
|
+
|
|
306
|
+
Describe every element, layout, spacing, icons, and text you see.
|
|
307
|
+
Mention relative positions and hierarchy.
|
|
308
|
+
Be distinct about what is drawn vs what might be background.
|
|
309
|
+
|
|
310
|
+
Do NOT focus on exact pixel coordinates or absolute positions - describe layouts
|
|
311
|
+
in terms of relative positioning (left/right/top/bottom, centered, evenly spaced, etc.).
|
|
312
|
+
|
|
313
|
+
It is expected that they will be rough draft's / hand-drawn things. Interpret the drawing and its goals as best as you can.
|
|
314
|
+
DO NOT MENTION THAT THINGS HAVE "Hand-sketched" or "Hand-drawn" vibes. Make assumptions of what they were trying to do.
|
|
315
|
+
Just FYI, this gets passed onto a generator to generate the actual code of modern UI componenents.
|
|
316
|
+
`.trim();
|
|
317
|
+
const result = await model.generateContent([
|
|
318
|
+
imageAnalysisPrompt,
|
|
319
|
+
...imageParts
|
|
320
|
+
]);
|
|
321
|
+
const response = await result.response;
|
|
322
|
+
const text = response.text();
|
|
323
|
+
return text;
|
|
324
|
+
} catch (error) {
|
|
325
|
+
console.error("Failed to analyze image with Gemini Vision:", error);
|
|
326
|
+
return `[Extension Error] Failed to analyze drawing: ${error instanceof Error ? error.message : String(error)}`;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function createGeminiCLIStream(annotation, projectContext, options) {
|
|
330
|
+
const encoder = new TextEncoder();
|
|
331
|
+
return new ReadableStream({
|
|
332
|
+
async start(controller) {
|
|
333
|
+
const apiKey = options?.apiKey || process.env.GEMINI_API_KEY;
|
|
334
|
+
let visionDescription = "";
|
|
335
|
+
if (annotation.type === "drawing" && annotation.drawingImage && apiKey) {
|
|
336
|
+
const progressEvent = {
|
|
337
|
+
type: "message",
|
|
338
|
+
role: "assistant",
|
|
339
|
+
content: "\u{1F3A8} Analyzing drawing image with Gemini Vision...",
|
|
340
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
341
|
+
};
|
|
342
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(progressEvent)}
|
|
343
|
+
|
|
344
|
+
`));
|
|
345
|
+
visionDescription = await analyzeImageWithGemini(
|
|
346
|
+
apiKey,
|
|
347
|
+
annotation.drawingImage,
|
|
348
|
+
options?.model || "gemini-2.5-flash"
|
|
349
|
+
);
|
|
350
|
+
const analysisEvent = {
|
|
351
|
+
type: "message",
|
|
352
|
+
role: "assistant",
|
|
353
|
+
content: `\u{1F441}\uFE0F Visual Analysis:
|
|
354
|
+
${visionDescription}`,
|
|
355
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
356
|
+
};
|
|
357
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(analysisEvent)}
|
|
358
|
+
|
|
359
|
+
`));
|
|
360
|
+
}
|
|
361
|
+
const prompt = buildPromptFromAnnotation(
|
|
362
|
+
annotation,
|
|
363
|
+
projectContext,
|
|
364
|
+
{
|
|
365
|
+
fastMode: options?.fastMode ?? false,
|
|
366
|
+
visionDescription
|
|
367
|
+
}
|
|
368
|
+
);
|
|
369
|
+
const promptEvent = {
|
|
370
|
+
type: "debug",
|
|
371
|
+
content: prompt,
|
|
372
|
+
label: "GEMINI CLI PROMPT",
|
|
373
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
374
|
+
};
|
|
375
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(promptEvent)}
|
|
376
|
+
|
|
377
|
+
`));
|
|
378
|
+
const { events } = spawnGeminiCLI(prompt, options);
|
|
379
|
+
for await (const event of events) {
|
|
380
|
+
const sseData = `data: ${JSON.stringify(event)}
|
|
381
|
+
|
|
382
|
+
`;
|
|
383
|
+
controller.enqueue(encoder.encode(sseData));
|
|
384
|
+
if (event.type === "done") {
|
|
385
|
+
controller.close();
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
async function runGeminiCLI(annotation, projectContext, options) {
|
|
393
|
+
const apiKey = options?.apiKey || process.env.GEMINI_API_KEY;
|
|
394
|
+
let visionDescription = "";
|
|
395
|
+
if (annotation.type === "drawing" && annotation.drawingImage && apiKey) {
|
|
396
|
+
visionDescription = await analyzeImageWithGemini(
|
|
397
|
+
apiKey,
|
|
398
|
+
annotation.drawingImage,
|
|
399
|
+
options?.model || "gemini-2.5-flash"
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
const prompt = buildPromptFromAnnotation(
|
|
403
|
+
annotation,
|
|
404
|
+
projectContext,
|
|
405
|
+
{
|
|
406
|
+
fastMode: options?.fastMode ?? false,
|
|
407
|
+
visionDescription
|
|
408
|
+
}
|
|
409
|
+
);
|
|
410
|
+
const { events: eventIterator } = spawnGeminiCLI(prompt, options);
|
|
411
|
+
const events = [];
|
|
412
|
+
let response = "";
|
|
413
|
+
let success = true;
|
|
414
|
+
for await (const event of eventIterator) {
|
|
415
|
+
events.push(event);
|
|
416
|
+
if (event.type === "message" && event.role === "assistant" && event.content) {
|
|
417
|
+
response += event.content;
|
|
418
|
+
}
|
|
419
|
+
if (event.type === "done" && event.code !== 0) {
|
|
420
|
+
success = false;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return { success, response, events };
|
|
424
|
+
}
|
|
425
|
+
function createGeminiRouteHandler(defaultOptions) {
|
|
426
|
+
return async function POST2(request) {
|
|
427
|
+
const { annotation, projectContext } = await request.json();
|
|
428
|
+
const cwd = defaultOptions?.cwd ?? process.cwd();
|
|
429
|
+
const annotationId = annotation.id || `temp-${Date.now()}`;
|
|
430
|
+
createSnapshot(annotationId, cwd);
|
|
431
|
+
const stream = createGeminiCLIStream(annotation, projectContext, {
|
|
432
|
+
cwd,
|
|
433
|
+
...defaultOptions
|
|
434
|
+
});
|
|
435
|
+
return new Response(stream, {
|
|
436
|
+
headers: {
|
|
437
|
+
"Content-Type": "text/event-stream",
|
|
438
|
+
"Cache-Control": "no-cache",
|
|
439
|
+
"Connection": "keep-alive",
|
|
440
|
+
"X-Annotation-Id": annotationId
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function createRevertRouteHandler(defaultOptions) {
|
|
446
|
+
return async function DELETE2(request) {
|
|
447
|
+
const { annotationId } = await request.json();
|
|
448
|
+
const cwd = defaultOptions?.cwd ?? process.cwd();
|
|
449
|
+
if (!annotationId) {
|
|
450
|
+
return new Response(JSON.stringify({ success: false, message: "Missing annotationId" }), {
|
|
451
|
+
status: 400,
|
|
452
|
+
headers: { "Content-Type": "application/json" }
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
const result = revertAnnotation(annotationId, cwd);
|
|
456
|
+
return new Response(JSON.stringify(result), {
|
|
457
|
+
status: result.success ? 200 : 400,
|
|
458
|
+
headers: { "Content-Type": "application/json" }
|
|
459
|
+
});
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
var POST = createGeminiRouteHandler();
|
|
463
|
+
var DELETE = createRevertRouteHandler();
|
|
464
|
+
|
|
465
|
+
exports.DELETE = DELETE;
|
|
466
|
+
exports.POST = POST;
|
|
467
|
+
exports.buildPromptFromAnnotation = buildPromptFromAnnotation;
|
|
468
|
+
exports.createGeminiCLIStream = createGeminiCLIStream;
|
|
469
|
+
exports.createGeminiRouteHandler = createGeminiRouteHandler;
|
|
470
|
+
exports.createRevertRouteHandler = createRevertRouteHandler;
|
|
471
|
+
exports.getTrackedAnnotations = getTrackedAnnotations;
|
|
472
|
+
exports.revertAnnotation = revertAnnotation;
|
|
473
|
+
exports.runGeminiCLI = runGeminiCLI;
|
|
474
|
+
exports.spawnGeminiCLI = spawnGeminiCLI;
|
|
475
|
+
//# sourceMappingURL=server.js.map
|
|
476
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/utils.ts","../src/server/gemini-cli.ts"],"names":["execSync","spawn","GoogleGenerativeAI","POST","DELETE"],"mappings":"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","file":"server.js","sourcesContent":["// =============================================================================\n// Skema Utility Functions\n// =============================================================================\n\n/**\n * Convert a Blob to a base64 string\n */\nexport async function blobToBase64(blob: Blob): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => {\n const result = reader.result as string;\n resolve(result);\n };\n reader.onerror = () => reject(reader.error);\n reader.readAsDataURL(blob);\n });\n}\n\n/**\n * Add a labeled grid overlay to an SVG string\n * Grid uses A/B/C column labels and 0/1/2 row numbers for positioning reference\n * @param svgString - The SVG markup to add grid to\n * @param opts - Grid options (color, cell size, whether to show labels)\n * @returns SVG string with grid overlay added\n */\nexport function addGridToSvg(\n svgString: string,\n opts: { color?: string; size?: number; labels?: boolean } = {}\n): string {\n const { color = '#0066FF', size = 100, labels = true } = opts;\n\n // Parse SVG to get dimensions\n const viewBoxMatch = svgString.match(/viewBox=\"([^\"]+)\"/);\n if (!viewBoxMatch) return svgString;\n\n const [x, y, w, h] = viewBoxMatch[1].split(' ').map(Number);\n\n // Build grid lines and labels\n const gridElements: string[] = [];\n\n // Vertical lines\n for (let i = 0; i <= Math.ceil(w / size); i++) {\n const xPos = i * size;\n if (i > 0) {\n gridElements.push(\n `<line x1=\"${xPos}\" y1=\"0\" x2=\"${xPos}\" y2=\"${h}\" stroke=\"${color}\" stroke-width=\"1\" stroke-opacity=\"0.5\"/>`\n );\n }\n if (labels) {\n // Column labels (A, B, C, ...)\n const colLabel = String.fromCharCode(65 + i); // 65 = 'A'\n gridElements.push(\n `<text x=\"${xPos + size / 2}\" y=\"16\" fill=\"${color}\" font-size=\"12\" font-family=\"sans-serif\" text-anchor=\"middle\">${colLabel}</text>`\n );\n }\n }\n\n // Horizontal lines\n for (let i = 0; i <= Math.ceil(h / size); i++) {\n const yPos = i * size;\n gridElements.push(\n `<line x1=\"0\" y1=\"${yPos}\" x2=\"${w}\" y2=\"${yPos}\" stroke=\"${color}\" stroke-width=\"1\" stroke-opacity=\"0.5\"/>`\n );\n if (labels && i < Math.ceil(h / size)) {\n // Row labels (0, 1, 2, ...)\n gridElements.push(\n `<text x=\"8\" y=\"${yPos + size / 2 + 4}\" fill=\"${color}\" font-size=\"12\" font-family=\"sans-serif\">${i}</text>`\n );\n }\n }\n\n // Create grid group\n const gridGroup = `<g id=\"skema-grid\" transform=\"translate(${x}, ${y})\">${gridElements.join('')}</g>`;\n\n // Insert grid before closing </svg> tag\n return svgString.replace('</svg>', `${gridGroup}</svg>`);\n}\n\n/**\n * Get the grid cell reference (e.g., \"B2\") for a given position\n * @param x - X coordinate in pixels\n * @param y - Y coordinate in pixels\n * @param gridSize - Size of each grid cell (default 100px)\n * @returns Grid cell reference string (e.g., \"B2\")\n */\nexport function getGridCellReference(x: number, y: number, gridSize: number = 100): string {\n const col = Math.floor(x / gridSize);\n const row = Math.floor(y / gridSize);\n const colLabel = String.fromCharCode(65 + col); // 65 = 'A'\n return `${colLabel}${row}`;\n}\n\n/**\n * Extract text content from tldraw shapes\n * @param shapes - Array of tldraw shapes\n * @returns Combined text content from text and note shapes\n */\nexport function extractTextFromShapes(shapes: unknown[]): string {\n const textContent: string[] = [];\n\n for (const shape of shapes) {\n const s = shape as { type?: string; props?: { text?: string; richText?: unknown } };\n\n if (s.type === 'text' || s.type === 'note') {\n // Handle plain text\n if (s.props?.text) {\n textContent.push(s.props.text);\n }\n // Handle rich text (newer tldraw versions)\n if (s.props?.richText && typeof s.props.richText === 'object') {\n const rt = s.props.richText as { content?: Array<{ content?: Array<{ text?: string }> }> };\n if (rt.content) {\n for (const block of rt.content) {\n if (block.content) {\n for (const inline of block.content) {\n if (inline.text) {\n textContent.push(inline.text);\n }\n }\n }\n }\n }\n }\n }\n }\n\n return textContent.filter(Boolean).join('\\n');\n}\n","import { spawn, execSync, type ChildProcess } from 'child_process';\nimport { GoogleGenerativeAI } from '@google/generative-ai';\nimport type { Annotation, NearbyElement, ProjectStyleContext, ViewportInfo } from '../types';\nimport { getGridCellReference } from '../lib/utils';\n\n// Store annotation ID -> git stash ref for undo functionality\nconst annotationSnapshots = new Map<string, string>();\n\n/**\n * Create a git snapshot before making changes (for undo support)\n */\nfunction createSnapshot(annotationId: string, cwd: string): string | null {\n try {\n // Create a stash-like commit object without actually stashing\n // This captures the current working directory state\n const stashRef = execSync('git stash create', { cwd, encoding: 'utf-8' }).trim();\n\n if (stashRef) {\n annotationSnapshots.set(annotationId, stashRef);\n console.log(`[Skema] Created snapshot ${stashRef.slice(0, 7)} for annotation ${annotationId}`);\n return stashRef;\n }\n\n // If stash create returns empty, there are no changes to snapshot\n // Store current HEAD instead\n const headRef = execSync('git rev-parse HEAD', { cwd, encoding: 'utf-8' }).trim();\n annotationSnapshots.set(annotationId, headRef);\n console.log(`[Skema] Using HEAD ${headRef.slice(0, 7)} for annotation ${annotationId}`);\n return headRef;\n } catch (error) {\n console.error('[Skema] Failed to create snapshot:', error);\n return null;\n }\n}\n\n/**\n * Revert changes for an annotation by restoring from snapshot\n */\nexport function revertAnnotation(annotationId: string, cwd: string = process.cwd()): { success: boolean; message: string } {\n const snapshotRef = annotationSnapshots.get(annotationId);\n\n if (!snapshotRef) {\n return { success: false, message: `No snapshot found for annotation ${annotationId}` };\n }\n\n try {\n // Get list of modified files since the snapshot\n const changedFiles = execSync(`git diff --name-only ${snapshotRef}`, { cwd, encoding: 'utf-8' })\n .trim()\n .split('\\n')\n .filter(Boolean);\n\n if (changedFiles.length === 0) {\n annotationSnapshots.delete(annotationId);\n return { success: true, message: 'No changes to revert' };\n }\n\n // Restore each changed file from the snapshot\n for (const file of changedFiles) {\n try {\n execSync(`git checkout ${snapshotRef} -- \"${file}\"`, { cwd, encoding: 'utf-8' });\n console.log(`[Skema] Reverted: ${file}`);\n } catch {\n // File might not exist in snapshot (new file), so delete it\n try {\n execSync(`git checkout HEAD -- \"${file}\"`, { cwd, encoding: 'utf-8' });\n } catch {\n // Ignore if file doesn't exist\n }\n }\n }\n\n annotationSnapshots.delete(annotationId);\n console.log(`[Skema] Reverted ${changedFiles.length} file(s) for annotation ${annotationId}`);\n return { success: true, message: `Reverted ${changedFiles.length} file(s)` };\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n console.error('[Skema] Failed to revert:', message);\n return { success: false, message };\n }\n}\n\n/**\n * Get all tracked annotation IDs\n */\nexport function getTrackedAnnotations(): string[] {\n return Array.from(annotationSnapshots.keys());\n}\n\nexport interface GeminiCLIOptions {\n /** Working directory for Gemini CLI */\n cwd?: string;\n /** API key (defaults to GEMINI_API_KEY env var) */\n apiKey?: string;\n /** Auto-approve all tool calls (default: true) */\n yolo?: boolean;\n /** Output format (default: 'stream-json') */\n outputFormat?: 'text' | 'json' | 'stream-json';\n /** Model to use (default: 'gemini-2.5-flash' for speed) */\n model?: string;\n /** Use minimal/fast prompt (default: true) */\n fastMode?: boolean;\n}\n\nexport interface ProjectContext {\n pathname?: string;\n viewport?: { width: number; height: number };\n}\n\nexport interface GeminiCLIEvent {\n type: 'init' | 'message' | 'tool_use' | 'tool_result' | 'error' | 'result' | 'done' | 'debug';\n timestamp?: string;\n content?: string;\n role?: 'user' | 'assistant';\n tool_name?: string;\n tool_id?: string;\n status?: 'success' | 'error';\n code?: number;\n [key: string]: unknown;\n}\n\n/**\n * Build a prompt for Gemini CLI from an annotation\n */\nexport function buildPromptFromAnnotation(\n annotation: Partial<Annotation> & { comment?: string },\n projectContext?: ProjectContext,\n options?: { fastMode?: boolean; visionDescription?: string }\n): string {\n const fastMode = options?.fastMode ?? true;\n\n // Handle drawing annotations specially - they generate new components\n if (annotation.type === 'drawing') {\n return buildDrawingPrompt(annotation, projectContext, options?.visionDescription);\n }\n\n // Fast mode: minimal prompt for quick changes\n if (fastMode) {\n const selector = (annotation as { selector?: string }).selector || '';\n const text = (annotation as { text?: string }).text || '';\n const tag = (annotation as { tagName?: string }).tagName?.toLowerCase() || '';\n\n // Build a very concise prompt\n let target = '';\n if (text) {\n target = `\"${text.slice(0, 50)}\"`;\n } else if (selector) {\n target = `\\`${selector}\\``;\n } else if (tag) {\n target = `<${tag}>`;\n }\n\n return `${annotation.comment}${target ? ` (target: ${target})` : ''}. Make the change directly, no explanation needed. IMPORTANT: Do NOT modify the import { SkemaWrapper } from \"@/components/skema-wrapper\" line.`;\n }\n\n // Detailed mode: full context\n let prompt = `Make this code change: \"${annotation.comment || 'No specific comment provided'}\"\n\nElement: `;\n\n if (annotation.type === 'dom_selection') {\n const domAnnotation = annotation as {\n tagName?: string;\n selector?: string;\n elementPath?: string;\n text?: string;\n cssClasses?: string;\n attributes?: Record<string, string>;\n elements?: Array<{\n tagName: string;\n selector: string;\n elementPath: string;\n text?: string;\n }>;\n };\n\n prompt += `<${domAnnotation.tagName?.toLowerCase() || 'unknown'}>`;\n if (domAnnotation.selector) prompt += ` | selector: ${domAnnotation.selector}`;\n if (domAnnotation.text) prompt += ` | text: \"${domAnnotation.text.slice(0, 100)}\"`;\n\n if (domAnnotation.elements && domAnnotation.elements.length > 1) {\n prompt += `\\n${domAnnotation.elements.length} elements selected`;\n }\n } else if (annotation.type === 'gesture') {\n const gestureAnnotation = annotation as { gesture?: string; boundingBox?: { x: number; y: number } };\n prompt += `gesture: ${gestureAnnotation.gesture || 'unknown'} at (${gestureAnnotation.boundingBox?.x}, ${gestureAnnotation.boundingBox?.y})`;\n } else {\n // Fallback for any other annotation type\n prompt += `annotation at (${annotation.boundingBox?.x}, ${annotation.boundingBox?.y})`;\n }\n\n prompt += `\\n\\nMake minimal changes. IMPORTANT: Do NOT modify the import { SkemaWrapper } from \"@/components/skema-wrapper\" line.`;\n\n return prompt;\n}\n\n/**\n * Build a specialized prompt for drawing annotations to generate React components\n */\nfunction buildDrawingPrompt(\n annotation: Partial<Annotation> & { comment?: string },\n projectContext?: ProjectContext,\n visionDescription?: string\n): string {\n const drawingAnnotation = annotation as {\n boundingBox?: { x: number; y: number; width: number; height: number };\n drawingSvg?: string;\n drawingImage?: string;\n extractedText?: string;\n gridConfig?: { color: string; size: number; labels: boolean };\n viewport?: ViewportInfo;\n projectStyles?: ProjectStyleContext;\n nearbyElements?: NearbyElement[];\n comment?: string;\n };\n\n const bbox = drawingAnnotation.boundingBox;\n const nearbyElements = drawingAnnotation.nearbyElements || [];\n const extractedText = drawingAnnotation.extractedText;\n const comment = drawingAnnotation.comment || 'Create a component based on this drawing';\n const hasImage = !!drawingAnnotation.drawingImage;\n const gridSize = drawingAnnotation.gridConfig?.size || 100;\n\n // Build grid cell reference for positioning\n let gridCellRef = '';\n if (bbox) {\n gridCellRef = getGridCellReference(bbox.x, bbox.y, gridSize);\n }\n\n // Build position context - focus on relative positioning, not absolute values\n let positionContext = '';\n if (bbox && drawingAnnotation.viewport) {\n const vp = drawingAnnotation.viewport;\n const relX = ((bbox.x / vp.width) * 100).toFixed(1);\n const relY = ((bbox.y / vp.height) * 100).toFixed(1);\n positionContext = `**Drawing Location:** Approximately ${relX}% from left, ${relY}% from top of viewport`;\n if (gridCellRef) {\n positionContext += ` (grid cell ${gridCellRef})`;\n }\n } else if (bbox) {\n positionContext = `**Drawing Area:** ${Math.round(bbox.width)}×${Math.round(bbox.height)}px`;\n }\n\n // Build nearby elements context with style info\n let nearbyContext = '';\n if (nearbyElements.length > 0) {\n const elementList = nearbyElements\n .slice(0, 5)\n .map(el => {\n let desc = `- <${el.tagName.toLowerCase()}>`;\n if (el.text) desc += `: \"${el.text.slice(0, 50)}\"`;\n if (el.className) desc += ` (class: ${el.className.slice(0, 50)})`;\n desc += ` (${el.selector})`;\n return desc;\n })\n .join('\\n');\n nearbyContext = `\\n**Nearby DOM Elements (for placement reference):**\\n${elementList}`;\n }\n\n // Build text extraction context\n let textContext = '';\n if (extractedText && extractedText.trim()) {\n textContext = `\\n**Text found in drawing (use as reference if hard to read):**\\n${extractedText}`;\n }\n\n // Image reference note\n let imageNote = hasImage\n ? '\\n**[Drawing image provided as base64 PNG with labeled grid overlay]**'\n : '';\n\n if (visionDescription) {\n imageNote += `\\n\\n## Visual Analysis of Drawing\\n${visionDescription}`;\n }\n\n // Construct the comprehensive prompt - Principal Front-End Engineer persona\n // IMPORTANT: Do NOT focus on pixel coordinates or absolute positioning\n const prompt = `Your task is to interpret a user's sketch/wireframe and turn it into code that is integrated in the codebase.\n\n## User's Request\n\"${comment}\"\n\n## Drawing Context\n${positionContext}${textContext}${nearbyContext}${imageNote}\n\n## Your Process\n1. **Analyze the Sketch:** Understand the visual intent—what UI component does the user want?\n2. **Interpret, Don't Transcribe:** Elevate the low-fidelity drawing into a high-fidelity component. Choose appropriate spacing, colors, and typography that match modern design standards.\n3. **Infer Missing Details:** If something is underspecified, use your expertise to make the best choice. An informed decision is better than an incomplete component.\n\n## Implementation Guidelines\n- Create a React component with inline styles or Tailwind CSS classes\n- Do NOT use hardcoded pixel positions or absolute coordinates - integrate naturally with existing page flow\n- Use flexbox, grid, or relative positioning to place the component appropriately\n- If the sketch shows:\n - **Rectangle/box:** Card, container, button, or input field depending on context\n - **Text elements:** Headings, paragraphs, or labels with appropriate hierarchy\n - **Form layout:** Input fields with labels, proper spacing\n - **Icons/shapes:** Use appropriate icons from lucide-react or inline SVGs\n - **Navigation:** Nav links, menus, or breadcrumbs\n - **Lists:** Ordered/unordered lists or grid layouts\n- Make the component fit naturally with the existing page design\n- Style it nicely and according to the existing codebase\n- Use semantic HTML and ARIA attributes where appropriate\n- The component should integrate well with the existing codebase structure\n\n## Annotations\n- Any **red marks** in the drawing are instructions—follow them but don't render them\n- Text annotations describe intent or constraints\n\nMake the changes directly. Insert the component at the appropriate location in the page. No explanation needed.\n\nIMPORTANT: Do NOT modify the import { SkemaWrapper } from \"@/components/skema-wrapper\" line or the SkemaWrapper component itself.`;\n\n return prompt;\n}\n\n/**\n * Spawn Gemini CLI and return an async iterator of events\n */\nexport function spawnGeminiCLI(\n prompt: string,\n options: GeminiCLIOptions = {}\n): {\n process: ChildProcess;\n events: AsyncIterable<GeminiCLIEvent>;\n} {\n const {\n cwd = process.cwd(),\n apiKey = process.env.GEMINI_API_KEY,\n yolo = true,\n outputFormat = 'stream-json',\n model = 'gemini-2.5-flash',\n } = options;\n\n const args = ['-p', prompt];\n\n if (yolo) {\n args.push('--yolo');\n }\n\n args.push('--output-format', outputFormat);\n args.push('-m', model);\n\n const gemini = spawn('gemini', args, {\n cwd,\n env: {\n ...process.env,\n ...(apiKey ? { GEMINI_API_KEY: apiKey } : {}),\n },\n });\n\n const events: AsyncIterable<GeminiCLIEvent> = {\n [Symbol.asyncIterator]() {\n let buffer = '';\n let done = false;\n const queue: GeminiCLIEvent[] = [];\n let resolveNext: ((value: IteratorResult<GeminiCLIEvent>) => void) | null = null;\n\n const pushEvent = (event: GeminiCLIEvent) => {\n if (resolveNext) {\n resolveNext({ value: event, done: false });\n resolveNext = null;\n } else {\n queue.push(event);\n }\n };\n\n gemini.stdout.on('data', (data: Buffer) => {\n buffer += data.toString();\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (line.trim()) {\n try {\n const event = JSON.parse(line) as GeminiCLIEvent;\n pushEvent(event);\n } catch {\n // Raw output, wrap it\n pushEvent({ type: 'message', content: line });\n }\n }\n }\n });\n\n gemini.stderr.on('data', (data: Buffer) => {\n pushEvent({ type: 'error', content: data.toString() });\n });\n\n gemini.on('close', (code) => {\n if (buffer.trim()) {\n try {\n const event = JSON.parse(buffer) as GeminiCLIEvent;\n pushEvent(event);\n } catch {\n pushEvent({ type: 'message', content: buffer });\n }\n }\n pushEvent({ type: 'done', code: code ?? 0 });\n done = true;\n if (resolveNext) {\n resolveNext({ value: undefined as unknown as GeminiCLIEvent, done: true });\n }\n });\n\n gemini.on('error', (err) => {\n pushEvent({ type: 'error', content: err.message });\n done = true;\n if (resolveNext) {\n resolveNext({ value: undefined as unknown as GeminiCLIEvent, done: true });\n }\n });\n\n return {\n next(): Promise<IteratorResult<GeminiCLIEvent>> {\n if (queue.length > 0) {\n return Promise.resolve({ value: queue.shift()!, done: false });\n }\n if (done) {\n return Promise.resolve({ value: undefined as unknown as GeminiCLIEvent, done: true });\n }\n return new Promise((resolve) => {\n resolveNext = resolve;\n });\n },\n };\n },\n };\n\n return { process: gemini, events };\n}\n\n/**\n * Analyze an image using the Google Generative AI SDK (Gemini Vision)\n */\nasync function analyzeImageWithGemini(apiKey: string, base64Image: string, modelName: string = 'gemini-2.5-flash'): Promise<string> {\n try {\n const genAI = new GoogleGenerativeAI(apiKey);\n const model = genAI.getGenerativeModel({ model: modelName });\n\n // Clean base64 string if needed (remove data URI prefix)\n const imageParts = [\n {\n inlineData: {\n data: base64Image.replace(/^data:image\\/\\w+;base64,/, ''),\n mimeType: 'image/png',\n },\n },\n ];\n\n const imageAnalysisPrompt = `\nAnalyze this UI wireframe sketch in extreme detail for a front-end developer.\n\nDescribe every element, layout, spacing, icons, and text you see.\nMention relative positions and hierarchy.\nBe distinct about what is drawn vs what might be background.\n\nDo NOT focus on exact pixel coordinates or absolute positions - describe layouts \nin terms of relative positioning (left/right/top/bottom, centered, evenly spaced, etc.).\n\nIt is expected that they will be rough draft's / hand-drawn things. Interpret the drawing and its goals as best as you can.\nDO NOT MENTION THAT THINGS HAVE \"Hand-sketched\" or \"Hand-drawn\" vibes. Make assumptions of what they were trying to do.\nJust FYI, this gets passed onto a generator to generate the actual code of modern UI componenents.\n`.trim();\n\n const result = await model.generateContent([\n imageAnalysisPrompt,\n ...imageParts,\n ]);\n\n const response = await result.response;\n const text = response.text();\n return text;\n } catch (error) {\n console.error('Failed to analyze image with Gemini Vision:', error);\n return `[Extension Error] Failed to analyze drawing: ${error instanceof Error ? error.message : String(error)}`;\n }\n}\n\n/**\n * Create a streaming response for use in API routes (Next.js, Express, etc.)\n */\nexport function createGeminiCLIStream(\n annotation: Partial<Annotation> & { comment?: string },\n projectContext?: ProjectContext,\n options?: GeminiCLIOptions\n): ReadableStream<Uint8Array> {\n // We need to handle the prompt building inside logic because it might be async now\n // But ReadableStream start controller can be async\n const encoder = new TextEncoder();\n\n return new ReadableStream({\n async start(controller) {\n const apiKey = options?.apiKey || process.env.GEMINI_API_KEY;\n let visionDescription = '';\n\n // Perform Image Analysis if needed for drawing annotations with images\n if (annotation.type === 'drawing' && (annotation as { drawingImage?: string }).drawingImage && apiKey) {\n // Send a \"progress\" event to the client\n const progressEvent: GeminiCLIEvent = {\n type: 'message',\n role: 'assistant',\n content: '🎨 Analyzing drawing image with Gemini Vision...',\n timestamp: new Date().toISOString()\n };\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(progressEvent)}\\n\\n`));\n\n visionDescription = await analyzeImageWithGemini(\n apiKey,\n (annotation as { drawingImage: string }).drawingImage,\n options?.model || 'gemini-2.5-flash'\n );\n\n // Log the analysis result\n const analysisEvent: GeminiCLIEvent = {\n type: 'message',\n role: 'assistant',\n content: `👁️ Visual Analysis:\\n${visionDescription}`,\n timestamp: new Date().toISOString()\n };\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(analysisEvent)}\\n\\n`));\n }\n\n const prompt = buildPromptFromAnnotation(\n annotation,\n projectContext,\n {\n fastMode: options?.fastMode ?? false,\n visionDescription\n }\n );\n\n // Send the prompt as a debug event so it shows up in browser console\n const promptEvent: GeminiCLIEvent = {\n type: 'debug',\n content: prompt,\n label: 'GEMINI CLI PROMPT',\n timestamp: new Date().toISOString()\n };\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(promptEvent)}\\n\\n`));\n\n const { events } = spawnGeminiCLI(prompt, options);\n\n for await (const event of events) {\n const sseData = `data: ${JSON.stringify(event)}\\n\\n`;\n controller.enqueue(encoder.encode(sseData));\n\n if (event.type === 'done') {\n controller.close();\n break;\n }\n }\n },\n });\n}\n\n/**\n * Run Gemini CLI and wait for completion\n */\nexport async function runGeminiCLI(\n annotation: Partial<Annotation> & { comment?: string },\n projectContext?: ProjectContext,\n options?: GeminiCLIOptions\n): Promise<{\n success: boolean;\n response: string;\n events: GeminiCLIEvent[];\n}> {\n const apiKey = options?.apiKey || process.env.GEMINI_API_KEY;\n let visionDescription = '';\n\n // Perform Image Analysis if needed for drawing annotations with images\n if (annotation.type === 'drawing' && (annotation as { drawingImage?: string }).drawingImage && apiKey) {\n visionDescription = await analyzeImageWithGemini(\n apiKey,\n (annotation as { drawingImage: string }).drawingImage,\n options?.model || 'gemini-2.5-flash'\n );\n }\n\n const prompt = buildPromptFromAnnotation(\n annotation,\n projectContext,\n {\n fastMode: options?.fastMode ?? false,\n visionDescription\n }\n );\n\n // Note: For runGeminiCLI (non-streaming), logs are not sent to browser\n // Use createGeminiCLIStream for browser console logging\n const { events: eventIterator } = spawnGeminiCLI(prompt, options);\n\n const events: GeminiCLIEvent[] = [];\n let response = '';\n let success = true;\n\n for await (const event of eventIterator) {\n events.push(event);\n\n if (event.type === 'message' && event.role === 'assistant' && event.content) {\n response += event.content;\n }\n\n if (event.type === 'done' && event.code !== 0) {\n success = false;\n }\n }\n\n return { success, response, events };\n}\n\n// =============================================================================\n// Next.js Route Handler - can be directly re-exported\n// =============================================================================\n\n/**\n * Next.js App Router POST handler for Gemini CLI\n *\n * Usage in your app/api/gemini/route.ts:\n * ```typescript\n * export { POST, DELETE } from 'skema-core/server';\n * ```\n *\n * Or with custom options:\n * ```typescript\n * import { createGeminiRouteHandler, createRevertRouteHandler } from 'skema-core/server';\n * export const POST = createGeminiRouteHandler({ cwd: '/custom/path' });\n * export const DELETE = createRevertRouteHandler({ cwd: '/custom/path' });\n * ```\n */\nexport function createGeminiRouteHandler(defaultOptions?: GeminiCLIOptions) {\n return async function POST(request: Request): Promise<Response> {\n const { annotation, projectContext } = await request.json();\n const cwd = defaultOptions?.cwd ?? process.cwd();\n\n // Create snapshot for undo support (using annotation.id if available)\n const annotationId = annotation.id || `temp-${Date.now()}`;\n createSnapshot(annotationId, cwd);\n\n const stream = createGeminiCLIStream(annotation, projectContext, {\n cwd,\n ...defaultOptions,\n });\n\n return new Response(stream, {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n 'X-Annotation-Id': annotationId,\n },\n });\n };\n}\n\n/**\n * Next.js App Router DELETE handler for reverting Gemini changes\n */\nexport function createRevertRouteHandler(defaultOptions?: { cwd?: string }) {\n return async function DELETE(request: Request): Promise<Response> {\n const { annotationId } = await request.json();\n const cwd = defaultOptions?.cwd ?? process.cwd();\n\n if (!annotationId) {\n return new Response(JSON.stringify({ success: false, message: 'Missing annotationId' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n const result = revertAnnotation(annotationId, cwd);\n\n return new Response(JSON.stringify(result), {\n status: result.success ? 200 : 400,\n headers: { 'Content-Type': 'application/json' },\n });\n };\n}\n\n/**\n * Default POST handler - ready to use in Next.js App Router\n *\n * Usage:\n * ```typescript\n * // app/api/gemini/route.ts\n * export { POST, DELETE } from 'skema-core/server';\n * ```\n */\nexport const POST = createGeminiRouteHandler();\nexport const DELETE = createRevertRouteHandler();\n"]}
|