next-ai-editor 0.1.1 → 0.1.3

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.
Files changed (50) hide show
  1. package/dist/{AIEditorProvider-Bs9zUVrL.cjs → AIEditorProvider-BGHm2xyU.cjs} +823 -378
  2. package/dist/AIEditorProvider-BGHm2xyU.cjs.map +1 -0
  3. package/dist/{AIEditorProvider-D-w9-GZb.js → AIEditorProvider-CxdGjdLL.js} +847 -402
  4. package/dist/AIEditorProvider-CxdGjdLL.js.map +1 -0
  5. package/dist/client/AIEditorProvider.d.ts +8 -16
  6. package/dist/client/AIEditorProvider.d.ts.map +1 -1
  7. package/dist/client/fiber-utils.d.ts +35 -0
  8. package/dist/client/fiber-utils.d.ts.map +1 -0
  9. package/dist/client/index.d.ts +1 -1
  10. package/dist/client/index.d.ts.map +1 -1
  11. package/dist/client/query-params.d.ts +9 -0
  12. package/dist/client/query-params.d.ts.map +1 -0
  13. package/dist/client.cjs +1 -1
  14. package/dist/client.js +1 -1
  15. package/dist/{index-DnoYi4f8.cjs → index-CNJqd4EQ.cjs} +656 -225
  16. package/dist/index-CNJqd4EQ.cjs.map +1 -0
  17. package/dist/{index-BFa7H-uO.js → index-DrmEf13c.js} +662 -231
  18. package/dist/index-DrmEf13c.js.map +1 -0
  19. package/dist/index.cjs +7 -2
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.js +15 -10
  22. package/dist/path-utils-Bai2xKx9.js +36 -0
  23. package/dist/path-utils-Bai2xKx9.js.map +1 -0
  24. package/dist/path-utils-DYzEWUGy.cjs +35 -0
  25. package/dist/path-utils-DYzEWUGy.cjs.map +1 -0
  26. package/dist/server/handlers/edit.d.ts.map +1 -1
  27. package/dist/server/handlers/index.d.ts +1 -0
  28. package/dist/server/handlers/index.d.ts.map +1 -1
  29. package/dist/server/handlers/read.d.ts.map +1 -1
  30. package/dist/server/handlers/resolve.d.ts.map +1 -1
  31. package/dist/server/handlers/suggestions.d.ts +3 -0
  32. package/dist/server/handlers/suggestions.d.ts.map +1 -0
  33. package/dist/server/index.d.ts +1 -0
  34. package/dist/server/index.d.ts.map +1 -1
  35. package/dist/server/utils/ast.d.ts +10 -0
  36. package/dist/server/utils/ast.d.ts.map +1 -1
  37. package/dist/server/utils/source-map.d.ts +10 -0
  38. package/dist/server/utils/source-map.d.ts.map +1 -1
  39. package/dist/server.cjs +6 -1
  40. package/dist/server.cjs.map +1 -1
  41. package/dist/server.js +14 -9
  42. package/dist/shared/path-utils.d.ts +24 -0
  43. package/dist/shared/path-utils.d.ts.map +1 -0
  44. package/dist/shared/types.d.ts +30 -0
  45. package/dist/shared/types.d.ts.map +1 -1
  46. package/package.json +1 -1
  47. package/dist/AIEditorProvider-Bs9zUVrL.cjs.map +0 -1
  48. package/dist/AIEditorProvider-D-w9-GZb.js.map +0 -1
  49. package/dist/index-BFa7H-uO.js.map +0 -1
  50. package/dist/index-DnoYi4f8.cjs.map +0 -1
@@ -1,13 +1,14 @@
1
1
  "use strict";
2
2
  const server = require("next/server");
3
3
  const fs = require("fs/promises");
4
- const parser = require("@babel/parser");
5
4
  const anthropic = require("@langchain/anthropic");
6
5
  const messages = require("@langchain/core/messages");
7
6
  const path = require("path");
7
+ const parser = require("@babel/parser");
8
8
  const traverse = require("@babel/traverse");
9
9
  const t = require("@babel/types");
10
10
  const sourceMap = require("@jridgewell/source-map");
11
+ const pathUtils = require("./path-utils-DYzEWUGy.cjs");
11
12
  const crypto = require("crypto");
12
13
  function _interopNamespaceDefault(e) {
13
14
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
@@ -84,9 +85,79 @@ function parseFile(content) {
84
85
  return null;
85
86
  }
86
87
  }
