next-ai-editor 0.2.3 → 0.2.5

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.
@@ -1,13 +1,11 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import fs, { readFile, writeFile, mkdir } from "fs/promises";
3
- import { ChatAnthropic } from "@langchain/anthropic";
4
- import { SystemMessage, HumanMessage } from "@langchain/core/messages";
5
3
  import path, { join } from "path";
4
+ import { SourceMapConsumer } from "@jridgewell/source-map";
5
+ import { c as cleanPath, s as shouldSkipPath, n as normalizeSourcePath } from "./path-utils-Bai2xKx9.js";
6
6
  import * as parser from "@babel/parser";
7
7
  import traverse from "@babel/traverse";
8
8
  import * as t from "@babel/types";
9
- import { SourceMapConsumer } from "@jridgewell/source-map";
10
- import { c as cleanPath, s as shouldSkipPath, n as normalizeSourcePath } from "./path-utils-Bai2xKx9.js";
11
9
  import crypto from "crypto";
12
10
  import { unstable_v2_resumeSession, unstable_v2_createSession } from "@anthropic-ai/claude-agent-sdk";
13
11
  import { existsSync } from "fs";
@@ -50,913 +48,609 @@ async function fileExists$1(filePath) {
50
48
  return false;
51
49
  }
52
50
  }
