next-ai-editor 0.1.1 → 0.1.2

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-D-w9-GZb.js → AIEditorProvider-CFFnEtEB.js} +849 -402
  2. package/dist/AIEditorProvider-CFFnEtEB.js.map +1 -0
  3. package/dist/{AIEditorProvider-Bs9zUVrL.cjs → AIEditorProvider-CmiACRfw.cjs} +825 -378
  4. package/dist/AIEditorProvider-CmiACRfw.cjs.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-BFa7H-uO.js → index-3OMXRwpD.js} +601 -224
  16. package/dist/index-3OMXRwpD.js.map +1 -0
  17. package/dist/{index-DnoYi4f8.cjs → index-9QODCOgD.cjs} +595 -218
  18. package/dist/index-9QODCOgD.cjs.map +1 -0
  19. package/dist/index.cjs +6 -2
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.js +14 -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 +5 -0
  38. package/dist/server/utils/source-map.d.ts.map +1 -1
  39. package/dist/server.cjs +5 -1
  40. package/dist/server.cjs.map +1 -1
  41. package/dist/server.js +13 -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,74 @@ 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
+ }
111
+ }
112
+ });
113
+ return componentName;
114
+ }
115
+ function validateGeneratedCode(newCode, originalCode, fileContent) {
116
+ try {
117
+ const isFullComponent = /^(export\s+)?(default\s+)?function\s+\w+/.test(newCode.trim()) || /^(export\s+)?(default\s+)?const\s+\w+\s*=/.test(newCode.trim());
118
+ if (isFullComponent) {
119
+ let codeToValidate = newCode;
120
+ if (fileContent) {
121
+ const interfaceMatches = fileContent.match(
122
+ /^(interface|type)\s+\w+[^}]*\}/gm
123
+ );
124
+ if (interfaceMatches) {
125
+ codeToValidate = interfaceMatches.join("\n\n") + "\n\n" + newCode;
126
+ }
127
+ }
128
+ parser__namespace.parse(codeToValidate, {
129
+ sourceType: "module",
130
+ plugins: ["jsx", "typescript"]
131
+ });
132
+ } else {
133
+ const wrapped = `function _() { return (${newCode}); }`;
134
+ parser__namespace.parse(wrapped, {
135
+ sourceType: "module",
136
+ plugins: ["jsx", "typescript"]
137
+ });
138
+ }
139
+ } catch (e) {
140
+ console.error("Generated code parse error:", e);
141
+ return false;
142
+ }
143
+ const origBraces = (originalCode.match(/[{}]/g) || []).length;
144
+ const newBraces = (newCode.match(/[{}]/g) || []).length;
145
+ const origTags = (originalCode.match(/[<>]/g) || []).length;
146
+ const newTags = (newCode.match(/[<>]/g) || []).length;
147
+ if (Math.abs(origBraces - newBraces) > 4 || Math.abs(origTags - newTags) > 4) {
148
+ console.warn(
149
+ `Structure changed significantly: braces ${origBraces}->${newBraces}, tags ${origTags}->${newTags}`
150
+ );
151
+ }
152
+ return true;
153
+ }
87
154
  function findTargetElement(ast, fileContent, options) {
88
155
  const { componentName, lineNumber, elementContext } = options;
89
- const matches = [];
90
156
  let componentNode = null;
91
157
  let componentStart = 0;
92
158
  let componentEnd = Infinity;
@@ -137,6 +203,7 @@ function findTargetElement(ast, fileContent, options) {
137
203
  componentEnd = fallback.end;
138
204
  }
139
205
  const allElementsByTag = /* @__PURE__ */ new Map();
206
+ const elementsAtLine = [];
140
207
  traverse(ast, {
141
208
  JSXElement(path2) {
142
209
  const loc = path2.node.loc;
@@ -152,59 +219,89 @@ function findTargetElement(ast, fileContent, options) {
152
219
  }
153
220
  allElementsByTag.get(tagName).push({ node: path2.node, startLine, endLine, score: 0 });
154
221
  }
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
- });
222
+ if (startLine === lineNumber) {
223
+ elementsAtLine.push({ node: path2.node, startLine, endLine, score: 0 });
167
224
  }
168
225
  }
169
226
  });
