next-ai-editor 0.2.3 → 0.2.4

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