53
- function parseFile(content) {
54
- try {
55
- return parser.parse(content, {
56
- sourceType: "module",
57
- plugins: [
58
- "jsx",
59
- "typescript",
60
- "decorators-legacy",
61
- "classProperties",
62
- "optionalChaining",
63
- "nullishCoalescingOperator"
64
- ]
65
- });
66
- } catch (e) {
67
- console.error("Parse error:", e);
68
- return null;
69
- }
70
- }
71
- function extractComponentName(ast) {
72
- let componentName = null;
73
- traverse(ast, {
74
- ExportDefaultDeclaration(path2) {
75
- var _a;
76
- if (t.isFunctionDeclaration(path2.node.declaration)) {
77
- componentName = ((_a = path2.node.declaration.id) == null ? void 0 : _a.name) || null;
78
- } else if (t.isArrowFunctionExpression(path2.node.declaration)) {
79
- componentName = "default";
80
- } else if (t.isIdentifier(path2.node.declaration)) {
81
- componentName = path2.node.declaration.name;
51
+ function parseDebugStack(stack) {
52
+ if (!stack) return null;
53
+ const stackStr = typeof stack === "string" ? stack : stack.stack || String(stack);
54
+ const frames = stackStr.split("\n");
55
+ const skipPatterns = [
56
+ "node_modules",
57
+ "SegmentViewNode",
58
+ "LayoutRouter",
59
+ "ErrorBoundary",
60
+ "fakeJSXCallSite"
61
+ ];
62
+ for (const frame of frames) {
63
+ if (skipPatterns.some((p) => frame.includes(p))) continue;
64
+ const match = frame.match(/at\s+(\w+)\s+\((.+?):(\d+):(\d+)\)?$/);
65
+ if (match) {
66
+ let filePath = match[2];
67
+ const line = parseInt(match[3], 10);
68
+ const column = parseInt(match[4], 10);
69
+ let chunkId;
70
+ const chunkMatch = filePath.match(/\?([^:]+)$/);
71
+ if (chunkMatch) {
72
+ chunkId = chunkMatch[1];
73
+ filePath = filePath.replace(/\?[^:]*$/, "");
82
74
  }
83
- },
84
- ExportNamedDeclaration(path2) {
85
- var _a;
86
- if (t.isFunctionDeclaration(path2.node.declaration)) {
87
- componentName = ((_a = path2.node.declaration.id) == null ? void 0 : _a.name) || null;
88
- } else if (t.isVariableDeclaration(path2.node.declaration)) {
89
- const declarator = path2.node.declaration.declarations[0];
90
- if (t.isIdentifier(declarator.id)) {
91
- componentName = declarator.id.name;
92
- }
93
- } else if (path2.node.specifiers && path2.node.specifiers.length > 0) {
94
- const specifier = path2.node.specifiers[0];
95
- if (t.isExportSpecifier(specifier) && t.isIdentifier(specifier.exported)) {
96
- componentName = specifier.exported.name;
97
- }
75
+ filePath = cleanPath(filePath);
76
+ if (!shouldSkipPath(filePath)) {
77
+ console.log("parseDebugStack extracted:", { filePath, line, column });
78
+ return { filePath, line, column, chunkId };
98
79
  }
99
80
  }
100
- });
101
- return componentName;
81
+ }
82
+ console.log(
83
+ "parseDebugStack: no valid frame found in stack:",
84
+ stackStr.substring(0, 200)
85
+ );
86
+ return null;
102
87
  }
103
- function validateGeneratedCode(newCode, originalCode, fileContent) {
104
- try {
105
- const isFullComponent = /^(export\s+)?(default\s+)?function\s+\w+/.test(newCode.trim()) || /^(export\s+)?(default\s+)?const\s+\w+\s*=/.test(newCode.trim());
106
- if (isFullComponent) {
107
- let codeToValidate = newCode;
108
- if (fileContent) {
109
- const interfaceMatches = fileContent.match(
110
- /^(interface|type)\s+\w+[^}]*\}/gm
111
- );
112
- if (interfaceMatches) {
113
- codeToValidate = interfaceMatches.join("\n\n") + "\n\n" + newCode;
114
- }
88
+ function extractComponentNameFromStack(stack) {
89
+ if (!stack) return null;
90
+ const stackStr = typeof stack === "string" ? stack : stack.stack || String(stack);
91
+ const frames = stackStr.split("\n");
92
+ const skipPatterns = [
93
+ "node_modules",
94
+ "SegmentViewNode",
95
+ "LayoutRouter",
96
+ "ErrorBoundary",
97
+ "fakeJSXCallSite",
98
+ "react_stack_bottom_frame"
99
+ ];
100
+ for (const frame of frames) {
101
+ if (skipPatterns.some((p) => frame.includes(p))) continue;
102
+ const match = frame.match(/at\s+(\w+)\s+\(/);
103
+ if (match && match[1]) {
104
+ const componentName = match[1];
105
+ if (componentName !== "Object" && componentName !== "anonymous") {
106
+ return componentName;
115
107
  }
116
- parser.parse(codeToValidate, {
117
- sourceType: "module",
118
- plugins: ["jsx", "typescript"]
119
- });
120
- } else {
121
- const wrapped = `function _() { return (${newCode}); }`;
122
- parser.parse(wrapped, {
123
- sourceType: "module",
124
- plugins: ["jsx", "typescript"]
125
- });
126
108
  }
127
- } catch (e) {
128
- console.error("Generated code parse error:", e);
129
- return false;
130
- }
131
- const origBraces = (originalCode.match(/[{}]/g) || []).length;
132
- const newBraces = (newCode.match(/[{}]/g) || []).length;
133
- const origTags = (originalCode.match(/[<>]/g) || []).length;
134
- const newTags = (newCode.match(/[<>]/g) || []).length;
135
- if (Math.abs(origBraces - newBraces) > 4 || Math.abs(origTags - newTags) > 4) {
136
- console.warn(
137
- `Structure changed significantly: braces ${origBraces}->${newBraces}, tags ${origTags}->${newTags}`
138
- );
139
109
  }
140
- return true;
110
+ return null;
141
111
  }
142
- function findTargetElement(ast, fileContent, options) {
143
- const { componentName, lineNumber, elementContext } = options;
144
- let componentNode = null;
145
- let componentStart = 0;
146
- let componentEnd = Infinity;
147
- let fallbackExportDefault = null;
148
- traverse(ast, {
149
- FunctionDeclaration(path2) {
150
- var _a, _b, _c;
151
- if (((_a = path2.node.id) == null ? void 0 : _a.name) === componentName) {
152
- componentNode = path2.node;
153
- componentStart = ((_b = path2.node.loc) == null ? void 0 : _b.start.line) || 0;
154
- componentEnd = ((_c = path2.node.loc) == null ? void 0 : _c.end.line) || Infinity;
112
+ function parseDebugStackFrames(stack) {
113
+ if (!stack) return [];
114
+ const stackStr = typeof stack === "string" ? stack : stack.stack || String(stack);
115
+ const frames = stackStr.split("\n");
116
+ const skipPatterns = [
117
+ "node_modules",
118
+ "SegmentViewNode",
119
+ "LayoutRouter",
120
+ "ErrorBoundary",
121
+ "fakeJSXCallSite",
122
+ "react_stack_bottom_frame"
123
+ ];
124
+ const positions = [];
125
+ for (const frame of frames) {
126
+ if (skipPatterns.some((p) => frame.includes(p))) continue;
127
+ const match = frame.match(/at\s+(\w+)\s+\((.+?):(\d+):(\d+)\)?$/);
128
+ if (match) {
129
+ match[1];
130
+ let filePath = match[2];
131
+ const line = parseInt(match[3], 10);
132
+ const column = parseInt(match[4], 10);
133
+ let chunkId;
134
+ const chunkMatch = filePath.match(/\?([^:]+)$/);
135
+ if (chunkMatch) {
136
+ chunkId = chunkMatch[1];
137
+ filePath = filePath.replace(/\?[^:]*$/, "");
155
138
  }
156
- },
157
- VariableDeclarator(path2) {
158
- var _a, _b;
159
- if (t.isIdentifier(path2.node.id) && path2.node.id.name === componentName) {
160
- componentNode = path2.node;
161
- const parent = path2.parentPath.parent;
162
- componentStart = ((_a = parent == null ? void 0 : parent.loc) == null ? void 0 : _a.start.line) || 0;
163
- componentEnd = ((_b = parent == null ? void 0 : parent.loc) == null ? void 0 : _b.end.line) || Infinity;
139
+ filePath = cleanPath(filePath);
140
+ if (!shouldSkipPath(filePath)) {
141
+ positions.push({ filePath, line, column, chunkId });
142
+ if (positions.length >= 2) break;
164
143
  }
165
- },
166
- ExportDefaultDeclaration(path2) {
167
- var _a, _b, _c, _d, _e;
168
- if (t.isFunctionDeclaration(path2.node.declaration)) {
169
- const funcName = (_a = path2.node.declaration.id) == null ? void 0 : _a.name;
170
- if (funcName === componentName || !funcName) {
171
- componentNode = path2.node;
172
- componentStart = ((_b = path2.node.loc) == null ? void 0 : _b.start.line) || 0;
173
- componentEnd = ((_c = path2.node.loc) == null ? void 0 : _c.end.line) || Infinity;
174
- } else if (!componentNode) {
175
- fallbackExportDefault = {
176
- node: path2.node,
177
- start: ((_d = path2.node.loc) == null ? void 0 : _d.start.line) || 0,
178
- end: ((_e = path2.node.loc) == null ? void 0 : _e.end.line) || Infinity
179
- };
144
+ }
145
+ }
146
+ console.log(`parseDebugStackFrames extracted ${positions.length} frames:`, positions);
147
+ return positions;
148
+ }
149
+ async function resolveOriginalPosition(compiledPos, projectRoot) {
150
+ try {
151
+ console.log("resolveOriginalPosition called with:", compiledPos);
152
+ let compiledFilePath = compiledPos.filePath;
153
+ compiledFilePath = cleanPath(compiledFilePath);
154
+ console.log("After cleanPath:", compiledFilePath);
155
+ if (compiledFilePath.startsWith("http://") || compiledFilePath.startsWith("https://")) {
156
+ const url = new URL(compiledFilePath);
157
+ const pathname = url.pathname;
158
+ if (pathname.startsWith("/_next/")) {
159
+ const relativePath = pathname.substring("/_next/".length);
160
+ compiledFilePath = path.join(projectRoot, ".next", "dev", relativePath);
161
+ } else {
162
+ console.warn("Unexpected HTTP URL path:", pathname);
163
+ return null;
164
+ }
165
+ } else if (!path.isAbsolute(compiledFilePath)) {
166
+ if (compiledFilePath.startsWith(".next/")) {
167
+ compiledFilePath = path.resolve(projectRoot, compiledFilePath);
168
+ } else {
169
+ const resolved = path.resolve(projectRoot, compiledFilePath);
170
+ if (await fileExists(resolved)) {
171
+ compiledFilePath = resolved;
172
+ } else {
173
+ const possiblePaths = [
174
+ path.join(projectRoot, ".next", "dev", compiledFilePath),
175
+ path.join(projectRoot, compiledFilePath)
176
+ ];
177
+ for (const tryPath of possiblePaths) {
178
+ if (await fileExists(tryPath)) {
179
+ compiledFilePath = tryPath;
180
+ break;
181
+ }
182
+ }
180
183
  }
181
184
  }
185
+ } else {
186
+ compiledFilePath = path.normalize(compiledFilePath);
182
187
  }
183
- });
184
- if (!componentNode && fallbackExportDefault !== null) {
185
- const fallback = fallbackExportDefault;
188
+ console.log("Normalized compiled file path:", compiledFilePath);
189
+ const compiledExists = await fileExists(compiledFilePath);
186
190
  console.log(
187
- `⚠️ Component "${componentName}" not found, using export default function as fallback`
191
+ "Compiled file exists:",
192
+ compiledExists,
193
+ "at:",
194
+ compiledFilePath
188
195
  );
189
- componentNode = fallback.node;
190
- componentStart = fallback.start;
191
- componentEnd = fallback.end;
192
- }
193
- const allElementsByTag = /* @__PURE__ */ new Map();
194
- const elementsAtLine = [];
195
- traverse(ast, {
196
- JSXElement(path2) {
197
- const loc = path2.node.loc;
198
- if (!loc) return;
199
- const startLine = loc.start.line;
200
- const endLine = loc.end.line;
201
- if (startLine < componentStart || endLine > componentEnd) return;
202
- const opening = path2.node.openingElement;
203
- if (t.isJSXIdentifier(opening.name)) {
204
- const tagName = opening.name.name;
205
- if (!allElementsByTag.has(tagName)) {
206
- allElementsByTag.set(tagName, []);
196
+ if (!compiledExists) {
197
+ console.error(
198
+ `Compiled file not found: ${compiledFilePath} (from parsed: ${compiledPos.filePath})`
199
+ );
200
+ }
201
+ const sourceMapPath = compiledFilePath + ".map";
202
+ console.log("Looking for source map at:", sourceMapPath);
203
+ const sourceMapExists = await fileExists(sourceMapPath);
204
+ console.log("Source map exists:", sourceMapExists);
205
+ if (!sourceMapExists) {
206
+ console.error(
207
+ `Source map not found: ${sourceMapPath} (from compiled: ${compiledPos.filePath}, normalized: ${compiledFilePath})`
208
+ );
209
+ const altPaths = [
210
+ compiledFilePath.replace(/\.js$/, ".map"),
211
+ path.join(
212
+ path.dirname(compiledFilePath),
213
+ path.basename(compiledFilePath) + ".map"
214
+ )
215
+ ];
216
+ console.log("Trying alternative paths:", altPaths);
217
+ for (const altPath of altPaths) {
218
+ if (await fileExists(altPath)) {
219
+ console.log("Found source map at alternative path:", altPath);
220
+ return await resolveFromSourceMap(altPath, compiledPos, projectRoot);
207
221
  }
208
- allElementsByTag.get(tagName).push({ node: path2.node, startLine, endLine, score: 0 });
209
- }
210
- if (startLine === lineNumber) {
211
- elementsAtLine.push({ node: path2.node, startLine, endLine, score: 0 });
212
222
  }
223
+ return null;
213
224
  }
214
- });
215
- if (elementsAtLine.length > 0) {
216
- if (elementsAtLine.length === 1) {
217
- const target = elementsAtLine[0];
218
- return {
219
- startLine: target.startLine,
220
- endLine: target.endLine,
221
- componentStart,
222
- componentEnd
223
- };
225
+ return await resolveFromSourceMap(sourceMapPath, compiledPos, projectRoot);
226
+ } catch (error) {
227
+ console.error("Error resolving source map:", error);
228
+ return null;
229
+ }
230
+ }
231
+ async function resolveFromSourceMap(sourceMapPath, compiledPos, projectRoot) {
232
+ try {
233
+ const sourceMapContent = await fs.readFile(sourceMapPath, "utf-8");
234
+ const sourceMap = JSON.parse(sourceMapContent);
235
+ if ((!sourceMap.sources || sourceMap.sources.length === 0) && (!sourceMap.sections || sourceMap.sections.length === 0)) {
236
+ console.warn(
237
+ "Empty source map detected, cannot resolve position:",
238
+ sourceMapPath
239
+ );
240
+ return null;
224
241
  }
225
- if (elementContext) {
226
- for (const elem of elementsAtLine) {
227
- if (t.isJSXElement(elem.node)) {
228
- const score = scoreElementMatch(elem.node, elementContext, fileContent);
229
- elem.score = score;
242
+ if (sourceMap.sections) {
243
+ let matchingSection = null;
244
+ for (let i = sourceMap.sections.length - 1; i >= 0; i--) {
245
+ const section = sourceMap.sections[i];
246
+ const offset = section.offset;
247
+ const sectionStartLine1Indexed = offset.line + 1;
248
+ if (compiledPos.line > sectionStartLine1Indexed || compiledPos.line === sectionStartLine1Indexed && compiledPos.column >= offset.column) {
249
+ matchingSection = section;
250
+ break;
230
251
  }
231
252
  }
232
- elementsAtLine.sort((a, b) => b.score - a.score);
233
- if (elementsAtLine[0].score > 0) {
234
- return {
235
- startLine: elementsAtLine[0].startLine,
236
- endLine: elementsAtLine[0].endLine,
237
- componentStart,
238
- componentEnd
239
- };
240
- }
241
- }
242
- return {
243
- startLine: elementsAtLine[0].startLine,
244
- endLine: elementsAtLine[0].endLine,
245
- componentStart,
246
- componentEnd
247
- };
248
- }
249
- if (elementContext == null ? void 0 : elementContext.tagName) {
250
- const allOfTag = allElementsByTag.get(elementContext.tagName);
251
- if (allOfTag && allOfTag.length > 0) {
252
- if (elementContext.textContent || elementContext.className) {
253
- for (const elem of allOfTag) {
254
- if (t.isJSXElement(elem.node)) {
255
- elem.score = scoreElementMatch(elem.node, elementContext, fileContent);
256
- }
253
+ if (matchingSection && matchingSection.map) {
254
+ const sectionMap = matchingSection.map;
255
+ const offset = matchingSection.offset;
256
+ let consumer2;
257
+ try {
258
+ consumer2 = await new SourceMapConsumer(sectionMap);
259
+ } catch (error) {
260
+ console.error("Error creating SourceMapConsumer:", error);
261
+ return null;
257
262
  }
258
- allOfTag.sort((a, b) => b.score - a.score);
259
- if (allOfTag[0].score > 50) {
260
- return {
261
- startLine: allOfTag[0].startLine,
262
- endLine: allOfTag[0].endLine,
263
- componentStart,
264
- componentEnd
265
- };
263
+ try {
264
+ const adjustedLine = compiledPos.line - offset.line;
265
+ const adjustedColumn = compiledPos.line === offset.line + 1 ? compiledPos.column - offset.column : compiledPos.column;
266
+ const originalPos = consumer2.originalPositionFor({
267
+ line: adjustedLine,
268
+ column: adjustedColumn
269
+ });
270
+ if (originalPos.source && originalPos.line !== null) {
271
+ const source = normalizeSourcePath(
272
+ originalPos.source || "",
273
+ projectRoot
274
+ );
275
+ return {
276
+ source,
277
+ line: originalPos.line,
278
+ column: originalPos.column ?? 0
279
+ };
280
+ }
281
+ } finally {
282
+ consumer2.destroy();
266
283
  }
267
284
  }
268
- if (elementContext.nthOfType && allOfTag.length >= elementContext.nthOfType) {
269
- const target = allOfTag[elementContext.nthOfType - 1];
285
+ return null;
286
+ }
287
+ const consumer = await new SourceMapConsumer(sourceMap);
288
+ try {
289
+ const originalPos = consumer.originalPositionFor({
290
+ line: compiledPos.line,
291
+ column: compiledPos.column
292
+ });
293
+ if (originalPos.source && originalPos.line !== null) {
294
+ const source = normalizeSourcePath(
295
+ originalPos.source || "",
296
+ projectRoot
297
+ );
270
298
  return {
271
- startLine: target.startLine,
272
- endLine: target.endLine,
273
- componentStart,
274
- componentEnd
299
+ source,
300
+ line: originalPos.line,
301
+ column: originalPos.column ?? 0
275
302
  };
276
303
  }
304
+ } finally {
305
+ consumer.destroy();
277
306
  }
307
+ return null;
308
+ } catch (error) {
309
+ console.error("Error resolving source map:", error);
310
+ return null;
278
311
  }
279
- const nearbyElements = [];
312
+ }
313
+ async function fileExists(filePath) {
314
+ try {
315
+ await fs.access(filePath);
316
+ return true;
317
+ } catch {
318
+ return false;
319
+ }
320
+ }
321
+ async function getOriginalPositionFromDebugStack(debugStack, projectRoot) {
322
+ const compiledPos = parseDebugStack(debugStack);
323
+ if (!compiledPos) return null;
324
+ return await resolveOriginalPosition(compiledPos, projectRoot);
325
+ }
326
+ function parseFile(content) {
327
+ try {
328
+ return parser.parse(content, {
329
+ sourceType: "module",
330
+ plugins: [
331
+ "jsx",
332
+ "typescript",
333
+ "decorators-legacy",
334
+ "classProperties",
335
+ "optionalChaining",
336
+ "nullishCoalescingOperator"
337
+ ]
338
+ });
339
+ } catch (e) {
340
+ console.error("Parse error:", e);
341
+ return null;
342
+ }
343
+ }
344
+ function extractComponentName(ast) {
345
+ let componentName = null;
280
346
  traverse(ast, {
281
- JSXElement(path2) {
282
- const loc = path2.node.loc;
283
- if (!loc) return;
284
- const startLine = loc.start.line;
285
- const endLine = loc.end.line;
286
- if (startLine < componentStart || endLine > componentEnd) return;
287
- if (Math.abs(startLine - lineNumber) <= 5) {
288
- const score = elementContext ? scoreElementMatch(path2.node, elementContext, fileContent) : 100 - Math.abs(startLine - lineNumber);
289
- nearbyElements.push({ node: path2.node, startLine, endLine, score });
347
+ ExportDefaultDeclaration(path2) {
348
+ var _a;
349
+ if (t.isFunctionDeclaration(path2.node.declaration)) {
350
+ componentName = ((_a = path2.node.declaration.id) == null ? void 0 : _a.name) || null;
351
+ } else if (t.isArrowFunctionExpression(path2.node.declaration)) {
352
+ componentName = "default";
353
+ } else if (t.isIdentifier(path2.node.declaration)) {
354
+ componentName = path2.node.declaration.name;
355
+ }
356
+ },
357
+ ExportNamedDeclaration(path2) {
358
+ var _a;
359
+ if (t.isFunctionDeclaration(path2.node.declaration)) {
360
+ componentName = ((_a = path2.node.declaration.id) == null ? void 0 : _a.name) || null;
361
+ } else if (t.isVariableDeclaration(path2.node.declaration)) {
362
+ const declarator = path2.node.declaration.declarations[0];
363
+ if (t.isIdentifier(declarator.id)) {
364
+ componentName = declarator.id.name;
365
+ }
366
+ } else if (path2.node.specifiers && path2.node.specifiers.length > 0) {
367
+ const specifier = path2.node.specifiers[0];
368
+ if (t.isExportSpecifier(specifier) && t.isIdentifier(specifier.exported)) {
369
+ componentName = specifier.exported.name;
370
+ }
290
371
  }
291
372
  }
292
373
  });
293
- if (nearbyElements.length > 0) {
294
- nearbyElements.sort((a, b) => b.score - a.score);
295
- return {
296
- startLine: nearbyElements[0].startLine,
297
- endLine: nearbyElements[0].endLine,
298
- componentStart,
299
- componentEnd
300
- };
301
- }
302
- if (componentNode && componentStart > 0) {
303
- return {
304
- startLine: componentStart,
305
- endLine: componentEnd,
306
- componentStart,
307
- componentEnd
308
- };
309
- }
310
- return null;
311
- }
312
- function scoreElementMatch(node, context, fileContent) {
313
- let score = 0;
314
- const opening = node.openingElement;
315
- if (t.isJSXIdentifier(opening.name)) {
316
- if (opening.name.name === context.tagName) {
317
- score += 50;
318
- } else {
319
- return 0;
320
- }
321
- } else if (t.isJSXMemberExpression(opening.name)) {
322
- const fullName = getJSXMemberName(opening.name);
323
- if (fullName === context.tagName || fullName.endsWith(`.${context.tagName}`)) {
324
- score += 50;
325
- } else {
326
- return 0;
327
- }
328
- }
329
- if (context.className) {
330
- const classAttr = opening.attributes.find(
331
- (attr) => t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && attr.name.name === "className"
332
- );
333
- if (classAttr && t.isJSXAttribute(classAttr)) {
334
- const classValue = getAttributeValue(classAttr);
335
- if (classValue && context.className.split(/\s+/).some((c) => classValue.includes(c))) {
336
- score += 20;
337
- }
338
- }
339
- }
340
- if (context.textContent && node.loc) {
341
- const elementCode = fileContent.split("\n").slice(node.loc.start.line - 1, node.loc.end.line).join("\n");
342
- const normalizedContent = context.textContent.toLowerCase().trim();
343
- const normalizedElement = elementCode.toLowerCase();
344
- if (normalizedElement.includes(normalizedContent)) {
345
- score += 30;
346
- }
347
- }
348
- if (context.props) {
349
- for (const [key, value] of Object.entries(context.props)) {
350
- if (key.startsWith("_")) continue;
351
- const attr = opening.attributes.find(
352
- (a) => t.isJSXAttribute(a) && t.isJSXIdentifier(a.name) && a.name.name === key
353
- );
354
- if (attr) {
355
- score += 5;
356
- if (typeof value === "string" && t.isJSXAttribute(attr)) {
357
- const attrValue = getAttributeValue(attr);
358
- if (attrValue === value) score += 10;
359
- }
360
- }
361
- }
362
- }
363
- return score;
364
- }
365
- function getJSXMemberName(node) {
366
- if (t.isJSXIdentifier(node.object)) {
367
- return `${node.object.name}.${node.property.name}`;
368
- }
369
- if (t.isJSXMemberExpression(node.object)) {
370
- return `${getJSXMemberName(node.object)}.${node.property.name}`;
371
- }
372
- return node.property.name;
373
- }
374
- function getAttributeValue(attr) {
375
- if (!attr.value) return null;
376
- if (t.isStringLiteral(attr.value)) return attr.value.value;
377
- if (t.isJSXExpressionContainer(attr.value) && t.isStringLiteral(attr.value.expression)) {
378
- return attr.value.expression.value;
379
- }
380
- return null;
374
+ return componentName;
381
375
  }
382
- async function handleEdit(req) {
383
- var _a;
384
- const devModeError = validateDevMode();
385
- if (devModeError) return devModeError;
376
+ function validateGeneratedCode(newCode, originalCode, fileContent) {
386
377
  try {
387
- const body = await req.json();
388
- const {
389
- filePath,
390
- lineNumber,
391
- componentName,
392
- suggestion,
393
- elementContext,
394
- editHistory,
395
- parentInstance
396
- } = body;
397
- const projectRoot = process.cwd();
398
- const normalizedPath = normalizePath(filePath);
399
- const absolutePath = await resolveFilePath(projectRoot, normalizedPath);
400
- if (!absolutePath) {
401
- return NextResponse.json(
402
- { success: false, error: `File not found: ${normalizedPath}` },
403
- { status: 404 }
404
- );
405
- }
406
- const fileContent = await fs.readFile(absolutePath, "utf-8");
407
- const ast = parseFile(fileContent);
408
- if (!ast) {
409
- return NextResponse.json(
410
- { success: false, error: "Failed to parse file" },
411
- { status: 400 }
412
- );
413
- }
414
- const target = findTargetElement(ast, fileContent, {
415
- componentName,
416
- lineNumber,
417
- elementContext
418
- });
419
- if (!target) {
420
- return NextResponse.json(
421
- { success: false, error: "Could not locate target element" },
422
- { status: 400 }
423
- );
424
- }
425
- const lines = fileContent.split("\n");
426
- const targetCode = lines.slice(target.startLine - 1, target.endLine).join("\n");
427
- const baseIndentation = ((_a = lines[target.startLine - 1].match(/^(\s*)/)) == null ? void 0 : _a[1]) || "";
428
- if (target.componentStart <= 0 || target.componentEnd === Infinity) {
429
- return NextResponse.json({
430
- success: false,
431
- error: `Could not determine component bounds. Component: ${target.componentStart}-${target.componentEnd}`
432
- });
433
- }
434
- const componentLines = lines.slice(
435
- target.componentStart - 1,
436
- target.componentEnd
437
- );
438
- const annotatedLines = componentLines.map((line, idx) => {
439
- const lineNum = target.componentStart + idx;
440
- const isTargetStart = lineNum === target.startLine;
441
- const isTargetEnd = lineNum === target.endLine;
442
- const isWithinTarget = lineNum >= target.startLine && lineNum <= target.endLine;
443
- let annotation = "";
444
- if (isTargetStart && isTargetEnd) {
445
- annotation = " // ← CLICKED ELEMENT (single line)";
446
- } else if (isTargetStart) {
447
- annotation = " // ← CLICKED ELEMENT STARTS";
448
- } else if (isTargetEnd) {
449
- annotation = " // ← CLICKED ELEMENT ENDS";
450
- } else if (isWithinTarget) {
451
- annotation = " // ← (clicked element)";
452
- }
453
- return line + annotation;
454
- });
455
- const fullComponentCode = annotatedLines.join("\n");
456
- const newCode = await generateEdit({
457
- targetCode,
458
- fullComponentCode,
459
- suggestion,
460
- elementContext,
461
- baseIndentation,
462
- targetLine: target.startLine,
463
- targetEndLine: target.endLine,
464
- componentStart: target.componentStart,
465
- componentEnd: target.componentEnd,
466
- editHistory: editHistory || [],
467
- parentInstance
468
- });
469
- if (!newCode) {
470
- return NextResponse.json({
471
- success: false,
472
- error: "AI failed to generate valid edit"
473
- });
474
- }
475
- const parentInstanceMatch = newCode.match(/\/\/ EDIT_PARENT_INSTANCE\s*\n([\s\S]+)/);
476
- if (parentInstanceMatch && parentInstance) {
477
- const parentCode = parentInstanceMatch[1].trim();
478
- const parentNormalizedPath = normalizePath(parentInstance.filePath);
479
- const parentAbsolutePath = await resolveFilePath(projectRoot, parentNormalizedPath);
480
- if (!parentAbsolutePath) {
481
- return NextResponse.json({
482
- success: false,
483
- error: `Parent file not found: ${parentNormalizedPath}`
484
- });
378
+ const isFullComponent = /^(export\s+)?(default\s+)?function\s+\w+/.test(newCode.trim()) || /^(export\s+)?(default\s+)?const\s+\w+\s*=/.test(newCode.trim());
379
+ if (isFullComponent) {
380
+ let codeToValidate = newCode;
381
+ if (fileContent) {
382
+ const interfaceMatches = fileContent.match(
383
+ /^(interface|type)\s+\w+[^}]*\}/gm
384
+ );
385
+ if (interfaceMatches) {
386
+ codeToValidate = interfaceMatches.join("\n\n") + "\n\n" + newCode;
387
+ }
485
388
  }
486
- const parentFileContent = await fs.readFile(parentAbsolutePath, "utf-8");
487
- const parentLines = parentFileContent.split("\n");
488
- const newParentLines = [...parentLines];
489
- newParentLines.splice(
490
- parentInstance.lineStart - 1,
491
- parentInstance.lineEnd - parentInstance.lineStart + 1,
492
- ...parentCode.split("\n")
493
- );
494
- await fs.writeFile(parentAbsolutePath, newParentLines.join("\n"), "utf-8");
495
- return NextResponse.json({
496
- success: true,
497
- fileSnapshot: parentFileContent,
498
- generatedCode: parentCode,
499
- modifiedLines: {
500
- start: parentInstance.lineStart,
501
- end: parentInstance.lineEnd
502
- },
503
- editedFile: parentInstance.filePath
504
- // Indicate which file was edited
389
+ parser.parse(codeToValidate, {
390
+ sourceType: "module",
391
+ plugins: ["jsx", "typescript"]
505
392
  });
506
- }
507
- const fullComponentMatch = newCode.match(/\/\/ FULL_COMPONENT\s*\n([\s\S]+)/);
508
- let codeToApply = newCode;
509
- let startLineToReplace = target.startLine;
510
- let endLineToReplace = target.endLine;
511
- const isFullComponentDeclaration = /^(export\s+)?(default\s+)?function\s+\w+/.test(newCode.trim()) || /^(export\s+)?const\s+\w+\s*=/.test(newCode.trim());
512
- if (fullComponentMatch) {
513
- codeToApply = fullComponentMatch[1].trim();
514
- startLineToReplace = target.componentStart;
515
- endLineToReplace = target.componentEnd;
516
- } else if (isFullComponentDeclaration && target.startLine !== target.componentStart) {
517
- codeToApply = newCode;
518
- startLineToReplace = target.componentStart;
519
- endLineToReplace = target.componentEnd;
520
- }
521
- if (!validateGeneratedCode(codeToApply, targetCode, fileContent)) {
522
- return NextResponse.json({
523
- success: false,
524
- error: "Generated code is invalid"
393
+ } else {
394
+ const wrapped = `function _() { return (${newCode}); }`;
395
+ parser.parse(wrapped, {
396
+ sourceType: "module",
397
+ plugins: ["jsx", "typescript"]
525
398
  });
526
399
  }
527
- const newLines = [...lines];
528
- newLines.splice(
529
- startLineToReplace - 1,
530
- endLineToReplace - startLineToReplace + 1,
531
- ...codeToApply.split("\n")
532
- );
533
- await fs.writeFile(absolutePath, newLines.join("\n"), "utf-8");
534
- return NextResponse.json({
535
- success: true,
536
- fileSnapshot: fileContent,
537
- // Original file content for undo
538
- generatedCode: codeToApply,
539
- // AI-generated code
540
- modifiedLines: {
541
- start: startLineToReplace,
542
- end: endLineToReplace
543
- },
544
- editedFile: filePath
545
- // Indicate which file was edited
546
- });
547
- } catch (error) {
548
- return NextResponse.json(
549
- { success: false, error: String(error) },
550
- { status: 500 }
551
- );
552
- }
553
- }
554
- async function generateEdit(options) {
555
- const {
556
- fullComponentCode,
557
- suggestion,
558
- baseIndentation,
559
- editHistory,
560
- parentInstance
561
- } = options;
562
- const apiKey = process.env.ANTHROPIC_API_KEY;
563
- if (!apiKey) throw new Error("ANTHROPIC_API_KEY not set");
564
- const model = new ChatAnthropic({
565
- apiKey,
566
- modelName: "claude-sonnet-4-5-20250929",
567
- maxTokens: 4096,
568
- temperature: 0
569
- });
570
- const systemPrompt = `You are a precise code editor for React/JSX components.
571
-
572
- WHAT YOU'LL SEE:
573
- - Component Definition: Full code of the clicked component with line annotations
574
- * The clicked element is marked with "// ← CLICKED ELEMENT STARTS" and "// ← CLICKED ELEMENT ENDS"
575
- * These are just annotations - NOT part of the actual code
576
- ${parentInstance ? `- Parent Instance: Where this component is used, with "// ← COMPONENT USAGE" marking the usage line` : ""}
577
-
578
- YOUR DECISION - Choose ONE approach based on the user's request:
579
-
580
- 1. EDIT COMPONENT DEFINITION (most common):
581
- - Modify styling, layout, or behavior that should apply to ALL instances
582
- - Example requests: "make the button blue", "add padding", "change font size"
583
- - Output: Just the modified element OR "// FULL_COMPONENT\\n" + complete modified component
584
-
585
- ${parentInstance ? `2. EDIT PARENT INSTANCE (when user wants to modify THIS specific usage):
586
- - Change props, text, or configuration for THIS specific instance only
587
- - Example requests: "change this title to...", "remove this card", "add another button here"
588
- - Output: "// EDIT_PARENT_INSTANCE\\n" + complete modified parent component` : ""}
589
-
590
- RULES:
591
- - Output ONLY code, no explanations
592
- - No markdown fences
593
- - Do NOT include annotation comments in your output
594
- - Preserve indentation
595
- - Make minimal changes`;
596
- let userPrompt = "";
597
- if (editHistory.length > 0) {
598
- userPrompt += "Previous edits made to this element:\n";
599
- editHistory.forEach((item, idx) => {
600
- userPrompt += `${idx + 1}. ${item.suggestion} ${item.success ? "(✓ applied)" : "(✗ failed)"}
601
- `;
602
- });
603
- userPrompt += "\n";
604
- }
605
- userPrompt += `COMPONENT DEFINITION (clicked element is annotated):
606
-
607
- \`\`\`jsx
608
- ${fullComponentCode}
609
- \`\`\`
610
- `;
611
- if (parentInstance) {
612
- const parentLines = parentInstance.content.split("\n");
613
- const annotatedParentLines = parentLines.map((line, idx) => {
614
- const lineNum = parentInstance.lineStart + idx;
615
- const isUsageLine = lineNum >= parentInstance.usageLineStart && lineNum <= parentInstance.usageLineEnd;
616
- return line + (isUsageLine ? " // ← COMPONENT USAGE" : "");
617
- });
618
- userPrompt += `
619
- PARENT INSTANCE (where component is used - in ${parentInstance.filePath}):
620
-
621
- \`\`\`jsx
622
- ${annotatedParentLines.join("\n")}
623
- \`\`\`
624
- `;
625
- }
626
- userPrompt += `
627
- User request: "${suggestion}"
628
- ${editHistory.length > 0 ? "(Build upon previous changes)" : ""}
629
-
630
- ${parentInstance ? `Decide whether to:
631
- 1. Edit the component definition (for changes that affect ALL instances)
632
- 2. Edit the parent instance (for changes specific to THIS usage)
633
-
634
- 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.`}`;
635
- try {
636
- const response = await model.invoke([
637
- new SystemMessage(systemPrompt),
638
- new HumanMessage(userPrompt)
639
- ]);
640
- let code = typeof response.content === "string" ? response.content : String(response.content);
641
- code = cleanGeneratedCode(code, baseIndentation);
642
- return code || null;
643
400
  } catch (e) {
644
- return null;
645
- }
646
- }
647
- function cleanGeneratedCode(code, baseIndentation) {
648
- var _a, _b, _c;
649
- const isParentEdit = code.trim().startsWith("// EDIT_PARENT_INSTANCE");
650
- if (isParentEdit) {
651
- const marker2 = "// EDIT_PARENT_INSTANCE\n";
652
- code = code.replace(/^\/\/ EDIT_PARENT_INSTANCE\n?/, "");
653
- code = code.replace(/^```[\w]*\n?/gm, "").replace(/\n?```$/gm, "").replace(/\s*\/\/ ← COMPONENT USAGE/g, "").trim();
654
- return marker2 + code;
401
+ console.error("Generated code parse error:", e);
402
+ return false;
655
403
  }
656
- const isFullComponent = code.trim().startsWith("// FULL_COMPONENT");
657
- let marker = "";
658
- if (isFullComponent) {
659
- marker = "// FULL_COMPONENT\n";
660
- code = code.replace(/^\/\/ FULL_COMPONENT\n?/, "");
404
+ const origBraces = (originalCode.match(/[{}]/g) || []).length;
405
+ const newBraces = (newCode.match(/[{}]/g) || []).length;
406
+ const origTags = (originalCode.match(/[<>]/g) || []).length;
407
+ const newTags = (newCode.match(/[<>]/g) || []).length;
408
+ if (Math.abs(origBraces - newBraces) > 4 || Math.abs(origTags - newTags) > 4) {
409
+ console.warn(
410
+ `Structure changed significantly: braces ${origBraces}->${newBraces}, tags ${origTags}->${newTags}`
411
+ );
661
412
  }
662
- code = code.replace(/^```[\w]*\n?/gm, "").replace(/\n?```$/gm, "").trim();
663
- code = code.replace(/^(Here'?s?|Here is|Modified|Output|Result)[:\s]*/i, "").replace(/\s*(Done|Complete|That'?s? it)\.?$/i, "").trim();
664
- code = code.replace(/\s*\/\/ ← CLICKED ELEMENT STARTS/g, "").replace(/\s*\/\/ ← CLICKED ELEMENT ENDS/g, "").replace(/\s*\/\/ ← CLICKED ELEMENT \(single line\)/g, "").replace(/\s*\/\/ ← \(clicked element\)/g, "").trim();
665
- if (!code) return "";
666
- if (!isFullComponent) {
667
- const lines = code.split("\n");
668
- const firstLineIndent = ((_b = (_a = lines[0]) == null ? void 0 : _a.match(/^(\s*)/)) == null ? void 0 : _b[1]) || "";
669
- const indentDiff = baseIndentation.length - firstLineIndent.length;
670
- if (indentDiff !== 0) {
671
- for (let i = 0; i < lines.length; i++) {
672
- if (lines[i].trim()) {
673
- const currentIndent = ((_c = lines[i].match(/^(\s*)/)) == null ? void 0 : _c[1]) || "";
674
- const newIndentLength = Math.max(
675
- 0,
676
- currentIndent.length + indentDiff
677
- );
678
- lines[i] = " ".repeat(newIndentLength) + lines[i].trimStart();
413
+ return true;
414
+ }
415
+ function findTargetElement(ast, fileContent, options) {
416
+ const { componentName, lineNumber, elementContext } = options;
417
+ let componentNode = null;
418
+ let componentStart = 0;
419
+ let componentEnd = Infinity;
420
+ let fallbackExportDefault = null;
421
+ traverse(ast, {
422
+ FunctionDeclaration(path2) {
423
+ var _a, _b, _c;
424
+ if (((_a = path2.node.id) == null ? void 0 : _a.name) === componentName) {
425
+ componentNode = path2.node;
426
+ componentStart = ((_b = path2.node.loc) == null ? void 0 : _b.start.line) || 0;
427
+ componentEnd = ((_c = path2.node.loc) == null ? void 0 : _c.end.line) || Infinity;
428
+ }
429
+ },
430
+ VariableDeclarator(path2) {
431
+ var _a, _b;
432
+ if (t.isIdentifier(path2.node.id) && path2.node.id.name === componentName) {
433
+ componentNode = path2.node;
434
+ const parent = path2.parentPath.parent;
435
+ componentStart = ((_a = parent == null ? void 0 : parent.loc) == null ? void 0 : _a.start.line) || 0;
436
+ componentEnd = ((_b = parent == null ? void 0 : parent.loc) == null ? void 0 : _b.end.line) || Infinity;
437
+ }
438
+ },
439
+ ExportDefaultDeclaration(path2) {
440
+ var _a, _b, _c, _d, _e;
441
+ if (t.isFunctionDeclaration(path2.node.declaration)) {
442
+ const funcName = (_a = path2.node.declaration.id) == null ? void 0 : _a.name;
443
+ if (funcName === componentName || !funcName) {
444
+ componentNode = path2.node;
445
+ componentStart = ((_b = path2.node.loc) == null ? void 0 : _b.start.line) || 0;
446
+ componentEnd = ((_c = path2.node.loc) == null ? void 0 : _c.end.line) || Infinity;
447
+ } else if (!componentNode) {
448
+ fallbackExportDefault = {
449
+ node: path2.node,
450
+ start: ((_d = path2.node.loc) == null ? void 0 : _d.start.line) || 0,
451
+ end: ((_e = path2.node.loc) == null ? void 0 : _e.end.line) || Infinity
452
+ };
679
453
  }
680
454
  }
681
- code = lines.join("\n");
682
455
  }
456
+ });
457
+ if (!componentNode && fallbackExportDefault !== null) {
458
+ const fallback = fallbackExportDefault;
459
+ console.log(
460
+ `⚠️ Component "${componentName}" not found, using export default function as fallback`
461
+ );
462
+ componentNode = fallback.node;
463
+ componentStart = fallback.start;
464
+ componentEnd = fallback.end;
683
465
  }
684
- return marker + code;
685
- }
686
- function parseDebugStack(stack) {
687
- if (!stack) return null;
688
- const stackStr = typeof stack === "string" ? stack : stack.stack || String(stack);
689
- const frames = stackStr.split("\n");
690
- const skipPatterns = [
691
- "node_modules",
692
- "SegmentViewNode",
693
- "LayoutRouter",
694
- "ErrorBoundary",
695
- "fakeJSXCallSite"
696
- ];
697
- for (const frame of frames) {
698
- if (skipPatterns.some((p) => frame.includes(p))) continue;
699
- const match = frame.match(/at\s+(\w+)\s+\((.+?):(\d+):(\d+)\)?$/);
700
- if (match) {
701
- let filePath = match[2];
702
- const line = parseInt(match[3], 10);
703
- const column = parseInt(match[4], 10);
704
- let chunkId;
705
- const chunkMatch = filePath.match(/\?([^:]+)$/);
706
- if (chunkMatch) {
707
- chunkId = chunkMatch[1];
708
- filePath = filePath.replace(/\?[^:]*$/, "");
466
+ const allElementsByTag = /* @__PURE__ */ new Map();
467
+ const elementsAtLine = [];
468
+ traverse(ast, {
469
+ JSXElement(path2) {
470
+ const loc = path2.node.loc;
471
+ if (!loc) return;
472
+ const startLine = loc.start.line;
473
+ const endLine = loc.end.line;
474
+ if (startLine < componentStart || endLine > componentEnd) return;
475
+ const opening = path2.node.openingElement;
476
+ if (t.isJSXIdentifier(opening.name)) {
477
+ const tagName = opening.name.name;
478
+ if (!allElementsByTag.has(tagName)) {
479
+ allElementsByTag.set(tagName, []);
480
+ }
481
+ allElementsByTag.get(tagName).push({ node: path2.node, startLine, endLine, score: 0 });
709
482
  }
710
- filePath = cleanPath(filePath);
711
- if (!shouldSkipPath(filePath)) {
712
- console.log("parseDebugStack extracted:", { filePath, line, column });
713
- return { filePath, line, column, chunkId };
483
+ if (startLine === lineNumber) {
484
+ elementsAtLine.push({ node: path2.node, startLine, endLine, score: 0 });
714
485
  }
715
486
  }
716
- }
717
- console.log(
718
- "parseDebugStack: no valid frame found in stack:",
719
- stackStr.substring(0, 200)
720
- );
721
- return null;
722
- }
723
- function extractComponentNameFromStack(stack) {
724
- if (!stack) return null;
725
- const stackStr = typeof stack === "string" ? stack : stack.stack || String(stack);
726
- const frames = stackStr.split("\n");
727
- const skipPatterns = [
728
- "node_modules",
729
- "SegmentViewNode",
730
- "LayoutRouter",
731
- "ErrorBoundary",
732
- "fakeJSXCallSite",
733
- "react_stack_bottom_frame"
734
- ];
735
- for (const frame of frames) {
736
- if (skipPatterns.some((p) => frame.includes(p))) continue;
737
- const match = frame.match(/at\s+(\w+)\s+\(/);
738
- if (match && match[1]) {
739
- const componentName = match[1];
740
- if (componentName !== "Object" && componentName !== "anonymous") {
741
- return componentName;
742
- }
487
+ });
488
+ if (elementsAtLine.length > 0) {
489
+ if (elementsAtLine.length === 1) {
490
+ const target = elementsAtLine[0];
491
+ return {
492
+ startLine: target.startLine,
493
+ endLine: target.endLine,
494
+ componentStart,
495
+ componentEnd
496
+ };
743
497
  }
744
- }
745
- return null;
746
- }
747
- function parseDebugStackFrames(stack) {
748
- if (!stack) return [];
749
- const stackStr = typeof stack === "string" ? stack : stack.stack || String(stack);
750
- const frames = stackStr.split("\n");
751
- const skipPatterns = [
752
- "node_modules",
753
- "SegmentViewNode",
754
- "LayoutRouter",
755
- "ErrorBoundary",
756
- "fakeJSXCallSite",
757
- "react_stack_bottom_frame"
758
- ];
759
- const positions = [];
760
- for (const frame of frames) {
761
- if (skipPatterns.some((p) => frame.includes(p))) continue;
762
- const match = frame.match(/at\s+(\w+)\s+\((.+?):(\d+):(\d+)\)?$/);
763
- if (match) {
764
- match[1];
765
- let filePath = match[2];
766
- const line = parseInt(match[3], 10);
767
- const column = parseInt(match[4], 10);
768
- let chunkId;
769
- const chunkMatch = filePath.match(/\?([^:]+)$/);
770
- if (chunkMatch) {
771
- chunkId = chunkMatch[1];
772
- filePath = filePath.replace(/\?[^:]*$/, "");
498
+ if (elementContext) {
499
+ for (const elem of elementsAtLine) {
500
+ if (t.isJSXElement(elem.node)) {
501
+ const score = scoreElementMatch(elem.node, elementContext, fileContent);
502
+ elem.score = score;
503
+ }
773
504
  }
774
- filePath = cleanPath(filePath);
775
- if (!shouldSkipPath(filePath)) {
776
- positions.push({ filePath, line, column, chunkId });
777
- if (positions.length >= 2) break;
505
+ elementsAtLine.sort((a, b) => b.score - a.score);
506
+ if (elementsAtLine[0].score > 0) {
507
+ return {
508
+ startLine: elementsAtLine[0].startLine,
509
+ endLine: elementsAtLine[0].endLine,
510
+ componentStart,
511
+ componentEnd
512
+ };
778
513
  }
779
514
  }
515
+ return {
516
+ startLine: elementsAtLine[0].startLine,
517
+ endLine: elementsAtLine[0].endLine,
518
+ componentStart,
519
+ componentEnd
520
+ };
780
521
  }
781
- console.log(`parseDebugStackFrames extracted ${positions.length} frames:`, positions);
782
- return positions;
783
- }
784
- async function resolveOriginalPosition(compiledPos, projectRoot) {
785
- try {
786
- console.log("resolveOriginalPosition called with:", compiledPos);
787
- let compiledFilePath = compiledPos.filePath;
788
- compiledFilePath = cleanPath(compiledFilePath);
789
- console.log("After cleanPath:", compiledFilePath);
790
- if (compiledFilePath.startsWith("http://") || compiledFilePath.startsWith("https://")) {
791
- const url = new URL(compiledFilePath);
792
- const pathname = url.pathname;
793
- if (pathname.startsWith("/_next/")) {
794
- const relativePath = pathname.substring("/_next/".length);
795
- compiledFilePath = path.join(projectRoot, ".next", "dev", relativePath);
796
- } else {
797
- console.warn("Unexpected HTTP URL path:", pathname);
798
- return null;
799
- }
800
- } else if (!path.isAbsolute(compiledFilePath)) {
801
- if (compiledFilePath.startsWith(".next/")) {
802
- compiledFilePath = path.resolve(projectRoot, compiledFilePath);
803
- } else {
804
- const resolved = path.resolve(projectRoot, compiledFilePath);
805
- if (await fileExists(resolved)) {
806
- compiledFilePath = resolved;
807
- } else {
808
- const possiblePaths = [
809
- path.join(projectRoot, ".next", "dev", compiledFilePath),
810
- path.join(projectRoot, compiledFilePath)
811
- ];
812
- for (const tryPath of possiblePaths) {
813
- if (await fileExists(tryPath)) {
814
- compiledFilePath = tryPath;
815
- break;
816
- }
522
+ if (elementContext == null ? void 0 : elementContext.tagName) {
523
+ const allOfTag = allElementsByTag.get(elementContext.tagName);
524
+ if (allOfTag && allOfTag.length > 0) {
525
+ if (elementContext.textContent || elementContext.className) {
526
+ for (const elem of allOfTag) {
527
+ if (t.isJSXElement(elem.node)) {
528
+ elem.score = scoreElementMatch(elem.node, elementContext, fileContent);
817
529
  }
818
530
  }
819
- }
820
- } else {
821
- compiledFilePath = path.normalize(compiledFilePath);
822
- }
823
- console.log("Normalized compiled file path:", compiledFilePath);
824
- const compiledExists = await fileExists(compiledFilePath);
825
- console.log(
826
- "Compiled file exists:",
827
- compiledExists,
828
- "at:",
829
- compiledFilePath
830
- );
831
- if (!compiledExists) {
832
- console.error(
833
- `Compiled file not found: ${compiledFilePath} (from parsed: ${compiledPos.filePath})`
834
- );
835
- }
836
- const sourceMapPath = compiledFilePath + ".map";
837
- console.log("Looking for source map at:", sourceMapPath);
838
- const sourceMapExists = await fileExists(sourceMapPath);
839
- console.log("Source map exists:", sourceMapExists);
840
- if (!sourceMapExists) {
841
- console.error(
842
- `Source map not found: ${sourceMapPath} (from compiled: ${compiledPos.filePath}, normalized: ${compiledFilePath})`
843
- );
844
- const altPaths = [
845
- compiledFilePath.replace(/\.js$/, ".map"),
846
- path.join(
847
- path.dirname(compiledFilePath),
848
- path.basename(compiledFilePath) + ".map"
849
- )
850
- ];
851
- console.log("Trying alternative paths:", altPaths);
852
- for (const altPath of altPaths) {
853
- if (await fileExists(altPath)) {
854
- console.log("Found source map at alternative path:", altPath);
855
- return await resolveFromSourceMap(altPath, compiledPos, projectRoot);
531
+ allOfTag.sort((a, b) => b.score - a.score);
532
+ if (allOfTag[0].score > 50) {
533
+ return {
534
+ startLine: allOfTag[0].startLine,
535
+ endLine: allOfTag[0].endLine,
536
+ componentStart,
537
+ componentEnd
538
+ };
856
539
  }
857
540
  }
858
- return null;
541
+ if (elementContext.nthOfType && allOfTag.length >= elementContext.nthOfType) {
542
+ const target = allOfTag[elementContext.nthOfType - 1];
543
+ return {
544
+ startLine: target.startLine,
545
+ endLine: target.endLine,
546
+ componentStart,
547
+ componentEnd
548
+ };
549
+ }
859
550
  }
860
- return await resolveFromSourceMap(sourceMapPath, compiledPos, projectRoot);
861
- } catch (error) {
862
- console.error("Error resolving source map:", error);
863
- return null;
864
551
  }
552
+ const nearbyElements = [];
553
+ traverse(ast, {
554
+ JSXElement(path2) {
555
+ const loc = path2.node.loc;
556
+ if (!loc) return;
557
+ const startLine = loc.start.line;
558
+ const endLine = loc.end.line;
559
+ if (startLine < componentStart || endLine > componentEnd) return;
560
+ if (Math.abs(startLine - lineNumber) <= 5) {
561
+ const score = elementContext ? scoreElementMatch(path2.node, elementContext, fileContent) : 100 - Math.abs(startLine - lineNumber);
562
+ nearbyElements.push({ node: path2.node, startLine, endLine, score });
563
+ }
564
+ }
565
+ });
566
+ if (nearbyElements.length > 0) {
567
+ nearbyElements.sort((a, b) => b.score - a.score);
568
+ return {
569
+ startLine: nearbyElements[0].startLine,
570
+ endLine: nearbyElements[0].endLine,
571
+ componentStart,
572
+ componentEnd
573
+ };
574
+ }
575
+ if (componentNode && componentStart > 0) {
576
+ return {
577
+ startLine: componentStart,
578
+ endLine: componentEnd,
579
+ componentStart,
580
+ componentEnd
581
+ };
582
+ }
583
+ return null;
865
584
  }
866
- async function resolveFromSourceMap(sourceMapPath, compiledPos, projectRoot) {
867
- try {
868
- const sourceMapContent = await fs.readFile(sourceMapPath, "utf-8");
869
- const sourceMap = JSON.parse(sourceMapContent);
870
- if ((!sourceMap.sources || sourceMap.sources.length === 0) && (!sourceMap.sections || sourceMap.sections.length === 0)) {
871
- console.warn(
872
- "Empty source map detected, cannot resolve position:",
873
- sourceMapPath
874
- );
875
- return null;
585
+ function scoreElementMatch(node, context, fileContent) {
586
+ let score = 0;
587
+ const opening = node.openingElement;
588
+ if (t.isJSXIdentifier(opening.name)) {
589
+ if (opening.name.name === context.tagName) {
590
+ score += 50;
591
+ } else {
592
+ return 0;
876
593
  }
877
- if (sourceMap.sections) {
878
- let matchingSection = null;
879
- for (let i = sourceMap.sections.length - 1; i >= 0; i--) {
880
- const section = sourceMap.sections[i];
881
- const offset = section.offset;
882
- const sectionStartLine1Indexed = offset.line + 1;
883
- if (compiledPos.line > sectionStartLine1Indexed || compiledPos.line === sectionStartLine1Indexed && compiledPos.column >= offset.column) {
884
- matchingSection = section;
885
- break;
886
- }
887
- }
888
- if (matchingSection && matchingSection.map) {
889
- const sectionMap = matchingSection.map;
890
- const offset = matchingSection.offset;
891
- let consumer2;
892
- try {
893
- consumer2 = await new SourceMapConsumer(sectionMap);
894
- } catch (error) {
895
- console.error("Error creating SourceMapConsumer:", error);
896
- return null;
897
- }
898
- try {
899
- const adjustedLine = compiledPos.line - offset.line;
900
- const adjustedColumn = compiledPos.line === offset.line + 1 ? compiledPos.column - offset.column : compiledPos.column;
901
- const originalPos = consumer2.originalPositionFor({
902
- line: adjustedLine,
903
- column: adjustedColumn
904
- });
905
- if (originalPos.source && originalPos.line !== null) {
906
- const source = normalizeSourcePath(
907
- originalPos.source || "",
908
- projectRoot
909
- );
910
- return {
911
- source,
912
- line: originalPos.line,
913
- column: originalPos.column ?? 0
914
- };
915
- }
916
- } finally {
917
- consumer2.destroy();
918
- }
594
+ } else if (t.isJSXMemberExpression(opening.name)) {
595
+ const fullName = getJSXMemberName(opening.name);
596
+ if (fullName === context.tagName || fullName.endsWith(`.${context.tagName}`)) {
597
+ score += 50;
598
+ } else {
599
+ return 0;
600
+ }
601
+ }
602
+ if (context.className) {
603
+ const classAttr = opening.attributes.find(
604
+ (attr) => t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && attr.name.name === "className"
605
+ );
606
+ if (classAttr && t.isJSXAttribute(classAttr)) {
607
+ const classValue = getAttributeValue(classAttr);
608
+ if (classValue && context.className.split(/\s+/).some((c) => classValue.includes(c))) {
609
+ score += 20;
919
610
  }
920
- return null;
921
611
  }
922
- const consumer = await new SourceMapConsumer(sourceMap);
923
- try {
924
- const originalPos = consumer.originalPositionFor({
925
- line: compiledPos.line,
926
- column: compiledPos.column
927
- });
928
- if (originalPos.source && originalPos.line !== null) {
929
- const source = normalizeSourcePath(
930
- originalPos.source || "",
931
- projectRoot
932
- );
933
- return {
934
- source,
935
- line: originalPos.line,
936
- column: originalPos.column ?? 0
937
- };
612
+ }
613
+ if (context.textContent && node.loc) {
614
+ const elementCode = fileContent.split("\n").slice(node.loc.start.line - 1, node.loc.end.line).join("\n");
615
+ const normalizedContent = context.textContent.toLowerCase().trim();
616
+ const normalizedElement = elementCode.toLowerCase();
617
+ if (normalizedElement.includes(normalizedContent)) {
618
+ score += 30;
619
+ }
620
+ }
621
+ if (context.props) {
622
+ for (const [key, value] of Object.entries(context.props)) {
623
+ if (key.startsWith("_")) continue;
624
+ const attr = opening.attributes.find(
625
+ (a) => t.isJSXAttribute(a) && t.isJSXIdentifier(a.name) && a.name.name === key
626
+ );
627
+ if (attr) {
628
+ score += 5;
629
+ if (typeof value === "string" && t.isJSXAttribute(attr)) {
630
+ const attrValue = getAttributeValue(attr);
631
+ if (attrValue === value) score += 10;
632
+ }
938
633
  }
939
- } finally {
940
- consumer.destroy();
941
634
  }
942
- return null;
943
- } catch (error) {
944
- console.error("Error resolving source map:", error);
945
- return null;
946
635
  }
636
+ return score;
947
637
  }
948
- async function fileExists(filePath) {
949
- try {
950
- await fs.access(filePath);
951
- return true;
952
- } catch {
953
- return false;
638
+ function getJSXMemberName(node) {
639
+ if (t.isJSXIdentifier(node.object)) {
640
+ return `${node.object.name}.${node.property.name}`;
641
+ }
642
+ if (t.isJSXMemberExpression(node.object)) {
643
+ return `${getJSXMemberName(node.object)}.${node.property.name}`;
954
644
  }
645
+ return node.property.name;
955
646
  }
956
- async function getOriginalPositionFromDebugStack(debugStack, projectRoot) {
957
- const compiledPos = parseDebugStack(debugStack);
958
- if (!compiledPos) return null;
959
- return await resolveOriginalPosition(compiledPos, projectRoot);
647
+ function getAttributeValue(attr) {
648
+ if (!attr.value) return null;
649
+ if (t.isStringLiteral(attr.value)) return attr.value.value;
650
+ if (t.isJSXExpressionContainer(attr.value) && t.isStringLiteral(attr.value.expression)) {
651
+ return attr.value.expression.value;
652
+ }
653
+ return null;
960
654
  }
961
655
  async function handleRead(req) {
962
656
  const devModeError = validateDevMode();
@@ -1313,209 +1007,6 @@ async function handleValidateSession(req) {
1313
1007
  );
1314
1008
  }
1315
1009
  }
1316
- const suggestionCache = /* @__PURE__ */ new Map();
1317
- const CACHE_TTL = 3e4;
1318
- function getCacheKey(params) {
1319
- return `${params.componentName}:${params.elementTag || "div"}:${params.lastSuggestion || ""}`;
1320
- }
1321
- function getCachedSuggestions(key) {
1322
- const cached = suggestionCache.get(key);
1323
- if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
1324
- return cached.suggestions;
1325
- }
1326
- suggestionCache.delete(key);
1327
- return null;
1328
- }
1329
- function cacheSuggestions(key, suggestions) {
1330
- suggestionCache.set(key, { suggestions, timestamp: Date.now() });
1331
- if (suggestionCache.size > 100) {
1332
- const oldestKeys = Array.from(suggestionCache.entries()).sort((a, b) => a[1].timestamp - b[1].timestamp).slice(0, 20).map(([key2]) => key2);
1333
- oldestKeys.forEach((key2) => suggestionCache.delete(key2));
1334
- }
1335
- }
1336
- const DEFAULT_SUGGESTIONS = [
1337
- "Add padding",
1338
- "Change color",
1339
- "Make larger",
1340
- "Add shadow",
1341
- "Round corners",
1342
- "Center content",
1343
- "Add hover effect",
1344
- "Adjust spacing"
1345
- ];
1346
- async function handleSuggestions(req) {
1347
- const devModeError = validateDevMode();
1348
- if (devModeError) return devModeError;
1349
- try {
1350
- const { searchParams } = new URL(req.url);
1351
- const componentName = searchParams.get("componentName") || "Component";
1352
- const elementTag = searchParams.get("elementTag") || void 0;
1353
- const className = searchParams.get("className") || void 0;
1354
- const textContent = searchParams.get("textContent") || void 0;
1355
- const lastSuggestion = searchParams.get("lastSuggestion") || void 0;
1356
- const editHistoryStr = searchParams.get("editHistory") || void 0;
1357
- const excludedSuggestionsStr = searchParams.get("excludedSuggestions") || void 0;
1358
- let editHistory = [];
1359
- if (editHistoryStr) {
1360
- try {
1361
- editHistory = JSON.parse(editHistoryStr);
1362
- } catch (e) {
1363
- }
1364
- }
1365
- let excludedSuggestions = [];
1366
- if (excludedSuggestionsStr) {
1367
- try {
1368
- excludedSuggestions = JSON.parse(excludedSuggestionsStr);
1369
- } catch (e) {
1370
- }
1371
- }
1372
- const cacheKey = getCacheKey({ componentName, elementTag, lastSuggestion });
1373
- const cached = excludedSuggestions.length === 0 ? getCachedSuggestions(cacheKey) : null;
1374
- if (cached) {
1375
- return NextResponse.json({
1376
- success: true,
1377
- suggestions: cached
1378
- });
1379
- }
1380
- const suggestions = await generateSuggestions({
1381
- componentName,
1382
- elementTag,
1383
- className,
1384
- textContent,
1385
- editHistory,
1386
- lastSuggestion,
1387
- excludedSuggestions
1388
- });
1389
- if (suggestions && suggestions.length > 0) {
1390
- cacheSuggestions(cacheKey, suggestions);
1391
- return NextResponse.json({
1392
- success: true,
1393
- suggestions
1394
- });
1395
- }
1396
- return NextResponse.json({
1397
- success: true,
1398
- suggestions: DEFAULT_SUGGESTIONS
1399
- });
1400
- } catch (error) {
1401
- return NextResponse.json(
1402
- {
1403
- success: false,
1404
- error: String(error),
1405
- suggestions: DEFAULT_SUGGESTIONS
1406
- // Fallback
1407
- },
1408
- { status: 500 }
1409
- );
1410
- }
1411
- }
1412
- async function generateSuggestions(options) {
1413
- const {
1414
- componentName,
1415
- elementTag,
1416
- className,
1417
- textContent,
1418
- editHistory,
1419
- lastSuggestion,
1420
- excludedSuggestions = []
1421
- } = options;
1422
- const apiKey = process.env.ANTHROPIC_API_KEY;
1423
- if (!apiKey) {
1424
- return null;
1425
- }
1426
- const model = new ChatAnthropic({
1427
- apiKey,
1428
- modelName: "claude-haiku-4-5-20251001",
1429
- maxTokens: 1024,
1430
- temperature: 0.3
1431
- // Slightly creative for variety
1432
- });
1433
- const systemPrompt = `You are a UI/UX expert suggesting quick edits for React components.
1434
-
1435
- Generate 6-8 concise, actionable suggestions that a developer might want to make next.
1436
-
1437
- RULES:
1438
- - Each suggestion must be 2-6 words (e.g., "Add padding", "Make text larger")
1439
- - Focus on common UI improvements: spacing, colors, sizing, layout, shadows, borders, typography
1440
- - Consider the element type (${elementTag || "element"})
1441
- - Output ONLY a JSON array of strings, no explanations, no markdown fences
1442
- ${excludedSuggestions.length > 0 ? `- DO NOT suggest any of these (user wants different options): ${excludedSuggestions.join(
1443
- ", "
1444
- )}` : ""}
1445
-
1446
- ${lastSuggestion ? `IMPORTANT - LAST EDIT CONTEXT:
1447
- The user just applied: "${lastSuggestion}"
1448
- Your suggestions MUST be direct follow-ups/refinements of this last change:
1449
- - If it was "Add padding" → suggest "More padding", "Less padding", "Add vertical padding only"
1450
- - If it was "Make it blue" → suggest "Darker blue", "Lighter blue", "Change to navy blue"
1451
- - If it was "Increase font size" → suggest "Decrease font size", "Make even larger", "Adjust line height"
1452
- - 4-6 suggestions should be variations/refinements of the last edit
1453
- - 2-4 suggestions can be other related improvements
1454
-
1455
- 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.`}
1456
-
1457
- Example output format:
1458
- ["Add hover effect", "Increase padding", "Make corners rounder", "Change to flex row", "Add drop shadow", "Adjust font size"]`;
1459
- let userPrompt = `Element: <${elementTag || "div"}>`;
1460
- if (className) {
1461
- userPrompt += `
1462
- Classes: ${className}`;
1463
- }
1464
- if (textContent) {
1465
- userPrompt += `
1466
- Text: "${textContent.slice(0, 50)}"`;
1467
- }
1468
- userPrompt += `
1469
- Component: ${componentName}`;
1470
- if (editHistory && editHistory.length > 0) {
1471
- userPrompt += `
1472
-
1473
- Recent edits:
1474
- `;
1475
- editHistory.slice(-3).forEach((item, idx) => {
1476
- userPrompt += `${idx + 1}. ${item.suggestion} ${item.success ? "✓" : "✗"}
1477
- `;
1478
- });
1479
- } else {
1480
- userPrompt += `
1481
-
1482
- No previous edits.`;
1483
- }
1484
- if (lastSuggestion) {
1485
- userPrompt += `
1486
-
1487
- **LAST EDIT APPLIED:** "${lastSuggestion}"`;
1488
- userPrompt += `
1489
-
1490
- Generate 6-8 follow-up suggestions (mostly variations of the last edit):`;
1491
- } else {
1492
- userPrompt += `
1493
-
1494
- Generate 6-8 initial suggestions:`;
1495
- }
1496
- try {
1497
- const response = await model.invoke([
1498
- new SystemMessage(systemPrompt),
1499
- new HumanMessage(userPrompt)
1500
- ]);
1501
- let content = typeof response.content === "string" ? response.content : String(response.content);
1502
- content = content.trim();
1503
- content = content.replace(/^```json?\s*/gm, "").replace(/\s*```$/gm, "");
1504
- const suggestions = JSON.parse(content);
1505
- if (Array.isArray(suggestions)) {
1506
- const validSuggestions = suggestions.filter((s) => typeof s === "string").map((s) => s.trim()).filter((s) => {
1507
- const words = s.split(/\s+/).length;
1508
- return words >= 1 && words <= 15;
1509
- }).slice(0, 8);
1510
- if (validSuggestions.length >= 4) {
1511
- return validSuggestions;
1512
- }
1513
- }
1514
- return null;
1515
- } catch (e) {
1516
- return null;
1517
- }
1518
- }
1519
1010
  class AIEditorAgent {
1520
1011
  constructor(session, projectRoot) {
1521
1012
  this.session = session;
@@ -1884,9 +1375,6 @@ async function handleAIEditorRequest(req, context) {
1884
1375
  const endpoint = path2[0];
1885
1376
  const method = req.method;
1886
1377
  switch (endpoint) {
1887
- case "edit":
1888
- if (method === "POST") return handleEdit(req);
1889
- break;
1890
1378
  case "read":
1891
1379
  if (method === "GET") return handleRead(req);
1892
1380
  break;
@@ -1902,9 +1390,6 @@ async function handleAIEditorRequest(req, context) {
1902
1390
  case "validate-session":
1903
1391
  if (method === "POST") return handleValidateSession(req);
1904
1392
  break;
1905
- case "suggestions":
1906
- if (method === "GET") return handleSuggestions(req);
1907
- break;
1908
1393
  case "chat":
1909
1394
  if (method === "POST") return handleChat(req);
1910
1395
  break;
@@ -1982,31 +1467,29 @@ async function handleCommentsRequest(request) {
1982
1467
  }
1983
1468
  }
1984
1469
  export {
1985
- handleEdit as a,
1986
- handleRead as b,
1987
- handleUndo as c,
1988
- handleResolve as d,
1989
- handleAbsolutePath as e,
1990
- handleValidateSession as f,
1991
- handleSuggestions as g,
1470
+ handleRead as a,
1471
+ handleUndo as b,
1472
+ handleResolve as c,
1473
+ handleAbsolutePath as d,
1474
+ handleValidateSession as e,
1475
+ handleCommentsRequest as f,
1476
+ fileExists$1 as g,
1992
1477
  handleAIEditorRequest as h,
1993
- handleCommentsRequest as i,
1994
- isPathSecure as j,
1995
- fileExists$1 as k,
1996
- extractComponentName as l,
1997
- validateGeneratedCode as m,
1478
+ isPathSecure as i,
1479
+ extractComponentName as j,
1480
+ validateGeneratedCode as k,
1481
+ findTargetElement as l,
1482
+ getJSXMemberName as m,
1998
1483
  normalizePath as n,
1999
- findTargetElement as o,
1484
+ getAttributeValue as o,
2000
1485
  parseFile as p,
2001
- getJSXMemberName as q,
1486
+ parseDebugStack as q,
2002
1487
  resolveFilePath as r,
2003
1488
  scoreElementMatch as s,
2004
- getAttributeValue as t,
2005
- parseDebugStack as u,
1489
+ extractComponentNameFromStack as t,
1490
+ parseDebugStackFrames as u,
2006
1491
  validateDevMode as v,
2007
- extractComponentNameFromStack as w,
2008
- parseDebugStackFrames as x,
2009
- resolveOriginalPosition as y,
2010
- getOriginalPositionFromDebugStack as z
1492
+ resolveOriginalPosition as w,
1493
+ getOriginalPositionFromDebugStack as x
2011
1494
  };
2012
- //# sourceMappingURL=comments-BcCC35iA.js.map
1495
+ //# sourceMappingURL=comments-BYFEhf6K.js.map