170
- if (matches.length === 0) {
171
- if (componentNode && componentStart > 0) {
227
+ if (elementsAtLine.length > 0) {
228
+ if (elementsAtLine.length === 1) {
229
+ const target = elementsAtLine[0];
172
230
  return {
173
- startLine: componentStart,
174
- endLine: componentEnd,
231
+ startLine: target.startLine,
232
+ endLine: target.endLine,
175
233
  componentStart,
176
234
  componentEnd
177
235
  };
178
236
  }
179
- return null;
237
+ if (elementContext) {
238
+ for (const elem of elementsAtLine) {
239
+ if (t__namespace.isJSXElement(elem.node)) {
240
+ const score = scoreElementMatch(elem.node, elementContext, fileContent);
241
+ elem.score = score;
242
+ }
243
+ }
244
+ elementsAtLine.sort((a, b) => b.score - a.score);
245
+ if (elementsAtLine[0].score > 0) {
246
+ return {
247
+ startLine: elementsAtLine[0].startLine,
248
+ endLine: elementsAtLine[0].endLine,
249
+ componentStart,
250
+ componentEnd
251
+ };
252
+ }
253
+ }
254
+ return {
255
+ startLine: elementsAtLine[0].startLine,
256
+ endLine: elementsAtLine[0].endLine,
257
+ componentStart,
258
+ componentEnd
259
+ };
180
260
  }
181
261
  if ((elementContext == null ? void 0 : elementContext.nthOfType) && elementContext.tagName) {
182
262
  const allOfTag = allElementsByTag.get(elementContext.tagName);
183
263
  if (allOfTag && allOfTag.length >= elementContext.nthOfType) {
184
264
  const target = allOfTag[elementContext.nthOfType - 1];
185
- console.log(
186
- ` Using nthOfType=${elementContext.nthOfType}: found ${allOfTag.length} <${elementContext.tagName}> elements`
187
- );
188
265
  return {
189
266
  startLine: target.startLine,
190
267
  endLine: target.endLine,
191
268
  componentStart,
192
269
  componentEnd
193
270
  };
194
- } else {
195
- console.log(
196
- ` nthOfType=${elementContext.nthOfType} but only found ${(allOfTag == null ? void 0 : allOfTag.length) || 0} <${elementContext.tagName}> elements`
197
- );
198
271
  }
199
272
  }
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
- };
273
+ const nearbyElements = [];
274
+ traverse(ast, {
275
+ JSXElement(path2) {
276
+ const loc = path2.node.loc;
277
+ if (!loc) return;
278
+ const startLine = loc.start.line;
279
+ const endLine = loc.end.line;
280
+ if (startLine < componentStart || endLine > componentEnd) return;
281
+ if (Math.abs(startLine - lineNumber) <= 5) {
282
+ const score = elementContext ? scoreElementMatch(path2.node, elementContext, fileContent) : 100 - Math.abs(startLine - lineNumber);
283
+ nearbyElements.push({ node: path2.node, startLine, endLine, score });
284
+ }
285
+ }
286
+ });
287
+ if (nearbyElements.length > 0) {
288
+ nearbyElements.sort((a, b) => b.score - a.score);
289
+ return {
290
+ startLine: nearbyElements[0].startLine,
291
+ endLine: nearbyElements[0].endLine,
292
+ componentStart,
293
+ componentEnd
294
+ };
295
+ }
296
+ if (componentNode && componentStart > 0) {
297
+ return {
298
+ startLine: componentStart,
299
+ endLine: componentEnd,
300
+ componentStart,
301
+ componentEnd
302
+ };
303
+ }
304
+ return null;
208
305
  }
209
306
  function scoreElementMatch(node, context, fileContent) {
210
307
  let score = 0;
@@ -288,7 +385,8 @@ async function handleEdit(req) {
288
385
  componentName,
289
386
  suggestion,
290
387
  elementContext,
291
- editHistory
388
+ editHistory,
389
+ parentInstance
292
390
  } = body;
293
391
  const projectRoot = process.cwd();
294
392
  const normalizedPath = normalizePath(filePath);
@@ -300,7 +398,6 @@ async function handleEdit(req) {
300
398
  );
301
399
  }
302
400
  const fileContent = await fs.readFile(absolutePath, "utf-8");
303
- console.log(`[/edit] componentName="${componentName}", filePath="${filePath}"`);
304
401
  const ast = parseFile(fileContent);
305
402
  if (!ast) {
306
403
  return server.NextResponse.json(
@@ -319,20 +416,10 @@ async function handleEdit(req) {
319
416
  { status: 400 }
320
417
  );
321
418
  }
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
419
  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
420
  const targetCode = lines.slice(target.startLine - 1, target.endLine).join("\n");
333
421
  const baseIndentation = ((_a = lines[target.startLine - 1].match(/^(\s*)/)) == null ? void 0 : _a[1]) || "";
334
422
  if (target.componentStart <= 0 || target.componentEnd === Infinity) {
335
- console.error("❌ Invalid component bounds detected");
336
423
  return server.NextResponse.json({
337
424
  success: false,
338
425
  error: `Could not determine component bounds. Component: ${target.componentStart}-${target.componentEnd}`
@@ -370,7 +457,8 @@ ${foundElementCode}`);
370
457
  targetEndLine: target.endLine,
371
458
  componentStart: target.componentStart,
372
459
  componentEnd: target.componentEnd,
373
- editHistory: editHistory || []
460
+ editHistory: editHistory || [],
461
+ parentInstance
374
462
  });
375
463
  if (!newCode) {
376
464
  return server.NextResponse.json({
@@ -378,38 +466,56 @@ ${foundElementCode}`);
378
466
  error: "AI failed to generate valid edit"
379
467
  });
380
468
  }
