next-ai-editor 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/index.cjs +1 -0
- package/dist/client/index.js +1 -0
- 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.cjs +1 -0
- package/dist/server/index.d.ts +0 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- 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,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
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
|
129
|
+
return null;
|
|
160
130
|
}
|
|
161
|
-
function
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
204
|
-
const fallback = fallbackExportDefault;
|
|
207
|
+
console.log("Normalized compiled file path:", compiledFilePath);
|
|
208
|
+
const compiledExists = await fileExists(compiledFilePath);
|
|
205
209
|
console.log(
|
|
206
|
-
|
|
210
|
+
"Compiled file exists:",
|
|
211
|
+
compiledExists,
|
|
212
|
+
"at:",
|
|
213
|
+
compiledFilePath
|
|
207
214
|
);
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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 (
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
288
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
if (
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if (
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
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
|
-
|
|
402
|
-
var _a;
|
|
403
|
-
const devModeError = validateDevMode();
|
|
404
|
-
if (devModeError) return devModeError;
|
|
395
|
+
function validateGeneratedCode(newCode, originalCode, fileContent) {
|
|
405
396
|
try {
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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
|
-
|
|
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
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
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
|
-
|
|
730
|
-
|
|
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
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
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
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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
|
-
|
|
794
|
-
if (
|
|
795
|
-
|
|
796
|
-
|
|
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
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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
|
-
|
|
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
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
if (
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
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
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
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
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
return
|
|
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
|
-
|
|
976
|
-
|
|
977
|
-
if (
|
|
978
|
-
|
|
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-
|
|
1512
|
+
//# sourceMappingURL=comments-2HX-AAwu.cjs.map
|