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.
- package/dist/{comments-BpGGRaC9.cjs → comments-2HX-AAwu.cjs} +536 -1053
- package/dist/comments-2HX-AAwu.cjs.map +1 -0
- package/dist/{comments-BcCC35iA.js → comments-BYFEhf6K.js} +554 -1071
- package/dist/comments-BYFEhf6K.js.map +1 -0
- package/dist/index.cjs +1 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +19 -21
- package/dist/server/handlers/index.d.ts +0 -2
- package/dist/server/handlers/index.d.ts.map +1 -1
- package/dist/server/index.d.ts +0 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server.cjs +1 -3
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +19 -21
- package/package.json +2 -5
- package/dist/comments-BcCC35iA.js.map +0 -1
- package/dist/comments-BpGGRaC9.cjs.map +0 -1
- package/dist/server/handlers/edit.d.ts +0 -3
- package/dist/server/handlers/edit.d.ts.map +0 -1
- package/dist/server/handlers/suggestions.d.ts +0 -3
- package/dist/server/handlers/suggestions.d.ts.map +0 -1
|
@@ -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
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
110
|
+
return null;
|
|
141
111
|
}
|
|
142
|
-
function
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
185
|
-
const fallback = fallbackExportDefault;
|
|
188
|
+
console.log("Normalized compiled file path:", compiledFilePath);
|
|
189
|
+
const compiledExists = await fileExists(compiledFilePath);
|
|
186
190
|
console.log(
|
|
187
|
-
|
|
191
|
+
"Compiled file exists:",
|
|
192
|
+
compiledExists,
|
|
193
|
+
"at:",
|
|
194
|
+
compiledFilePath
|
|
188
195
|
);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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 (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
269
|
-
|
|
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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
if (
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if (
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
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
|
-
|
|
383
|
-
var _a;
|
|
384
|
-
const devModeError = validateDevMode();
|
|
385
|
-
if (devModeError) return devModeError;
|
|
376
|
+
function validateGeneratedCode(newCode, originalCode, fileContent) {
|
|
386
377
|
try {
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
-
|
|
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
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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
|
-
|
|
711
|
-
|
|
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
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
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
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
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
|
-
|
|
775
|
-
if (
|
|
776
|
-
|
|
777
|
-
|
|
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
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
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
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
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
|
-
|
|
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
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
if (
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
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
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
return
|
|
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
|
-
|
|
957
|
-
|
|
958
|
-
if (
|
|
959
|
-
|
|
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
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
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
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
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
|
-
|
|
1484
|
+
getAttributeValue as o,
|
|
2000
1485
|
parseFile as p,
|
|
2001
|
-
|
|
1486
|
+
parseDebugStack as q,
|
|
2002
1487
|
resolveFilePath as r,
|
|
2003
1488
|
scoreElementMatch as s,
|
|
2004
|
-
|
|
2005
|
-
|
|
1489
|
+
extractComponentNameFromStack as t,
|
|
1490
|
+
parseDebugStackFrames as u,
|
|
2006
1491
|
validateDevMode as v,
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
resolveOriginalPosition as y,
|
|
2010
|
-
getOriginalPositionFromDebugStack as z
|
|
1492
|
+
resolveOriginalPosition as w,
|
|
1493
|
+
getOriginalPositionFromDebugStack as x
|
|
2011
1494
|
};
|
|
2012
|
-
//# sourceMappingURL=comments-
|
|
1495
|
+
//# sourceMappingURL=comments-BYFEhf6K.js.map
|