381
- console.log("Raw AI response length:", newCode.length);
382
- console.log("Looking for // FULL_COMPONENT marker...");
469
+ const parentInstanceMatch = newCode.match(/\/\/ EDIT_PARENT_INSTANCE\s*\n([\s\S]+)/);
470
+ if (parentInstanceMatch && parentInstance) {
471
+ const parentCode = parentInstanceMatch[1].trim();
472
+ const parentNormalizedPath = normalizePath(parentInstance.filePath);
473
+ const parentAbsolutePath = await resolveFilePath(projectRoot, parentNormalizedPath);
474
+ if (!parentAbsolutePath) {
475
+ return server.NextResponse.json({
476
+ success: false,
477
+ error: `Parent file not found: ${parentNormalizedPath}`
478
+ });
479
+ }
480
+ const parentFileContent = await fs.readFile(parentAbsolutePath, "utf-8");
481
+ const parentLines = parentFileContent.split("\n");
482
+ const newParentLines = [...parentLines];
483
+ newParentLines.splice(
484
+ parentInstance.lineStart - 1,
485
+ parentInstance.lineEnd - parentInstance.lineStart + 1,
486
+ ...parentCode.split("\n")
487
+ );
488
+ await fs.writeFile(parentAbsolutePath, newParentLines.join("\n"), "utf-8");
489
+ return server.NextResponse.json({
490
+ success: true,
491
+ fileSnapshot: parentFileContent,
492
+ generatedCode: parentCode,
493
+ modifiedLines: {
494
+ start: parentInstance.lineStart,
495
+ end: parentInstance.lineEnd
496
+ },
497
+ editedFile: parentInstance.filePath
498
+ // Indicate which file was edited
499
+ });
500
+ }
383
501
  const fullComponentMatch = newCode.match(/\/\/ FULL_COMPONENT\s*\n([\s\S]+)/);
384
502
  let codeToApply = newCode;
385
503
  let startLineToReplace = target.startLine;
386
504
  let endLineToReplace = target.endLine;
505
+ const isFullComponentDeclaration = /^(export\s+)?(default\s+)?function\s+\w+/.test(newCode.trim()) || /^(export\s+)?const\s+\w+\s*=/.test(newCode.trim());
387
506
  if (fullComponentMatch) {
388
- console.log("Found // FULL_COMPONENT marker, extracting full component code");
389
507
  codeToApply = fullComponentMatch[1].trim();
390
508
  startLineToReplace = target.componentStart;
391
509
  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
- );
510
+ } else if (isFullComponentDeclaration && target.startLine !== target.componentStart) {
511
+ codeToApply = newCode;
512
+ startLineToReplace = target.componentStart;
513
+ endLineToReplace = target.componentEnd;
402
514
  }
403
- console.log("Code to apply (first 200 chars):", codeToApply.substring(0, 200));
404
515
  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
516
  return server.NextResponse.json({
411
517
  success: false,
412
- error: "Generated code is invalid - check server logs for details"
518
+ error: "Generated code is invalid"
413
519
  });
414
520
  }
415
521
  const newLines = [...lines];
@@ -419,7 +525,6 @@ ${foundElementCode}`);
419
525
  ...codeToApply.split("\n")
420
526
  );
421
527
  await fs.writeFile(absolutePath, newLines.join("\n"), "utf-8");
422
- console.log(`✅ Updated ${normalizedPath}`);
423
528
  return server.NextResponse.json({
424
529
  success: true,
425
530
  fileSnapshot: fileContent,
@@ -429,10 +534,11 @@ ${foundElementCode}`);
429
534
  modifiedLines: {
430
535
  start: startLineToReplace,
431
536
  end: endLineToReplace
432
- }
537
+ },
538
+ editedFile: filePath
539
+ // Indicate which file was edited
433
540
  });
