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.
- package/dist/{AIEditorProvider-Bs9zUVrL.cjs → AIEditorProvider-BGHm2xyU.cjs} +823 -378
- package/dist/AIEditorProvider-BGHm2xyU.cjs.map +1 -0
- package/dist/{AIEditorProvider-D-w9-GZb.js → AIEditorProvider-CxdGjdLL.js} +847 -402
- package/dist/AIEditorProvider-CxdGjdLL.js.map +1 -0
- package/dist/client/AIEditorProvider.d.ts +8 -16
- package/dist/client/AIEditorProvider.d.ts.map +1 -1
- package/dist/client/fiber-utils.d.ts +35 -0
- package/dist/client/fiber-utils.d.ts.map +1 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/query-params.d.ts +9 -0
- package/dist/client/query-params.d.ts.map +1 -0
- package/dist/client.cjs +1 -1
- package/dist/client.js +1 -1
- package/dist/{index-DnoYi4f8.cjs → index-CNJqd4EQ.cjs} +656 -225
- package/dist/index-CNJqd4EQ.cjs.map +1 -0
- package/dist/{index-BFa7H-uO.js → index-DrmEf13c.js} +662 -231
- package/dist/index-DrmEf13c.js.map +1 -0
- package/dist/index.cjs +7 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +15 -10
- package/dist/path-utils-Bai2xKx9.js +36 -0
- package/dist/path-utils-Bai2xKx9.js.map +1 -0
- package/dist/path-utils-DYzEWUGy.cjs +35 -0
- package/dist/path-utils-DYzEWUGy.cjs.map +1 -0
- package/dist/server/handlers/edit.d.ts.map +1 -1
- package/dist/server/handlers/index.d.ts +1 -0
- package/dist/server/handlers/index.d.ts.map +1 -1
- package/dist/server/handlers/read.d.ts.map +1 -1
- package/dist/server/handlers/resolve.d.ts.map +1 -1
- package/dist/server/handlers/suggestions.d.ts +3 -0
- package/dist/server/handlers/suggestions.d.ts.map +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/utils/ast.d.ts +10 -0
- package/dist/server/utils/ast.d.ts.map +1 -1
- package/dist/server/utils/source-map.d.ts +10 -0
- package/dist/server/utils/source-map.d.ts.map +1 -1
- package/dist/server.cjs +6 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +14 -9
- package/dist/shared/path-utils.d.ts +24 -0
- package/dist/shared/path-utils.d.ts.map +1 -0
- package/dist/shared/types.d.ts +30 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/AIEditorProvider-Bs9zUVrL.cjs.map +0 -1
- package/dist/AIEditorProvider-D-w9-GZb.js.map +0 -1
- package/dist/index-BFa7H-uO.js.map +0 -1
- 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 (
|
|
156
|
-
|
|
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 (
|
|
171
|
-
if (
|
|
232
|
+
if (elementsAtLine.length > 0) {
|
|
233
|
+
if (elementsAtLine.length === 1) {
|
|
234
|
+
const target = elementsAtLine[0];
|
|
172
235
|
return {
|
|
173
|
-
startLine:
|
|
174
|
-
endLine:
|
|
236
|
+
startLine: target.startLine,
|
|
237
|
+
endLine: target.endLine,
|
|
175
238
|
componentStart,
|
|
176
239
|
componentEnd
|
|
177
240
|
};
|
|
178
241
|
}
|
|
179
|
-
|
|
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 (
|
|
266
|
+
if (elementContext == null ? void 0 : elementContext.tagName) {
|
|
182
267
|
const allOfTag = allElementsByTag.get(elementContext.tagName);
|
|
183
|
-
if (allOfTag && allOfTag.length
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
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
|
-
|
|
382
|
-
|
|
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
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
|
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
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
|
|
466
|
-
Modify
|
|
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
|
-
|
|
469
|
-
-
|
|
470
|
-
-
|
|
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
|
|
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 += `
|
|
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 ? "
|
|
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 =
|
|
605
|
-
if (!
|
|
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
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
-
|
|
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
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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
|
-
|
|
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 =
|
|
656
|
-
console.log("After
|
|
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
|
-
|
|
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
|
|
999
|
-
if (
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
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
|
-
|
|
1007
|
-
const
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
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
|
|
1241
|
+
console.log("Resolved frames:", resolvedFrames);
|
|
1024
1242
|
return server.NextResponse.json({
|
|
1025
1243
|
success: true,
|
|
1026
|
-
filePath:
|
|
1027
|
-
lineNumber:
|
|
1028
|
-
columnNumber:
|
|
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
|
-
|
|
1592
|
+
exports.validateGeneratedCode = validateGeneratedCode;
|
|
1593
|
+
//# sourceMappingURL=index-CNJqd4EQ.cjs.map
|