88
+ function extractComponentName(ast) {
89
+ let componentName = null;
90
+ traverse(ast, {
91
+ ExportDefaultDeclaration(path2) {
92
+ var _a;
93
+ if (t__namespace.isFunctionDeclaration(path2.node.declaration)) {
94
+ componentName = ((_a = path2.node.declaration.id) == null ? void 0 : _a.name) || null;
95
+ } else if (t__namespace.isArrowFunctionExpression(path2.node.declaration)) {
96
+ componentName = "default";
97
+ } else if (t__namespace.isIdentifier(path2.node.declaration)) {
98
+ componentName = path2.node.declaration.name;
99
+ }
100
+ },
101
+ ExportNamedDeclaration(path2) {
102
+ var _a;
103
+ if (t__namespace.isFunctionDeclaration(path2.node.declaration)) {
104
+ componentName = ((_a = path2.node.declaration.id) == null ? void 0 : _a.name) || null;
105
+ } else if (t__namespace.isVariableDeclaration(path2.node.declaration)) {
106
+ const declarator = path2.node.declaration.declarations[0];
107
+ if (t__namespace.isIdentifier(declarator.id)) {
108
+ componentName = declarator.id.name;
109
+ }
110
+ } else if (path2.node.specifiers && path2.node.specifiers.length > 0) {
111
+ const specifier = path2.node.specifiers[0];
112
+ if (t__namespace.isExportSpecifier(specifier) && t__namespace.isIdentifier(specifier.exported)) {
113
+ componentName = specifier.exported.name;
114
+ }
115
+ }
116
+ }
117
+ });
118
+ return componentName;
119
+ }
120
+ function validateGeneratedCode(newCode, originalCode, fileContent) {
121
+ try {
122
+ const isFullComponent = /^(export\s+)?(default\s+)?function\s+\w+/.test(newCode.trim()) || /^(export\s+)?(default\s+)?const\s+\w+\s*=/.test(newCode.trim());
123
+ if (isFullComponent) {
124
+ let codeToValidate = newCode;
125
+ if (fileContent) {
126
+ const interfaceMatches = fileContent.match(
127
+ /^(interface|type)\s+\w+[^}]*\}/gm
128
+ );
129
+ if (interfaceMatches) {
130
+ codeToValidate = interfaceMatches.join("\n\n") + "\n\n" + newCode;
131
+ }
132
+ }
133
+ parser__namespace.parse(codeToValidate, {
134
+ sourceType: "module",
135
+ plugins: ["jsx", "typescript"]
136
+ });
137
+ } else {
138
+ const wrapped = `function _() { return (${newCode}); }`;
139
+ parser__namespace.parse(wrapped, {
140
+ sourceType: "module",
141
+ plugins: ["jsx", "typescript"]
142
+ });
143
+ }
144
+ } catch (e) {
145
+ console.error("Generated code parse error:", e);
146
+ return false;
147
+ }
148
+ const origBraces = (originalCode.match(/[{}]/g) || []).length;
149
+ const newBraces = (newCode.match(/[{}]/g) || []).length;
150
+ const origTags = (originalCode.match(/[<>]/g) || []).length;
151
+ const newTags = (newCode.match(/[<>]/g) || []).length;
152
+ if (Math.abs(origBraces - newBraces) > 4 || Math.abs(origTags - newTags) > 4) {
153
+ console.warn(
154
+ `Structure changed significantly: braces ${origBraces}->${newBraces}, tags ${origTags}->${newTags}`
155
+ );
156
+ }
157
+ return true;
158
+ }
87
159
  function findTargetElement(ast, fileContent, options) {
88
160
  const { componentName, lineNumber, elementContext } = options;
89
- const matches = [];
90
161
  let componentNode = null;
91
162
  let componentStart = 0;
92
163
  let componentEnd = Infinity;
@@ -137,6 +208,7 @@ function findTargetElement(ast, fileContent, options) {
137
208
  componentEnd = fallback.end;
138
209
  }
139
210
  const allElementsByTag = /* @__PURE__ */ new Map();
211
+ const elementsAtLine = [];
140
212
  traverse(ast, {
141
213
  JSXElement(path2) {
142
214
  const loc = path2.node.loc;
@@ -152,59 +224,107 @@ function findTargetElement(ast, fileContent, options) {
152
224
  }
153
225
  allElementsByTag.get(tagName).push({ node: path2.node, startLine, endLine, score: 0 });
154
226
  }
155
- if (elementContext) {
156
- const score = scoreElementMatch(path2.node, elementContext, fileContent);
157
- if (score > 0) {
158
- matches.push({ node: path2.node, startLine, endLine, score });
159
- }
160
- } else if (Math.abs(startLine - lineNumber) <= 5) {
161
- matches.push({
162
- node: path2.node,
163
- startLine,
164
- endLine,
165
- score: 100 - Math.abs(startLine - lineNumber)
166
- });
227
+ if (startLine === lineNumber) {
228
+ elementsAtLine.push({ node: path2.node, startLine, endLine, score: 0 });
167
229
  }
168
230
  }
169
231
  });
170
- if (matches.length === 0) {
171
- if (componentNode && componentStart > 0) {
232
+ if (elementsAtLine.length > 0) {
233
+ if (elementsAtLine.length === 1) {
234
+ const target = elementsAtLine[0];
172
235
  return {
173
- startLine: componentStart,
174
- endLine: componentEnd,
236
+ startLine: target.startLine,
237
+ endLine: target.endLine,
175
238
  componentStart,
176
239
  componentEnd
177
240
  };
178
241
  }
179
- return null;
242
+ if (elementContext) {
243
+ for (const elem of elementsAtLine) {
244
+ if (t__namespace.isJSXElement(elem.node)) {
245
+ const score = scoreElementMatch(elem.node, elementContext, fileContent);
246
+ elem.score = score;
247
+ }
248
+ }
249
+ elementsAtLine.sort((a, b) => b.score - a.score);
250
+ if (elementsAtLine[0].score > 0) {
251
+ return {
252
+ startLine: elementsAtLine[0].startLine,
253
+ endLine: elementsAtLine[0].endLine,
254
+ componentStart,
255
+ componentEnd
256
+ };
257
+ }
258
+ }
259
+ return {
260
+ startLine: elementsAtLine[0].startLine,
261
+ endLine: elementsAtLine[0].endLine,
262
+ componentStart,
263
+ componentEnd
264
+ };
180
265
  }
181
- if ((elementContext == null ? void 0 : elementContext.nthOfType) && elementContext.tagName) {
266
+ if (elementContext == null ? void 0 : elementContext.tagName) {
182
267
  const allOfTag = allElementsByTag.get(elementContext.tagName);
183
- if (allOfTag && allOfTag.length >= elementContext.nthOfType) {
184
- const target = allOfTag[elementContext.nthOfType - 1];
185
- console.log(
186
- ` Using nthOfType=${elementContext.nthOfType}: found ${allOfTag.length} <${elementContext.tagName}> elements`
187
- );
188
- return {
189
- startLine: target.startLine,
190
- endLine: target.endLine,
191
- componentStart,
192
- componentEnd
193
- };
194
- } else {
195
- console.log(
196
- ` nthOfType=${elementContext.nthOfType} but only found ${(allOfTag == null ? void 0 : allOfTag.length) || 0} <${elementContext.tagName}> elements`
197
- );
268
+ if (allOfTag && allOfTag.length > 0) {
269
+ if (elementContext.textContent || elementContext.className) {
270
+ for (const elem of allOfTag) {
271
+ if (t__namespace.isJSXElement(elem.node)) {
272
+ elem.score = scoreElementMatch(elem.node, elementContext, fileContent);
273
+ }
274
+ }
275
+ allOfTag.sort((a, b) => b.score - a.score);
276
+ if (allOfTag[0].score > 50) {
277
+ return {
278
+ startLine: allOfTag[0].startLine,
279
+ endLine: allOfTag[0].endLine,
280
+ componentStart,
281
+ componentEnd
282
+ };
283
+ }
284
+ }
285
+ if (elementContext.nthOfType && allOfTag.length >= elementContext.nthOfType) {
286
+ const target = allOfTag[elementContext.nthOfType - 1];
287
+ return {
288
+ startLine: target.startLine,
289
+ endLine: target.endLine,
290
+ componentStart,
291
+ componentEnd
292
+ };
293
+ }
294
+ }
295
+ }
296
+ const nearbyElements = [];
297
+ traverse(ast, {
298
+ JSXElement(path2) {
299
+ const loc = path2.node.loc;
300
+ if (!loc) return;
301
+ const startLine = loc.start.line;
302
+ const endLine = loc.end.line;
303
+ if (startLine < componentStart || endLine > componentEnd) return;
304
+ if (Math.abs(startLine - lineNumber) <= 5) {
305
+ const score = elementContext ? scoreElementMatch(path2.node, elementContext, fileContent) : 100 - Math.abs(startLine - lineNumber);
306
+ nearbyElements.push({ node: path2.node, startLine, endLine, score });
307
+ }
198
308
  }
309
+ });
310
+ if (nearbyElements.length > 0) {
311
+ nearbyElements.sort((a, b) => b.score - a.score);
312
+ return {
313
+ startLine: nearbyElements[0].startLine,
314
+ endLine: nearbyElements[0].endLine,
315
+ componentStart,
316
+ componentEnd
317
+ };
318
+ }
319
+ if (componentNode && componentStart > 0) {
320
+ return {
321
+ startLine: componentStart,
322
+ endLine: componentEnd,
323
+ componentStart,
324
+ componentEnd
325
+ };
199
326
  }
200
- matches.sort((a, b) => b.score - a.score);
201
- const best = matches[0];
202
- return {
203
- startLine: best.startLine,
204
- endLine: best.endLine,
205
- componentStart,
206
- componentEnd
207
- };
327
+ return null;
208
328
  }
209
329
  function scoreElementMatch(node, context, fileContent) {
210
330
  let score = 0;
@@ -288,7 +408,8 @@ async function handleEdit(req) {
288
408
  componentName,
289
409
  suggestion,
290
410
  elementContext,
291
- editHistory
411
+ editHistory,
412
+ parentInstance
292
413
  } = body;
293
414
  const projectRoot = process.cwd();
294
415
  const normalizedPath = normalizePath(filePath);
@@ -300,7 +421,6 @@ async function handleEdit(req) {
300
421
  );
301
422
  }
302
423
  const fileContent = await fs.readFile(absolutePath, "utf-8");
303
- console.log(`[/edit] componentName="${componentName}", filePath="${filePath}"`);
304
424
  const ast = parseFile(fileContent);
305
425
  if (!ast) {
306
426
  return server.NextResponse.json(
@@ -319,20 +439,10 @@ async function handleEdit(req) {
319
439
  { status: 400 }
320
440
  );
321
441
  }
322
- console.log(
323
- `📍 Found element <${(elementContext == null ? void 0 : elementContext.tagName) || "component"}> at lines ${target.startLine}-${target.endLine} (component: ${target.componentStart}-${target.componentEnd})`
324
- );
325
- console.log(
326
- ` Element context: tagName=${elementContext == null ? void 0 : elementContext.tagName}, nthOfType=${elementContext == null ? void 0 : elementContext.nthOfType}, textContent="${elementContext == null ? void 0 : elementContext.textContent}"`
327
- );
328
442
  const lines = fileContent.split("\n");
329
- const foundElementCode = lines.slice(target.startLine - 1, Math.min(target.startLine + 2, target.endLine)).join("\n");
330
- console.log(` Found element preview:
331
- ${foundElementCode}`);
332
443
  const targetCode = lines.slice(target.startLine - 1, target.endLine).join("\n");
333
444
  const baseIndentation = ((_a = lines[target.startLine - 1].match(/^(\s*)/)) == null ? void 0 : _a[1]) || "";
334
445
  if (target.componentStart <= 0 || target.componentEnd === Infinity) {
335
- console.error("❌ Invalid component bounds detected");
336
446
  return server.NextResponse.json({
337
447
  success: false,
338
448
  error: `Could not determine component bounds. Component: ${target.componentStart}-${target.componentEnd}`
@@ -370,7 +480,8 @@ ${foundElementCode}`);
370
480
  targetEndLine: target.endLine,
371
481
  componentStart: target.componentStart,
372
482
  componentEnd: target.componentEnd,
373
- editHistory: editHistory || []
483
+ editHistory: editHistory || [],
484
+ parentInstance
374
485
  });
375
486
  if (!newCode) {
376
487
  return server.NextResponse.json({
@@ -378,38 +489,56 @@ ${foundElementCode}`);
378
489
  error: "AI failed to generate valid edit"
379
490
  });
380
491
  }
381
- console.log("Raw AI response length:", newCode.length);
382
- console.log("Looking for // FULL_COMPONENT marker...");
492
+ const parentInstanceMatch = newCode.match(/\/\/ EDIT_PARENT_INSTANCE\s*\n([\s\S]+)/);
493
+ if (parentInstanceMatch && parentInstance) {
494
+ const parentCode = parentInstanceMatch[1].trim();
495
+ const parentNormalizedPath = normalizePath(parentInstance.filePath);
496
+ const parentAbsolutePath = await resolveFilePath(projectRoot, parentNormalizedPath);
497
+ if (!parentAbsolutePath) {
498
+ return server.NextResponse.json({
499
+ success: false,
500
+ error: `Parent file not found: ${parentNormalizedPath}`
501
+ });
502
+ }
503
+ const parentFileContent = await fs.readFile(parentAbsolutePath, "utf-8");
504
+ const parentLines = parentFileContent.split("\n");
505
+ const newParentLines = [...parentLines];
506
+ newParentLines.splice(
507
+ parentInstance.lineStart - 1,
508
+ parentInstance.lineEnd - parentInstance.lineStart + 1,
509
+ ...parentCode.split("\n")
510
+ );
511
+ await fs.writeFile(parentAbsolutePath, newParentLines.join("\n"), "utf-8");
512
+ return server.NextResponse.json({
513
+ success: true,
514
+ fileSnapshot: parentFileContent,
515
+ generatedCode: parentCode,
516
+ modifiedLines: {
517
+ start: parentInstance.lineStart,
518
+ end: parentInstance.lineEnd
519
+ },
520
+ editedFile: parentInstance.filePath
521
+ // Indicate which file was edited
522
+ });
523
+ }
383
524
  const fullComponentMatch = newCode.match(/\/\/ FULL_COMPONENT\s*\n([\s\S]+)/);
384
525
  let codeToApply = newCode;
385
526
  let startLineToReplace = target.startLine;
386
527
  let endLineToReplace = target.endLine;
528
+ const isFullComponentDeclaration = /^(export\s+)?(default\s+)?function\s+\w+/.test(newCode.trim()) || /^(export\s+)?const\s+\w+\s*=/.test(newCode.trim());
387
529
  if (fullComponentMatch) {
388
- console.log("Found // FULL_COMPONENT marker, extracting full component code");
389
530
  codeToApply = fullComponentMatch[1].trim();
390
531
  startLineToReplace = target.componentStart;
391
532
  endLineToReplace = target.componentEnd;
392
- console.log(
393
- `🔄 AI returned full component modification (lines ${startLineToReplace}-${endLineToReplace})`
394
- );
395
- console.log("Extracted component code length:", codeToApply.length);
396
- console.log("Extracted component code (first 300 chars):", codeToApply.substring(0, 300));
397
- } else {
398
- console.log("No // FULL_COMPONENT marker found, treating as target element modification");
399
- console.log(
400
- `🔄 AI returned target element modification (lines ${startLineToReplace}-${endLineToReplace})`
401
- );
533
+ } else if (isFullComponentDeclaration && target.startLine !== target.componentStart) {
534
+ codeToApply = newCode;
535
+ startLineToReplace = target.componentStart;
536
+ endLineToReplace = target.componentEnd;
402
537
  }
403
- console.log("Code to apply (first 200 chars):", codeToApply.substring(0, 200));
404
538
  if (!validateGeneratedCode(codeToApply, targetCode, fileContent)) {
405
- console.error("❌ Generated code failed validation");
406
- console.error("=== Generated code START ===");
407
- console.error(codeToApply);
408
- console.error("=== Generated code END ===");
409
- console.error(`Length: ${codeToApply.length} chars, ${codeToApply.split("\n").length} lines`);
410
539
  return server.NextResponse.json({
411
540
  success: false,
412
- error: "Generated code is invalid - check server logs for details"
541
+ error: "Generated code is invalid"
413
542
  });
414
543
  }
415
544
  const newLines = [...lines];
@@ -419,7 +548,6 @@ ${foundElementCode}`);
419
548
  ...codeToApply.split("\n")
420
549
  );
421
550
  await fs.writeFile(absolutePath, newLines.join("\n"), "utf-8");
422
- console.log(`✅ Updated ${normalizedPath}`);
423
551
  return server.NextResponse.json({
424
552
  success: true,
425
553
  fileSnapshot: fileContent,
@@ -429,10 +557,11 @@ ${foundElementCode}`);
429
557
  modifiedLines: {
430
558
  start: startLineToReplace,
431
559
  end: endLineToReplace
432
- }
560
+ },
561
+ editedFile: filePath
562
+ // Indicate which file was edited
433
563
  });