434
541
  } catch (error) {
435
- console.error("AI Editor error:", error);
436
542
  return server.NextResponse.json(
437
543
  { success: false, error: String(error) },
438
544
  { status: 500 }
@@ -444,7 +550,8 @@ async function generateEdit(options) {
444
550
  fullComponentCode,
445
551
  suggestion,
446
552
  baseIndentation,
447
- editHistory
553
+ editHistory,
554
+ parentInstance
448
555
  } = options;
449
556
  const apiKey = process.env.ANTHROPIC_API_KEY;
450
557
  if (!apiKey) throw new Error("ANTHROPIC_API_KEY not set");
@@ -457,25 +564,29 @@ async function generateEdit(options) {
457
564
  const systemPrompt = `You are a precise code editor for React/JSX components.
458
565
 
459
566
  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
567
+ - Component Definition: Full code of the clicked component with line annotations
568
+ * The clicked element is marked with "// ← CLICKED ELEMENT STARTS" and "// ← CLICKED ELEMENT ENDS"
569
+ * These are just annotations - NOT part of the actual code
570
+ ${parentInstance ? `- Parent Instance: Where this component is used, with "// COMPONENT USAGE" marking the usage line` : ""}
464
571
 
465
- YOUR TASK:
466
- Modify ONLY the marked element (unless the request requires changing its parent/wrapper).
572
+ YOUR DECISION - Choose ONE approach based on the user's request:
467
573
 
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)
574
+ 1. EDIT COMPONENT DEFINITION (most common):
575
+ - Modify styling, layout, or behavior that should apply to ALL instances
576
+ - Example requests: "make the button blue", "add padding", "change font size"
577
+ - Output: Just the modified element OR "// FULL_COMPONENT\\n" + complete modified component
578
+
579
+ ${parentInstance ? `2. EDIT PARENT INSTANCE (when user wants to modify THIS specific usage):
580
+ - Change props, text, or configuration for THIS specific instance only
581
+ - Example requests: "change this title to...", "remove this card", "add another button here"
582
+ - Output: "// EDIT_PARENT_INSTANCE\\n" + complete modified parent component` : ""}
471
583
 
472
584
  RULES:
473
585
  - Output ONLY code, no explanations
474
586
  - No markdown fences
475
- - Do NOT include the "// ← CLICKED ELEMENT" annotation comments in your output
587
+ - Do NOT include annotation comments in your output
476
588
  - Preserve indentation
477
- - Make minimal changes
478
- - Do NOT modify unrelated elements`;
589
+ - Make minimal changes`;
479
590
  let userPrompt = "";
480
591
  if (editHistory.length > 0) {
481
592
  userPrompt += "Previous edits made to this element:\n";
@@ -485,16 +596,36 @@ RULES:
485
596
  });
486
597
  userPrompt += "\n";
487
598
  }
488
- userPrompt += `Component (clicked element is annotated with "// ← CLICKED ELEMENT" comments):
599
+ userPrompt += `COMPONENT DEFINITION (clicked element is annotated):
489
600
 
490
601
  \`\`\`jsx
491
602
  ${fullComponentCode}
492
603
  \`\`\`
604
+ `;
605
+ if (parentInstance) {
606
+ const parentLines = parentInstance.content.split("\n");
607
+ const annotatedParentLines = parentLines.map((line, idx) => {
608
+ const lineNum = parentInstance.lineStart + idx;
609
+ const isUsageLine = lineNum >= parentInstance.usageLineStart && lineNum <= parentInstance.usageLineEnd;
610
+ return line + (isUsageLine ? " // ← COMPONENT USAGE" : "");
611
+ });
612
+ userPrompt += `
613
+ PARENT INSTANCE (where component is used - in ${parentInstance.filePath}):
493
614
 
615
+ \`\`\`jsx
616
+ ${annotatedParentLines.join("\n")}
617
+ \`\`\`
618
+ `;
619
+ }
620
+ userPrompt += `
494
621
  User request: "${suggestion}"
495
- ${editHistory.length > 0 ? "\n(Build upon previous changes)" : ""}
622
+ ${editHistory.length > 0 ? "(Build upon previous changes)" : ""}
623
+
624
+ ${parentInstance ? `Decide whether to:
625
+ 1. Edit the component definition (for changes that affect ALL instances)
626
+ 2. Edit the parent instance (for changes specific to THIS usage)
496
627
 
497
- Modify the annotated element to fulfill this request. Remember: do NOT include the annotation comments in your output.`;
628
+ 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
629
  try {
499
630
  const response = await model.invoke([
500
631
  new messages.SystemMessage(systemPrompt),
@@ -504,12 +635,18 @@ Modify the annotated element to fulfill this request. Remember: do NOT include t
504
635
  code = cleanGeneratedCode(code, baseIndentation);
505
636
  return code || null;
506
637
  } catch (e) {
507
- console.error("AI generation error:", e);
508
638
  return null;
509
639
  }
510
640
  }
511
641
  function cleanGeneratedCode(code, baseIndentation) {
512
642
  var _a, _b, _c;
643
+ const isParentEdit = code.trim().startsWith("// EDIT_PARENT_INSTANCE");
644
+ if (isParentEdit) {
645
+ const marker2 = "// EDIT_PARENT_INSTANCE\n";
646
+ code = code.replace(/^\/\/ EDIT_PARENT_INSTANCE\n?/, "");
647
+ code = code.replace(/^```[\w]*\n?/gm, "").replace(/\n?```$/gm, "").replace(/\s*\/\/ ← COMPONENT USAGE/g, "").trim();
648
+ return marker2 + code;
649
+ }
513
650
  const isFullComponent = code.trim().startsWith("// FULL_COMPONENT");
514
651
  let marker = "";
515
652
  if (isFullComponent) {
@@ -540,43 +677,6 @@ function cleanGeneratedCode(code, baseIndentation) {
540
677
  }
541
678
  return marker + code;
542
679
  }
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
680
  function parseDebugStack(stack) {
581
681
  if (!stack) return null;
582
682
  const stackStr = typeof stack === "string" ? stack : stack.stack || String(stack);
@@ -601,8 +701,8 @@ function parseDebugStack(stack) {
601
701
  chunkId = chunkMatch[1];
602
702
  filePath = filePath.replace(/\?[^:]*$/, "");
603
703
  }
604
- filePath = cleanPathTurbopack(filePath);
605
- if (!shouldSkip(filePath)) {
704
+ filePath = pathUtils.cleanPath(filePath);
705
+ if (!pathUtils.shouldSkipPath(filePath)) {
606
706
  console.log("parseDebugStack extracted:", { filePath, line, column });
607
707
  return { filePath, line, column, chunkId };
608
708
  }
@@ -614,46 +714,49 @@ function parseDebugStack(stack) {
614
714
  );
615
715
  return null;
616
716
  }
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);
717
+ function parseDebugStackFrames(stack) {
718
+ if (!stack) return [];
719
+ const stackStr = typeof stack === "string" ? stack : stack.stack || String(stack);
720
+ const frames = stackStr.split("\n");
721
+ const skipPatterns = [
722
+ "node_modules",
723
+ "SegmentViewNode",
724
+ "LayoutRouter",
725
+ "ErrorBoundary",
726
+ "fakeJSXCallSite",
727
+ "react_stack_bottom_frame"
728
+ ];
729
+ const positions = [];
730
+ for (const frame of frames) {
731
+ if (skipPatterns.some((p) => frame.includes(p))) continue;
732
+ const match = frame.match(/at\s+(\w+)\s+\((.+?):(\d+):(\d+)\)?$/);
733
+ if (match) {
734
+ match[1];
735
+ let filePath = match[2];
736
+ const line = parseInt(match[3], 10);
737
+ const column = parseInt(match[4], 10);
738
+ let chunkId;
739
+ const chunkMatch = filePath.match(/\?([^:]+)$/);
740
+ if (chunkMatch) {
741
+ chunkId = chunkMatch[1];
742
+ filePath = filePath.replace(/\?[^:]*$/, "");
743
+ }
744
+ filePath = pathUtils.cleanPath(filePath);
745
+ if (!pathUtils.shouldSkipPath(filePath)) {
746
+ positions.push({ filePath, line, column, chunkId });
747
+ if (positions.length >= 2) break;
748
+ }
626
749
  }
627
- } catch (e) {
628
- console.warn("Failed to decode URL-encoded path:", cleaned, e);
629
- }
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
750
  }
634
- return cleaned;
635
- }
636
- function cleanPath(p) {
637
- return cleanPathTurbopack(p);
638
- }
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);
644
- }
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));
751
+ console.log(`parseDebugStackFrames extracted ${positions.length} frames:`, positions);
752
+ return positions;
650
753
  }
651
754
  async function resolveOriginalPosition(compiledPos, projectRoot) {
652
755
  try {
653
756
  console.log("resolveOriginalPosition called with:", compiledPos);
654
757
  let compiledFilePath = compiledPos.filePath;
655
- compiledFilePath = cleanPathTurbopack(compiledFilePath);
656
- console.log("After cleanPathTurbopack:", compiledFilePath);
758
+ compiledFilePath = pathUtils.cleanPath(compiledFilePath);
759
+ console.log("After cleanPath:", compiledFilePath);
657
760
  if (compiledFilePath.startsWith("http://") || compiledFilePath.startsWith("https://")) {
658
761
  const url = new URL(compiledFilePath);
659
762
  const pathname = url.pathname;
@@ -770,7 +873,7 @@ async function resolveFromSourceMap(sourceMapPath, compiledPos, projectRoot) {
770
873
  column: adjustedColumn
771
874
  });
772
875
  if (originalPos.source && originalPos.line !== null) {
773
- const source = normalizeSourcePath(
876
+ const source = pathUtils.normalizeSourcePath(
774
877
  originalPos.source || "",
775
878
  projectRoot
776
879
  );
@@ -793,7 +896,7 @@ async function resolveFromSourceMap(sourceMapPath, compiledPos, projectRoot) {
793
896
  column: compiledPos.column
794
897
  });
795
898
  if (originalPos.source && originalPos.line !== null) {
796
- const source = normalizeSourcePath(
899
+ const source = pathUtils.normalizeSourcePath(
797
900
  originalPos.source || "",
798
901
  projectRoot
799
902
  );
@@ -826,7 +929,6 @@ async function getOriginalPositionFromDebugStack(debugStack, projectRoot) {
826
929
  return await resolveOriginalPosition(compiledPos, projectRoot);
827
930
  }
828
931
  async function handleRead(req) {
829
- var _a;
830
932
  const devModeError = validateDevMode();
831
933
  if (devModeError) return devModeError;
832
934
  const { searchParams } = new URL(req.url);
@@ -843,6 +945,11 @@ async function handleRead(req) {
843
945
  textContent: textContent || void 0,
844
946
  className: className || void 0
845
947
  } : void 0;
948
+ const parentFilePath = searchParams.get("parentFilePath") || "";
949
+ const parentLine = parseInt(searchParams.get("parentLine") || "0");
950
+ const parentComponentName = searchParams.get("parentComponentName") || "";
951
+ const parentDebugStack = searchParams.get("parentDebugStack") || "";
952
+ const childKey = searchParams.get("childKey") || "";
846
953
  const projectRoot = process.cwd();
847
954
  if (debugStack) {
848
955
  const compiledPos = parseDebugStack(debugStack);
@@ -873,21 +980,7 @@ async function handleRead(req) {
873
980
  { status: 400 }
874
981
  );
875
982
  }
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
- });
983
+ const componentName = extractComponentName(ast);
891
984
  if (!componentName) {
892
985
  return server.NextResponse.json({
893
986
  success: true,
@@ -909,28 +1002,79 @@ async function handleRead(req) {
909
1002
  lineEnd: content.split("\n").length
910
1003
  });
911
1004
  }
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
1005
  const lines = content.split("\n");
929
1006
  const componentLines = lines.slice(
930
1007
  target.componentStart - 1,
931
1008
  target.componentEnd
932
1009
  );
933
1010
  const preview = componentLines.join("\n");
1011
+ let parentInstance = null;
1012
+ if (parentDebugStack) {
1013
+ try {
1014
+ let resolvedParentPath = parentFilePath;
1015
+ let resolvedParentLine = parentLine;
1016
+ let resolvedParentComponentName = parentComponentName;
1017
+ if (resolvedParentComponentName && parentDebugStack) {
1018
+ const compiledPos = parseDebugStack(parentDebugStack);
1019
+ if (compiledPos) {
1020
+ const originalPos = await resolveOriginalPosition(
1021
+ compiledPos,
1022
+ projectRoot
1023
+ );
1024
+ if (originalPos) {
1025
+ resolvedParentPath = originalPos.source;
1026
+ resolvedParentLine = originalPos.line;
1027
+ }
1028
+ }
1029
+ }
1030
+ const normalizedParentPath = normalizePath(resolvedParentPath);
1031
+ const absoluteParentPath = await resolveFilePath(
1032
+ projectRoot,
1033
+ normalizedParentPath
1034
+ );
1035
+ if (absoluteParentPath && resolvedParentComponentName) {
1036
+ const parentContent = await fs.readFile(absoluteParentPath, "utf-8");
1037
+ const parentAst = parseFile(parentContent);
1038
+ if (parentAst) {
1039
+ let nthOfType2 = void 0;
1040
+ if (childKey) {
1041
+ const keyAsNumber = parseInt(childKey, 10);
1042
+ if (!isNaN(keyAsNumber)) {
1043
+ nthOfType2 = keyAsNumber + 1;
1044
+ }
1045
+ }
1046
+ const parentTarget = findTargetElement(parentAst, parentContent, {
1047
+ componentName: resolvedParentComponentName,
1048
+ lineNumber: 0,
1049
+ // Don't use line number - rely on nthOfType to find correct instance
1050
+ elementContext: {
1051
+ tagName: componentName,
1052
+ // Search for child component usage
1053
+ nthOfType: nthOfType2
1054
+ // Find specific instance if key is numeric
1055
+ }
1056
+ });
1057
+ if (parentTarget) {
1058
+ const parentLines = parentContent.split("\n");
1059
+ const parentComponentLines = parentLines.slice(
1060
+ parentTarget.componentStart - 1,
1061
+ parentTarget.componentEnd
1062
+ );
1063
+ parentInstance = {
1064
+ filePath: resolvedParentPath,
1065
+ content: parentComponentLines.join("\n"),
1066
+ lineStart: parentTarget.componentStart,
1067
+ lineEnd: parentTarget.componentEnd,
1068
+ usageLineStart: parentTarget.startLine,
1069
+ usageLineEnd: parentTarget.endLine,
1070
+ componentName: resolvedParentComponentName
1071
+ };
1072
+ }
1073
+ }
1074
+ }
1075
+ } catch (error) {
1076
+ }
1077
+ }
934
1078
  return server.NextResponse.json({
935
1079
  success: true,
936
1080
  content: preview,
@@ -938,8 +1082,10 @@ async function handleRead(req) {
938
1082
  lineEnd: target.componentEnd,
939
1083
  targetStartLine: target.startLine,
940
1084
  targetEndLine: target.endLine,
941
- componentName
1085
+ componentName,
942
1086
  // Return the actual component name parsed from code
1087
+ parentInstance
1088
+ // Optional: where this component is used
943
1089
  });
944
1090
  }
945
1091
  async function handleUndo(req) {
@@ -995,37 +1141,58 @@ async function handleResolve(req) {
995
1141
  { status: 400 }
996
1142
  );
997
1143
  }
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 }
1144
+ const compiledFrames = parseDebugStackFrames(debugStack);
1145
+ if (compiledFrames.length === 0) {
1146
+ const compiledPos = parseDebugStack(debugStack);
1147
+ if (!compiledPos) {
1148
+ console.error("Could not parse debug stack:", debugStack);
1149
+ return server.NextResponse.json(
1150
+ { success: false, error: "Could not parse stack" },
1151
+ { status: 422 }
1152
+ );
1153
+ }
1154
+ const originalPos = await resolveOriginalPosition(
1155
+ compiledPos,
1156
+ process.cwd()
1004
1157
  );
1158
+ if (!originalPos) {
1159
+ return server.NextResponse.json(
1160
+ { success: false, error: "Source map lookup failed" },
1161
+ { status: 404 }
1162
+ );
1163
+ }
1164
+ return server.NextResponse.json({
1165
+ success: true,
1166
+ filePath: originalPos.source,
1167
+ lineNumber: originalPos.line,
1168
+ columnNumber: originalPos.column ?? 0
1169
+ });
1005
1170
  }
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
- );
1171
+ const resolvedFrames = [];
1172
+ for (const frame of compiledFrames) {
1173
+ const originalPos = await resolveOriginalPosition(frame, process.cwd());
1174
+ if (originalPos) {
1175
+ resolvedFrames.push({
1176
+ filePath: originalPos.source,
1177
+ lineNumber: originalPos.line,
1178
+ columnNumber: originalPos.column ?? 0
1179
+ });
1180
+ }
1181
+ }
1182
+ if (resolvedFrames.length === 0) {
1018
1183
  return server.NextResponse.json(
1019
- { success: false, error: "Source map lookup failed" },
1184
+ { success: false, error: "Source map lookup failed for all frames" },
1020
1185
  { status: 404 }
1021
1186
  );
1022
1187
  }
1023
- console.log("Resolved original position:", originalPos);
1188
+ console.log("Resolved frames:", resolvedFrames);
1024
1189
  return server.NextResponse.json({
1025
1190
  success: true,
1026
- filePath: originalPos.source,
1027
- lineNumber: originalPos.line,
1028
- columnNumber: originalPos.column ?? 0
1191
+ filePath: resolvedFrames[0].filePath,
1192
+ lineNumber: resolvedFrames[0].lineNumber,
1193
+ columnNumber: resolvedFrames[0].columnNumber,
1194
+ frames: resolvedFrames
1195
+ // [componentDefinition, parentInstance]
1029
1196
  });
1030
1197
  } catch (error) {
1031
1198
  console.error("Source resolve error:", error);
@@ -1110,6 +1277,209 @@ async function handleValidateSession(req) {
1110
1277
  );
1111
1278
  }
1112
1279
  }
1280
+ const suggestionCache = /* @__PURE__ */ new Map();
1281
+ const CACHE_TTL = 3e4;
1282
+ function getCacheKey(params) {
1283
+ return `${params.componentName}:${params.elementTag || "div"}:${params.lastSuggestion || ""}`;
1284
+ }
1285
+ function getCachedSuggestions(key) {
1286
+ const cached = suggestionCache.get(key);
1287
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
1288
+ return cached.suggestions;
1289
+ }
1290
+ suggestionCache.delete(key);
1291
+ return null;
1292
+ }
1293
+ function cacheSuggestions(key, suggestions) {
1294
+ suggestionCache.set(key, { suggestions, timestamp: Date.now() });
1295
+ if (suggestionCache.size > 100) {
1296
+ const oldestKeys = Array.from(suggestionCache.entries()).sort((a, b) => a[1].timestamp - b[1].timestamp).slice(0, 20).map(([key2]) => key2);
1297
+ oldestKeys.forEach((key2) => suggestionCache.delete(key2));
1298
+ }
1299
+ }
1300
+ const DEFAULT_SUGGESTIONS = [
1301
+ "Add padding",
1302
+ "Change color",
1303
+ "Make larger",
1304
+ "Add shadow",
1305
+ "Round corners",
1306
+ "Center content",
1307
+ "Add hover effect",
1308
+ "Adjust spacing"
1309
+ ];
1310
+ async function handleSuggestions(req) {
1311
+ const devModeError = validateDevMode();
1312
+ if (devModeError) return devModeError;
1313
+ try {
1314
+ const { searchParams } = new URL(req.url);
1315
+ const componentName = searchParams.get("componentName") || "Component";
1316
+ const elementTag = searchParams.get("elementTag") || void 0;
1317
+ const className = searchParams.get("className") || void 0;
1318
+ const textContent = searchParams.get("textContent") || void 0;
1319
+ const lastSuggestion = searchParams.get("lastSuggestion") || void 0;
1320
+ const editHistoryStr = searchParams.get("editHistory") || void 0;
1321
+ const excludedSuggestionsStr = searchParams.get("excludedSuggestions") || void 0;
1322
+ let editHistory = [];
1323
+ if (editHistoryStr) {
1324
+ try {
1325
+ editHistory = JSON.parse(editHistoryStr);
1326
+ } catch (e) {
1327
+ }
1328
+ }
1329
+ let excludedSuggestions = [];
1330
+ if (excludedSuggestionsStr) {
1331
+ try {
1332
+ excludedSuggestions = JSON.parse(excludedSuggestionsStr);
1333
+ } catch (e) {
1334
+ }
1335
+ }
1336
+ const cacheKey = getCacheKey({ componentName, elementTag, lastSuggestion });
1337
+ const cached = excludedSuggestions.length === 0 ? getCachedSuggestions(cacheKey) : null;
1338
+ if (cached) {
1339
+ return server.NextResponse.json({
1340
+ success: true,
1341
+ suggestions: cached
1342
+ });
1343
+ }
1344
+ const suggestions = await generateSuggestions({
1345
+ componentName,
1346
+ elementTag,
1347
+ className,
1348
+ textContent,
1349
+ editHistory,
1350
+ lastSuggestion,
1351
+ excludedSuggestions
1352
+ });
1353
+ if (suggestions && suggestions.length > 0) {
1354
+ cacheSuggestions(cacheKey, suggestions);
1355
+ return server.NextResponse.json({
1356
+ success: true,
1357
+ suggestions
1358
+ });
1359
+ }
1360
+ return server.NextResponse.json({
1361
+ success: true,
1362
+ suggestions: DEFAULT_SUGGESTIONS
1363
+ });
1364
+ } catch (error) {
1365
+ return server.NextResponse.json(
1366
+ {
1367
+ success: false,
1368
+ error: String(error),
1369
+ suggestions: DEFAULT_SUGGESTIONS
1370
+ // Fallback
1371
+ },
1372
+ { status: 500 }
1373
+ );
1374
+ }
1375
+ }
1376
+ async function generateSuggestions(options) {
1377
+ const {
1378
+ componentName,
1379
+ elementTag,
1380
+ className,
1381
+ textContent,
1382
+ editHistory,
1383
+ lastSuggestion,
1384
+ excludedSuggestions = []
1385
+ } = options;
1386
+ const apiKey = process.env.ANTHROPIC_API_KEY;
1387
+ if (!apiKey) {
1388
+ return null;
1389
+ }
1390
+ const model = new anthropic.ChatAnthropic({
1391
+ apiKey,
1392
+ modelName: "claude-haiku-4-5-20251001",
1393
+ maxTokens: 1024,
1394
+ temperature: 0.3
1395
+ // Slightly creative for variety
1396
+ });
1397
+ const systemPrompt = `You are a UI/UX expert suggesting quick edits for React components.
1398
+
1399
+ Generate 6-8 concise, actionable suggestions that a developer might want to make next.
1400
+
1401
+ RULES:
1402
+ - Each suggestion must be 2-6 words (e.g., "Add padding", "Make text larger")
1403
+ - Focus on common UI improvements: spacing, colors, sizing, layout, shadows, borders, typography
1404
+ - Consider the element type (${elementTag || "element"})
1405
+ - Output ONLY a JSON array of strings, no explanations, no markdown fences
1406
+ ${excludedSuggestions.length > 0 ? `- DO NOT suggest any of these (user wants different options): ${excludedSuggestions.join(
1407
+ ", "
1408
+ )}` : ""}
1409
+
1410
+ ${lastSuggestion ? `IMPORTANT - LAST EDIT CONTEXT:
1411
+ The user just applied: "${lastSuggestion}"
1412
+ Your suggestions MUST be direct follow-ups/refinements of this last change:
1413
+ - If it was "Add padding" → suggest "More padding", "Less padding", "Add vertical padding only"
1414
+ - If it was "Make it blue" → suggest "Darker blue", "Lighter blue", "Change to navy blue"
1415
+ - If it was "Increase font size" → suggest "Decrease font size", "Make even larger", "Adjust line height"
1416
+ - 4-6 suggestions should be variations/refinements of the last edit
1417
+ - 2-4 suggestions can be other related improvements
1418
+
1419
+ 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.`}
1420
+
1421
+ Example output format:
1422
+ ["Add hover effect", "Increase padding", "Make corners rounder", "Change to flex row", "Add drop shadow", "Adjust font size"]`;
1423
+ let userPrompt = `Element: <${elementTag || "div"}>`;
1424
+ if (className) {
1425
+ userPrompt += `
1426
+ Classes: ${className}`;
1427
+ }
1428
+ if (textContent) {
1429
+ userPrompt += `
1430
+ Text: "${textContent.slice(0, 50)}"`;
1431
+ }
1432
+ userPrompt += `
1433
+ Component: ${componentName}`;
1434
+ if (editHistory && editHistory.length > 0) {
1435
+ userPrompt += `
1436
+
1437
+ Recent edits:
1438
+ `;
1439
+ editHistory.slice(-3).forEach((item, idx) => {
1440
+ userPrompt += `${idx + 1}. ${item.suggestion} ${item.success ? "✓" : "✗"}
1441
+ `;
1442
+ });
1443
+ } else {
1444
+ userPrompt += `
1445
+
1446
+ No previous edits.`;
1447
+ }
1448
+ if (lastSuggestion) {
1449
+ userPrompt += `
1450
+
1451
+ **LAST EDIT APPLIED:** "${lastSuggestion}"`;
1452
+ userPrompt += `
1453
+
1454
+ Generate 6-8 follow-up suggestions (mostly variations of the last edit):`;
1455
+ } else {
1456
+ userPrompt += `
1457
+
1458
+ Generate 6-8 initial suggestions:`;
1459
+ }
1460
+ try {
1461
+ const response = await model.invoke([
1462
+ new messages.SystemMessage(systemPrompt),
1463
+ new messages.HumanMessage(userPrompt)
1464
+ ]);
1465
+ let content = typeof response.content === "string" ? response.content : String(response.content);
1466
+ content = content.trim();
1467
+ content = content.replace(/^```json?\s*/gm, "").replace(/\s*```$/gm, "");
1468
+ const suggestions = JSON.parse(content);
1469
+ if (Array.isArray(suggestions)) {
1470
+ const validSuggestions = suggestions.filter((s) => typeof s === "string").map((s) => s.trim()).filter((s) => {
1471
+ const words = s.split(/\s+/).length;
1472
+ return words >= 1 && words <= 15;
1473
+ }).slice(0, 8);
1474
+ if (validSuggestions.length >= 4) {
1475
+ return validSuggestions;
1476
+ }
1477
+ }
1478
+ return null;
1479
+ } catch (e) {
1480
+ return null;
1481
+ }
1482
+ }
1113
1483
  async function handleAIEditorRequest(req, context) {
1114
1484
  const { path: path2 } = await context.params;
1115
1485
  const endpoint = path2[0];
@@ -1133,12 +1503,16 @@ async function handleAIEditorRequest(req, context) {
1133
1503
  case "validate-session":
1134
1504
  if (method === "POST") return handleValidateSession(req);
1135
1505
  break;
1506
+ case "suggestions":
1507
+ if (method === "GET") return handleSuggestions(req);
1508
+ break;
1136
1509
  }
1137
1510
  return server.NextResponse.json(
1138
1511
  { error: `Unknown endpoint: ${endpoint}` },
1139
1512
  { status: 404 }
1140
1513
  );
1141
1514
  }
1515
+ exports.extractComponentName = extractComponentName;
1142
1516
  exports.fileExists = fileExists$1;
1143
1517
  exports.findTargetElement = findTargetElement;
1144
1518
  exports.getAttributeValue = getAttributeValue;
@@ -1149,14 +1523,17 @@ exports.handleAbsolutePath = handleAbsolutePath;
1149
1523
  exports.handleEdit = handleEdit;
1150
1524
  exports.handleRead = handleRead;
1151
1525
  exports.handleResolve = handleResolve;
1526
+ exports.handleSuggestions = handleSuggestions;
1152
1527
  exports.handleUndo = handleUndo;
1153
1528
  exports.handleValidateSession = handleValidateSession;
1154
1529
  exports.isPathSecure = isPathSecure;
1155
1530
  exports.normalizePath = normalizePath;
1156
1531
  exports.parseDebugStack = parseDebugStack;
1532
+ exports.parseDebugStackFrames = parseDebugStackFrames;
1157
1533
  exports.parseFile = parseFile;
1158
1534
  exports.resolveFilePath = resolveFilePath;
1159
1535
  exports.resolveOriginalPosition = resolveOriginalPosition;
1160
1536
  exports.scoreElementMatch = scoreElementMatch;
1161
1537
  exports.validateDevMode = validateDevMode;
1162
- //# sourceMappingURL=index-DnoYi4f8.cjs.map
1538
+ exports.validateGeneratedCode = validateGeneratedCode;
1539
+ //# sourceMappingURL=index-9QODCOgD.cjs.map