434
564
  } catch (error) {
435
- console.error("AI Editor error:", error);
436
565
  return server.NextResponse.json(
437
566
  { success: false, error: String(error) },
438
567
  { status: 500 }
@@ -444,7 +573,8 @@ async function generateEdit(options) {
444
573
  fullComponentCode,
445
574
  suggestion,
446
575
  baseIndentation,
447
- editHistory
576
+ editHistory,
577
+ parentInstance
448
578
  } = options;
449
579
  const apiKey = process.env.ANTHROPIC_API_KEY;
450
580
  if (!apiKey) throw new Error("ANTHROPIC_API_KEY not set");
@@ -457,25 +587,29 @@ async function generateEdit(options) {
457
587
  const systemPrompt = `You are a precise code editor for React/JSX components.
458
588
 
459
589
  WHAT YOU'LL SEE:
460
- - Full component code with line annotations
461
- - The clicked element is marked with "// ← CLICKED ELEMENT STARTS" and "// ← CLICKED ELEMENT ENDS"
462
- - Lines within the element have "// (clicked element)"
463
- - These are just annotations to help you - they are NOT part of the actual code
590
+ - Component Definition: Full code of the clicked component with line annotations
591
+ * The clicked element is marked with "// ← CLICKED ELEMENT STARTS" and "// ← CLICKED ELEMENT ENDS"
592
+ * These are just annotations - NOT part of the actual code
593
+ ${parentInstance ? `- Parent Instance: Where this component is used, with "// COMPONENT USAGE" marking the usage line` : ""}
594
+
595
+ YOUR DECISION - Choose ONE approach based on the user's request:
464
596
 
465
- YOUR TASK:
466
- Modify ONLY the marked element (unless the request requires changing its parent/wrapper).
597
+ 1. EDIT COMPONENT DEFINITION (most common):
598
+ - Modify styling, layout, or behavior that should apply to ALL instances
599
+ - Example requests: "make the button blue", "add padding", "change font size"
600
+ - Output: Just the modified element OR "// FULL_COMPONENT\\n" + complete modified component
467
601
 
468
- OUTPUT:
469
- - Just the modified element code (WITHOUT the annotation comments)
470
- - OR if broader changes needed: "// FULL_COMPONENT\\n" + complete modified component (WITHOUT annotation comments)
602
+ ${parentInstance ? `2. EDIT PARENT INSTANCE (when user wants to modify THIS specific usage):
603
+ - Change props, text, or configuration for THIS specific instance only
604
+ - Example requests: "change this title to...", "remove this card", "add another button here"
605
+ - Output: "// EDIT_PARENT_INSTANCE\\n" + complete modified parent component` : ""}
471
606
 
472
607
  RULES:
473
608
  - Output ONLY code, no explanations
474
609
  - No markdown fences
475
- - Do NOT include the "// ← CLICKED ELEMENT" annotation comments in your output
610
+ - Do NOT include annotation comments in your output
476
611
  - Preserve indentation
477
- - Make minimal changes
478
- - Do NOT modify unrelated elements`;
612
+ - Make minimal changes`;
479
613
  let userPrompt = "";
480
614
  if (editHistory.length > 0) {
481
615
  userPrompt += "Previous edits made to this element:\n";
@@ -485,16 +619,36 @@ RULES:
485
619
  });
486
620
  userPrompt += "\n";
487
621
  }
488
- userPrompt += `Component (clicked element is annotated with "// ← CLICKED ELEMENT" comments):
622
+ userPrompt += `COMPONENT DEFINITION (clicked element is annotated):
489
623
 
490
624
  \`\`\`jsx
491
625
  ${fullComponentCode}
492
626
  \`\`\`
627
+ `;
628
+ if (parentInstance) {
629
+ const parentLines = parentInstance.content.split("\n");
630
+ const annotatedParentLines = parentLines.map((line, idx) => {
631
+ const lineNum = parentInstance.lineStart + idx;
632
+ const isUsageLine = lineNum >= parentInstance.usageLineStart && lineNum <= parentInstance.usageLineEnd;
633
+ return line + (isUsageLine ? " // ← COMPONENT USAGE" : "");
634
+ });
635
+ userPrompt += `
636
+ PARENT INSTANCE (where component is used - in ${parentInstance.filePath}):
493
637
 
638
+ \`\`\`jsx
639
+ ${annotatedParentLines.join("\n")}
640
+ \`\`\`
641
+ `;
642
+ }
643
+ userPrompt += `
494
644
  User request: "${suggestion}"
495
- ${editHistory.length > 0 ? "\n(Build upon previous changes)" : ""}
645
+ ${editHistory.length > 0 ? "(Build upon previous changes)" : ""}
646
+
647
+ ${parentInstance ? `Decide whether to:
648
+ 1. Edit the component definition (for changes that affect ALL instances)
649
+ 2. Edit the parent instance (for changes specific to THIS usage)
496
650
 
497
- Modify the annotated element to fulfill this request. Remember: do NOT include the annotation comments in your output.`;
651
+ Then output the appropriate code with the correct marker.` : `Modify the annotated element to fulfill this request. Remember: do NOT include the annotation comments in your output.`}`;
498
652
  try {
499
653
  const response = await model.invoke([
500
654
  new messages.SystemMessage(systemPrompt),
@@ -504,12 +658,18 @@ Modify the annotated element to fulfill this request. Remember: do NOT include t
504
658
  code = cleanGeneratedCode(code, baseIndentation);
505
659
  return code || null;
506
660
  } catch (e) {
507
- console.error("AI generation error:", e);
508
661
  return null;
509
662
  }
510
663
  }
511
664
  function cleanGeneratedCode(code, baseIndentation) {
512
665
  var _a, _b, _c;
666
+ const isParentEdit = code.trim().startsWith("// EDIT_PARENT_INSTANCE");
667
+ if (isParentEdit) {
668
+ const marker2 = "// EDIT_PARENT_INSTANCE\n";
669
+ code = code.replace(/^\/\/ EDIT_PARENT_INSTANCE\n?/, "");
670
+ code = code.replace(/^```[\w]*\n?/gm, "").replace(/\n?```$/gm, "").replace(/\s*\/\/ ← COMPONENT USAGE/g, "").trim();
671
+ return marker2 + code;
672
+ }
513
673
  const isFullComponent = code.trim().startsWith("// FULL_COMPONENT");
514
674
  let marker = "";
515
675
  if (isFullComponent) {
@@ -540,43 +700,6 @@ function cleanGeneratedCode(code, baseIndentation) {
540
700
  }
541
701
  return marker + code;
542
702
  }
543
- function validateGeneratedCode(newCode, originalCode, fileContent) {
544
- try {
545
- const isFullComponent = /^(export\s+)?(default\s+)?function\s+\w+/.test(newCode.trim()) || /^(export\s+)?(default\s+)?const\s+\w+\s*=/.test(newCode.trim());
546
- if (isFullComponent) {
547
- let codeToValidate = newCode;
548
- if (fileContent) {
549
- const interfaceMatches = fileContent.match(/^(interface|type)\s+\w+[^}]*\}/gm);
550
- if (interfaceMatches) {
551
- codeToValidate = interfaceMatches.join("\n\n") + "\n\n" + newCode;
552
- }
553
- }
554
- parser__namespace.parse(codeToValidate, {
555
- sourceType: "module",
556
- plugins: ["jsx", "typescript"]
557
- });
558
- } else {
559
- const wrapped = `function _() { return (${newCode}); }`;
560
- parser__namespace.parse(wrapped, {
561
- sourceType: "module",
562
- plugins: ["jsx", "typescript"]
563
- });
564
- }
565
- } catch (e) {
566
- console.error("Generated code parse error:", e);
567
- return false;
568
- }
569
- const origBraces = (originalCode.match(/[{}]/g) || []).length;
570
- const newBraces = (newCode.match(/[{}]/g) || []).length;
571
- const origTags = (originalCode.match(/[<>]/g) || []).length;
572
- const newTags = (newCode.match(/[<>]/g) || []).length;
573
- if (Math.abs(origBraces - newBraces) > 4 || Math.abs(origTags - newTags) > 4) {
574
- console.warn(
575
- `Structure changed significantly: braces ${origBraces}->${newBraces}, tags ${origTags}->${newTags}`
576
- );
577
- }
578
- return true;
579
- }
580
703
  function parseDebugStack(stack) {
581
704
  if (!stack) return null;
582
705
  const stackStr = typeof stack === "string" ? stack : stack.stack || String(stack);
@@ -601,8 +724,8 @@ function parseDebugStack(stack) {
601
724
  chunkId = chunkMatch[1];
602
725
  filePath = filePath.replace(/\?[^:]*$/, "");
603
726
  }
604
- filePath = cleanPathTurbopack(filePath);
605
- if (!shouldSkip(filePath)) {
727
+ filePath = pathUtils.cleanPath(filePath);
728
+ if (!pathUtils.shouldSkipPath(filePath)) {
606
729
  console.log("parseDebugStack extracted:", { filePath, line, column });
607
730
  return { filePath, line, column, chunkId };
608
731
  }
@@ -614,46 +737,73 @@ function parseDebugStack(stack) {
614
737
  );
615
738
  return null;
616
739
  }
617
- function cleanPathTurbopack(p) {
618
- let cleaned = p;
619
- cleaned = cleaned.replace(/^about:\/\/React\/Server\//, "");
620
- if (cleaned.startsWith("file://")) {
621
- cleaned = cleaned.replace(/^file:\/\//, "");
622
- }
623
- try {
624
- if (cleaned.includes("%")) {
625
- cleaned = decodeURIComponent(cleaned);
740
+ function extractComponentNameFromStack(stack) {
741
+ if (!stack) return null;
742
+ const stackStr = typeof stack === "string" ? stack : stack.stack || String(stack);
743
+ const frames = stackStr.split("\n");
744
+ const skipPatterns = [
745
+ "node_modules",
746
+ "SegmentViewNode",
747
+ "LayoutRouter",
748
+ "ErrorBoundary",
749
+ "fakeJSXCallSite",
750
+ "react_stack_bottom_frame"
751
+ ];
752
+ for (const frame of frames) {
753
+ if (skipPatterns.some((p) => frame.includes(p))) continue;
754
+ const match = frame.match(/at\s+(\w+)\s+\(/);
755
+ if (match && match[1]) {
756
+ const componentName = match[1];
757
+ if (componentName !== "Object" && componentName !== "anonymous") {
758
+ return componentName;
759
+ }
626
760
  }
627
- } catch (e) {
628
- console.warn("Failed to decode URL-encoded path:", cleaned, e);
629
761
  }
630
- cleaned = cleaned.replace(/^webpack-internal:\/\/\/\([^)]+\)\/\.\//, "").replace(/^webpack-internal:\/\/\/\([^)]+\)\//, "").replace(/^webpack-internal:\/\//, "").replace(/^webpack:\/\/[^/]*\//, "").replace(/^\([^)]+\)\//, "").replace(/\?.*$/, "");
631
- if (cleaned.startsWith(".next/") || path.isAbsolute(cleaned)) {
632
- return cleaned;
633
- }
634
- return cleaned;
635
- }
636
- function cleanPath(p) {
637
- return cleanPathTurbopack(p);
762
+ return null;
638
763
  }
639
- function normalizeSourcePath(source, projectRoot) {
640
- let cleaned = cleanPath(source);
641
- cleaned = cleaned.replace(/\\/g, "/");
642
- if (cleaned.startsWith(projectRoot)) {
643
- cleaned = cleaned.substring(projectRoot.length + 1);
764
+ function parseDebugStackFrames(stack) {
765
+ if (!stack) return [];
766
+ const stackStr = typeof stack === "string" ? stack : stack.stack || String(stack);
767
+ const frames = stackStr.split("\n");
768
+ const skipPatterns = [
769
+ "node_modules",
770
+ "SegmentViewNode",
771
+ "LayoutRouter",
772
+ "ErrorBoundary",
773
+ "fakeJSXCallSite",
774
+ "react_stack_bottom_frame"
775
+ ];
776
+ const positions = [];
777
+ for (const frame of frames) {
778
+ if (skipPatterns.some((p) => frame.includes(p))) continue;
779
+ const match = frame.match(/at\s+(\w+)\s+\((.+?):(\d+):(\d+)\)?$/);
780
+ if (match) {
781
+ match[1];
782
+ let filePath = match[2];
783
+ const line = parseInt(match[3], 10);
784
+ const column = parseInt(match[4], 10);
785
+ let chunkId;
786
+ const chunkMatch = filePath.match(/\?([^:]+)$/);
787
+ if (chunkMatch) {
788
+ chunkId = chunkMatch[1];
789
+ filePath = filePath.replace(/\?[^:]*$/, "");
790
+ }
791
+ filePath = pathUtils.cleanPath(filePath);
792
+ if (!pathUtils.shouldSkipPath(filePath)) {
793
+ positions.push({ filePath, line, column, chunkId });
794
+ if (positions.length >= 2) break;
795
+ }
796
+ }
644
797
  }
645
- return cleaned.replace(/^\/+/, "");
646
- }
647
- function shouldSkip(p) {
648
- if (!p) return true;
649
- return ["node_modules", "next/dist", "react-dom"].some((s) => p.includes(s));
798
+ console.log(`parseDebugStackFrames extracted ${positions.length} frames:`, positions);
799
+ return positions;
650
800
  }
651
801
  async function resolveOriginalPosition(compiledPos, projectRoot) {
652
802
  try {
653
803
  console.log("resolveOriginalPosition called with:", compiledPos);
654
804
  let compiledFilePath = compiledPos.filePath;
655
- compiledFilePath = cleanPathTurbopack(compiledFilePath);
656
- console.log("After cleanPathTurbopack:", compiledFilePath);
805
+ compiledFilePath = pathUtils.cleanPath(compiledFilePath);
806
+ console.log("After cleanPath:", compiledFilePath);
657
807
  if (compiledFilePath.startsWith("http://") || compiledFilePath.startsWith("https://")) {
658
808
  const url = new URL(compiledFilePath);
659
809
  const pathname = url.pathname;
@@ -770,7 +920,7 @@ async function resolveFromSourceMap(sourceMapPath, compiledPos, projectRoot) {
770
920
  column: adjustedColumn
771
921
  });
772
922
  if (originalPos.source && originalPos.line !== null) {
773
- const source = normalizeSourcePath(
923
+ const source = pathUtils.normalizeSourcePath(
774
924
  originalPos.source || "",
775
925
  projectRoot
776
926
  );
@@ -793,7 +943,7 @@ async function resolveFromSourceMap(sourceMapPath, compiledPos, projectRoot) {
793
943
  column: compiledPos.column
794
944
  });
795
945
  if (originalPos.source && originalPos.line !== null) {
796
- const source = normalizeSourcePath(
946
+ const source = pathUtils.normalizeSourcePath(
797
947
  originalPos.source || "",
798
948
  projectRoot
799
949
  );
@@ -826,7 +976,6 @@ async function getOriginalPositionFromDebugStack(debugStack, projectRoot) {
826
976
  return await resolveOriginalPosition(compiledPos, projectRoot);
827
977
  }
828
978
  async function handleRead(req) {
829
- var _a;
830
979
  const devModeError = validateDevMode();
831
980
  if (devModeError) return devModeError;
832
981
  const { searchParams } = new URL(req.url);
@@ -843,6 +992,11 @@ async function handleRead(req) {
843
992
  textContent: textContent || void 0,
844
993
  className: className || void 0
845
994
  } : void 0;
995
+ const parentFilePath = searchParams.get("parentFilePath") || "";
996
+ const parentLine = parseInt(searchParams.get("parentLine") || "0");
997
+ const parentComponentName = searchParams.get("parentComponentName") || "";
998
+ const parentDebugStack = searchParams.get("parentDebugStack") || "";
999
+ const childKey = searchParams.get("childKey") || "";
846
1000
  const projectRoot = process.cwd();
847
1001
  if (debugStack) {
848
1002
  const compiledPos = parseDebugStack(debugStack);
@@ -873,21 +1027,7 @@ async function handleRead(req) {
873
1027
  { status: 400 }
874
1028
  );
875
1029
  }
876
- let componentName = "";
877
- traverse(ast, {
878
- ExportDefaultDeclaration(path2) {
879
- var _a2;
880
- if (t__namespace.isFunctionDeclaration(path2.node.declaration)) {
881
- componentName = ((_a2 = path2.node.declaration.id) == null ? void 0 : _a2.name) || "";
882
- }
883
- },
884
- ExportNamedDeclaration(path2) {
885
- var _a2;
886
- if (t__namespace.isFunctionDeclaration(path2.node.declaration)) {
887
- componentName = ((_a2 = path2.node.declaration.id) == null ? void 0 : _a2.name) || "";
888
- }
889
- }
890
- });
1030
+ const componentName = extractComponentName(ast);
891
1031
  if (!componentName) {
892
1032
  return server.NextResponse.json({
893
1033
  success: true,
@@ -909,28 +1049,85 @@ async function handleRead(req) {
909
1049
  lineEnd: content.split("\n").length
910
1050
  });
911
1051
  }
912
- console.log(`[/read] Found element:`);
913
- console.log(
914
- ` Component: ${componentName} (lines ${target.componentStart}-${target.componentEnd})`
915
- );
916
- console.log(` Target element: lines ${target.startLine}-${target.endLine}`);
917
- console.log(
918
- ` Element context: tagName=${elementContext == null ? void 0 : elementContext.tagName}, nthOfType=${elementContext == null ? void 0 : elementContext.nthOfType}`
919
- );
920
- const foundLines = content.split("\n").slice(
921
- target.startLine - 1,
922
- Math.min(target.startLine + 2, target.endLine)
923
- );
924
- console.log(` Preview: ${(_a = foundLines[0]) == null ? void 0 : _a.trim()}`);
925
- console.log(
926
- ` textContent="${elementContext == null ? void 0 : elementContext.textContent}", className="${elementContext == null ? void 0 : elementContext.className}"`
927
- );
928
1052
  const lines = content.split("\n");
929
1053
  const componentLines = lines.slice(
930
1054
  target.componentStart - 1,
931
1055
  target.componentEnd
932
1056
  );
933
1057
  const preview = componentLines.join("\n");
1058
+ let parentInstance = null;
1059
+ if (parentDebugStack) {
1060
+ try {
1061
+ let resolvedParentPath = parentFilePath;
1062
+ let resolvedParentLine = parentLine;
1063
+ let resolvedParentComponentName = parentComponentName;
1064
+ if (!resolvedParentComponentName && parentDebugStack) {
1065
+ resolvedParentComponentName = extractComponentNameFromStack(parentDebugStack) || "";
1066
+ }
1067
+ if (parentDebugStack) {
1068
+ const compiledPos = parseDebugStack(parentDebugStack);
1069
+ if (compiledPos) {
1070
+ const originalPos = await resolveOriginalPosition(
1071
+ compiledPos,
1072
+ projectRoot
1073
+ );
1074
+ if (originalPos) {
1075
+ resolvedParentPath = originalPos.source;
1076
+ resolvedParentLine = originalPos.line;
1077
+ }
1078
+ }
1079
+ }
1080
+ const normalizedParentPath = normalizePath(resolvedParentPath);
1081
+ const absoluteParentPath = await resolveFilePath(
1082
+ projectRoot,
1083
+ normalizedParentPath
1084
+ );
1085
+ if (absoluteParentPath && resolvedParentComponentName) {
1086
+ const parentContent = await fs.readFile(absoluteParentPath, "utf-8");
1087
+ const parentAst = parseFile(parentContent);
1088
+ if (parentAst) {
1089
+ let nthOfType2 = void 0;
1090
+ if (childKey) {
1091
+ const keyAsNumber = parseInt(childKey, 10);
1092
+ if (!isNaN(keyAsNumber)) {
1093
+ nthOfType2 = keyAsNumber + 1;
1094
+ }
1095
+ }
1096
+ const parentTarget = findTargetElement(parentAst, parentContent, {
1097
+ componentName: resolvedParentComponentName,
1098
+ lineNumber: 0,
1099
+ // Don't use line number - rely on element context to find correct instance
1100
+ elementContext: {
1101
+ tagName: componentName,
1102
+ // Search for child component usage
1103
+ nthOfType: nthOfType2,
1104
+ // Find specific instance if key is numeric
1105
+ textContent: textContent || void 0
1106
+ // Use text content to match the specific instance
1107
+ }
1108
+ });
1109
+ if (parentTarget) {
1110
+ const parentLines = parentContent.split("\n");
1111
+ const parentComponentLines = parentLines.slice(
1112
+ parentTarget.componentStart - 1,
1113
+ parentTarget.componentEnd
1114
+ );
1115
+ parentInstance = {
1116
+ filePath: resolvedParentPath,
1117
+ content: parentComponentLines.join("\n"),
1118
+ lineStart: parentTarget.componentStart,
1119
+ lineEnd: parentTarget.componentEnd,
1120
+ usageLineStart: parentTarget.startLine,
1121
+ usageLineEnd: parentTarget.endLine,
1122
+ componentName: resolvedParentComponentName
1123
+ };
1124
+ }
1125
+ }
1126
+ }
1127
+ } catch (error) {
1128
+ console.error("Error resolving parent instance:", error);
1129
+ }
1130
+ }
934
1131
  return server.NextResponse.json({
935
1132
  success: true,
936
1133
  content: preview,
@@ -938,8 +1135,10 @@ async function handleRead(req) {
938
1135
  lineEnd: target.componentEnd,
939
1136
  targetStartLine: target.startLine,
940
1137
  targetEndLine: target.endLine,
941
- componentName
1138
+ componentName,
942
1139
  // Return the actual component name parsed from code
1140
+ parentInstance
1141
+ // Optional: where this component is used
943
1142
  });
944
1143
  }
945
1144
  async function handleUndo(req) {
@@ -995,37 +1194,58 @@ async function handleResolve(req) {
995
1194
  { status: 400 }
996
1195
  );
997
1196
  }
998
- const compiledPos = parseDebugStack(debugStack);
999
- if (!compiledPos) {
1000
- console.error("Could not parse debug stack:", debugStack);
1001
- return server.NextResponse.json(
1002
- { success: false, error: "Could not parse stack" },
1003
- { status: 422 }
1197
+ const compiledFrames = parseDebugStackFrames(debugStack);
1198
+ if (compiledFrames.length === 0) {
1199
+ const compiledPos = parseDebugStack(debugStack);
1200
+ if (!compiledPos) {
1201
+ console.error("Could not parse debug stack:", debugStack);
1202
+ return server.NextResponse.json(
1203
+ { success: false, error: "Could not parse stack" },
1204
+ { status: 422 }
1205
+ );
1206
+ }
1207
+ const originalPos = await resolveOriginalPosition(
1208
+ compiledPos,
1209
+ process.cwd()
1004
1210
  );
1211
+ if (!originalPos) {
1212
+ return server.NextResponse.json(
1213
+ { success: false, error: "Source map lookup failed" },
1214
+ { status: 404 }
1215
+ );
1216
+ }
1217
+ return server.NextResponse.json({
1218
+ success: true,
1219
+ filePath: originalPos.source,
1220
+ lineNumber: originalPos.line,
1221
+ columnNumber: originalPos.column ?? 0
1222
+ });
1005
1223
  }
1006
- console.log("Parsed compiled position:", compiledPos);
1007
- const originalPos = await resolveOriginalPosition(
1008
- compiledPos,
1009
- process.cwd()
1010
- );
1011
- if (!originalPos) {
1012
- console.error(
1013
- "Source map lookup failed for:",
1014
- compiledPos.filePath,
1015
- "at line",
1016
- compiledPos.line
1017
- );
1224
+ const resolvedFrames = [];
1225
+ for (const frame of compiledFrames) {
1226
+ const originalPos = await resolveOriginalPosition(frame, process.cwd());
1227
+ if (originalPos) {
1228
+ resolvedFrames.push({
1229
+ filePath: originalPos.source,
1230
+ lineNumber: originalPos.line,
1231
+ columnNumber: originalPos.column ?? 0
1232
+ });
1233
+ }
1234
+ }
1235
+ if (resolvedFrames.length === 0) {
1018
1236
  return server.NextResponse.json(
1019
- { success: false, error: "Source map lookup failed" },
1237
+ { success: false, error: "Source map lookup failed for all frames" },
1020
1238
  { status: 404 }
1021
1239
  );
1022
1240
  }
1023
- console.log("Resolved original position:", originalPos);
1241
+ console.log("Resolved frames:", resolvedFrames);
1024
1242
  return server.NextResponse.json({
1025
1243
  success: true,
1026
- filePath: originalPos.source,
1027
- lineNumber: originalPos.line,
1028
- columnNumber: originalPos.column ?? 0
1244
+ filePath: resolvedFrames[0].filePath,
1245
+ lineNumber: resolvedFrames[0].lineNumber,
1246
+ columnNumber: resolvedFrames[0].columnNumber,
1247
+ frames: resolvedFrames
1248
+ // [componentDefinition, parentInstance]
1029
1249
  });
1030
1250
  } catch (error) {
1031
1251
  console.error("Source resolve error:", error);
@@ -1110,6 +1330,209 @@ async function handleValidateSession(req) {
1110
1330
  );
1111
1331
  }
1112
1332
  }
1333
+ const suggestionCache = /* @__PURE__ */ new Map();
1334
+ const CACHE_TTL = 3e4;
1335
+ function getCacheKey(params) {
1336
+ return `${params.componentName}:${params.elementTag || "div"}:${params.lastSuggestion || ""}`;
1337
+ }
1338
+ function getCachedSuggestions(key) {
1339
+ const cached = suggestionCache.get(key);
1340
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
1341
+ return cached.suggestions;
1342
+ }
1343
+ suggestionCache.delete(key);
1344
+ return null;
1345
+ }
1346
+ function cacheSuggestions(key, suggestions) {
1347
+ suggestionCache.set(key, { suggestions, timestamp: Date.now() });
1348
+ if (suggestionCache.size > 100) {
1349
+ const oldestKeys = Array.from(suggestionCache.entries()).sort((a, b) => a[1].timestamp - b[1].timestamp).slice(0, 20).map(([key2]) => key2);
1350
+ oldestKeys.forEach((key2) => suggestionCache.delete(key2));
1351
+ }
1352
+ }
1353
+ const DEFAULT_SUGGESTIONS = [
1354
+ "Add padding",
1355
+ "Change color",
1356
+ "Make larger",
1357
+ "Add shadow",
1358
+ "Round corners",
1359
+ "Center content",
1360
+ "Add hover effect",
1361
+ "Adjust spacing"
1362
+ ];
1363
+ async function handleSuggestions(req) {
1364
+ const devModeError = validateDevMode();
1365
+ if (devModeError) return devModeError;
1366
+ try {
1367
+ const { searchParams } = new URL(req.url);
1368
+ const componentName = searchParams.get("componentName") || "Component";
1369
+ const elementTag = searchParams.get("elementTag") || void 0;
1370
+ const className = searchParams.get("className") || void 0;
1371
+ const textContent = searchParams.get("textContent") || void 0;
1372
+ const lastSuggestion = searchParams.get("lastSuggestion") || void 0;
1373
+ const editHistoryStr = searchParams.get("editHistory") || void 0;
1374
+ const excludedSuggestionsStr = searchParams.get("excludedSuggestions") || void 0;
1375
+ let editHistory = [];
1376
+ if (editHistoryStr) {
1377
+ try {
1378
+ editHistory = JSON.parse(editHistoryStr);
1379
+ } catch (e) {
1380
+ }
1381
+ }
1382
+ let excludedSuggestions = [];
1383
+ if (excludedSuggestionsStr) {
1384
+ try {
1385
+ excludedSuggestions = JSON.parse(excludedSuggestionsStr);
1386
+ } catch (e) {
1387
+ }
1388
+ }
1389
+ const cacheKey = getCacheKey({ componentName, elementTag, lastSuggestion });
1390
+ const cached = excludedSuggestions.length === 0 ? getCachedSuggestions(cacheKey) : null;
1391
+ if (cached) {
1392
+ return server.NextResponse.json({
1393
+ success: true,
1394
+ suggestions: cached
1395
+ });
1396
+ }
1397
+ const suggestions = await generateSuggestions({
1398
+ componentName,
1399
+ elementTag,
1400
+ className,
1401
+ textContent,
1402
+ editHistory,
1403
+ lastSuggestion,
1404
+ excludedSuggestions
1405
+ });
1406
+ if (suggestions && suggestions.length > 0) {
1407
+ cacheSuggestions(cacheKey, suggestions);
1408
+ return server.NextResponse.json({
1409
+ success: true,
1410
+ suggestions
1411
+ });
1412
+ }
1413
+ return server.NextResponse.json({
1414
+ success: true,
1415
+ suggestions: DEFAULT_SUGGESTIONS
1416
+ });
1417
+ } catch (error) {
1418
+ return server.NextResponse.json(
1419
+ {
1420
+ success: false,
1421
+ error: String(error),
1422
+ suggestions: DEFAULT_SUGGESTIONS
1423
+ // Fallback
1424
+ },
1425
+ { status: 500 }
1426
+ );
1427
+ }
1428
+ }
1429
+ async function generateSuggestions(options) {
1430
+ const {
1431
+ componentName,
1432
+ elementTag,
1433
+ className,
1434
+ textContent,
1435
+ editHistory,
1436
+ lastSuggestion,
1437
+ excludedSuggestions = []
1438
+ } = options;
1439
+ const apiKey = process.env.ANTHROPIC_API_KEY;
1440
+ if (!apiKey) {
1441
+ return null;
1442
+ }
1443
+ const model = new anthropic.ChatAnthropic({
1444
+ apiKey,
1445
+ modelName: "claude-haiku-4-5-20251001",
1446
+ maxTokens: 1024,
1447
+ temperature: 0.3
1448
+ // Slightly creative for variety
1449
+ });
1450
+ const systemPrompt = `You are a UI/UX expert suggesting quick edits for React components.
1451
+
1452
+ Generate 6-8 concise, actionable suggestions that a developer might want to make next.
1453
+
1454
+ RULES:
1455
+ - Each suggestion must be 2-6 words (e.g., "Add padding", "Make text larger")
1456
+ - Focus on common UI improvements: spacing, colors, sizing, layout, shadows, borders, typography
1457
+ - Consider the element type (${elementTag || "element"})
1458
+ - Output ONLY a JSON array of strings, no explanations, no markdown fences
1459
+ ${excludedSuggestions.length > 0 ? `- DO NOT suggest any of these (user wants different options): ${excludedSuggestions.join(
1460
+ ", "
1461
+ )}` : ""}
1462
+
1463
+ ${lastSuggestion ? `IMPORTANT - LAST EDIT CONTEXT:
1464
+ The user just applied: "${lastSuggestion}"
1465
+ Your suggestions MUST be direct follow-ups/refinements of this last change:
1466
+ - If it was "Add padding" → suggest "More padding", "Less padding", "Add vertical padding only"
1467
+ - If it was "Make it blue" → suggest "Darker blue", "Lighter blue", "Change to navy blue"
1468
+ - If it was "Increase font size" → suggest "Decrease font size", "Make even larger", "Adjust line height"
1469
+ - 4-6 suggestions should be variations/refinements of the last edit
1470
+ - 2-4 suggestions can be other related improvements
1471
+
1472
+ Generate follow-up suggestions that let the user iteratively refine what they just did.` : `Generate varied initial suggestions covering different aspects: layout, colors, spacing, shadows, typography, etc.`}
1473
+
1474
+ Example output format:
1475
+ ["Add hover effect", "Increase padding", "Make corners rounder", "Change to flex row", "Add drop shadow", "Adjust font size"]`;
1476
+ let userPrompt = `Element: <${elementTag || "div"}>`;
1477
+ if (className) {
1478
+ userPrompt += `
1479
+ Classes: ${className}`;
1480
+ }
1481
+ if (textContent) {
1482
+ userPrompt += `
1483
+ Text: "${textContent.slice(0, 50)}"`;
1484
+ }
1485
+ userPrompt += `
1486
+ Component: ${componentName}`;
1487
+ if (editHistory && editHistory.length > 0) {
1488
+ userPrompt += `
1489
+
1490
+ Recent edits:
1491
+ `;
1492
+ editHistory.slice(-3).forEach((item, idx) => {
1493
+ userPrompt += `${idx + 1}. ${item.suggestion} ${item.success ? "✓" : "✗"}
1494
+ `;
1495
+ });
1496
+ } else {
1497
+ userPrompt += `
1498
+
1499
+ No previous edits.`;
1500
+ }
1501
+ if (lastSuggestion) {
1502
+ userPrompt += `
1503
+
1504
+ **LAST EDIT APPLIED:** "${lastSuggestion}"`;
1505
+ userPrompt += `
1506
+
1507
+ Generate 6-8 follow-up suggestions (mostly variations of the last edit):`;
1508
+ } else {
1509
+ userPrompt += `
1510
+
1511
+ Generate 6-8 initial suggestions:`;
1512
+ }
1513
+ try {
1514
+ const response = await model.invoke([
1515
+ new messages.SystemMessage(systemPrompt),
1516
+ new messages.HumanMessage(userPrompt)
1517
+ ]);
1518
+ let content = typeof response.content === "string" ? response.content : String(response.content);
1519
+ content = content.trim();
1520
+ content = content.replace(/^```json?\s*/gm, "").replace(/\s*```$/gm, "");
1521
+ const suggestions = JSON.parse(content);
1522
+ if (Array.isArray(suggestions)) {
1523
+ const validSuggestions = suggestions.filter((s) => typeof s === "string").map((s) => s.trim()).filter((s) => {
1524
+ const words = s.split(/\s+/).length;
1525
+ return words >= 1 && words <= 15;
1526
+ }).slice(0, 8);
1527
+ if (validSuggestions.length >= 4) {
1528
+ return validSuggestions;
1529
+ }
1530
+ }
1531
+ return null;
1532
+ } catch (e) {
1533
+ return null;
1534
+ }
1535
+ }
1113
1536
  async function handleAIEditorRequest(req, context) {
1114
1537
  const { path: path2 } = await context.params;
1115
1538
  const endpoint = path2[0];
@@ -1133,12 +1556,17 @@ async function handleAIEditorRequest(req, context) {
1133
1556
  case "validate-session":
1134
1557
  if (method === "POST") return handleValidateSession(req);
1135
1558
  break;
1559
+ case "suggestions":
1560
+ if (method === "GET") return handleSuggestions(req);
1561
+ break;
1136
1562
  }
1137
1563
  return server.NextResponse.json(
1138
1564
  { error: `Unknown endpoint: ${endpoint}` },
1139
1565
  { status: 404 }
1140
1566
  );
1141
1567
  }
1568
+ exports.extractComponentName = extractComponentName;
1569
+ exports.extractComponentNameFromStack = extractComponentNameFromStack;
1142
1570
  exports.fileExists = fileExists$1;
1143
1571
  exports.findTargetElement = findTargetElement;
1144
1572
  exports.getAttributeValue = getAttributeValue;
@@ -1149,14 +1577,17 @@ exports.handleAbsolutePath = handleAbsolutePath;
1149
1577
  exports.handleEdit = handleEdit;
1150
1578
  exports.handleRead = handleRead;
1151
1579
  exports.handleResolve = handleResolve;
1580
+ exports.handleSuggestions = handleSuggestions;
1152
1581
  exports.handleUndo = handleUndo;
1153
1582
  exports.handleValidateSession = handleValidateSession;
1154
1583
  exports.isPathSecure = isPathSecure;
1155
1584
  exports.normalizePath = normalizePath;
1156
1585
  exports.parseDebugStack = parseDebugStack;
1586
+ exports.parseDebugStackFrames = parseDebugStackFrames;
1157
1587
  exports.parseFile = parseFile;
1158
1588
  exports.resolveFilePath = resolveFilePath;
1159
1589
  exports.resolveOriginalPosition = resolveOriginalPosition;
1160
1590
  exports.scoreElementMatch = scoreElementMatch;
1161
1591
  exports.validateDevMode = validateDevMode;
1162
- //# sourceMappingURL=index-DnoYi4f8.cjs.map
1592
+ exports.validateGeneratedCode = validateGeneratedCode;
1593
+ //# sourceMappingURL=index-CNJqd4EQ.cjs.map