next-ai-editor 0.2.2 → 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/{AIEditorProvider-IjMydA1Y.cjs → AIEditorProvider-CLgf1Vwa.cjs} +185 -153
- package/dist/{AIEditorProvider-IjMydA1Y.cjs.map → AIEditorProvider-CLgf1Vwa.cjs.map} +1 -1
- package/dist/{AIEditorProvider-633ZBZul.js → AIEditorProvider-DWId5Qmv.js} +185 -153
- package/dist/{AIEditorProvider-633ZBZul.js.map → AIEditorProvider-DWId5Qmv.js.map} +1 -1
- package/dist/client/AIEditorProvider.d.ts +9 -1
- package/dist/client/AIEditorProvider.d.ts.map +1 -1
- package/dist/client.cjs +1 -1
- package/dist/client.js +1 -1
- package/dist/{comments-Daur80r4.cjs → comments-2HX-AAwu.cjs} +545 -1051
- package/dist/comments-2HX-AAwu.cjs.map +1 -0
- package/dist/{comments-D3m0RsOO.js → comments-BYFEhf6K.js} +563 -1069
- package/dist/comments-BYFEhf6K.js.map +1 -0
- package/dist/index.cjs +2 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +20 -22
- package/dist/next-ai-editor.css +1 -2
- package/dist/server/handlers/config.d.ts +7 -0
- package/dist/server/handlers/config.d.ts.map +1 -0
- 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-D3m0RsOO.js.map +0 -1
- package/dist/comments-Daur80r4.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;
|
|
393
|
+
return componentName;
|
|
330
394
|
}
|
|
331
|
-
function
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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;
|
|
395
|
+
function validateGeneratedCode(newCode, originalCode, fileContent) {
|
|
396
|
+
try {
|
|
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;
|
|
378
406
|
}
|
|
379
407
|
}
|
|
408
|
+
parser__namespace.parse(codeToValidate, {
|
|
409
|
+
sourceType: "module",
|
|
410
|
+
plugins: ["jsx", "typescript"]
|
|
411
|
+
});
|
|
412
|
+
} else {
|
|
413
|
+
const wrapped = `function _() { return (${newCode}); }`;
|
|
414
|
+
parser__namespace.parse(wrapped, {
|
|
415
|
+
sourceType: "module",
|
|
416
|
+
plugins: ["jsx", "typescript"]
|
|
417
|
+
});
|
|
380
418
|
}
|
|
419
|
+
} catch (e) {
|
|
420
|
+
console.error("Generated code parse error:", e);
|
|
421
|
+
return false;
|
|
381
422
|
}
|
|
382
|
-
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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;
|
|
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
|
+
);
|
|
398
431
|
}
|
|
399
|
-
return
|
|
432
|
+
return true;
|
|
400
433
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
componentName
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
parentInstance
|
|
415
|
-
} = body;
|
|
416
|
-
const projectRoot = process.cwd();
|
|
417
|
-
const normalizedPath = normalizePath(filePath);
|
|
418
|
-
const absolutePath = await resolveFilePath(projectRoot, normalizedPath);
|
|
419
|
-
if (!absolutePath) {
|
|
420
|
-
return server.NextResponse.json(
|
|
421
|
-
{ success: false, error: `File not found: ${normalizedPath}` },
|
|
422
|
-
{ status: 404 }
|
|
423
|
-
);
|
|
424
|
-
}
|
|
425
|
-
const fileContent = await fs.readFile(absolutePath, "utf-8");
|
|
426
|
-
const ast = parseFile(fileContent);
|
|
427
|
-
if (!ast) {
|
|
428
|
-
return server.NextResponse.json(
|
|
429
|
-
{ success: false, error: "Failed to parse file" },
|
|
430
|
-
{ status: 400 }
|
|
431
|
-
);
|
|
432
|
-
}
|
|
433
|
-
const target = findTargetElement(ast, fileContent, {
|
|
434
|
-
componentName,
|
|
435
|
-
lineNumber,
|
|
436
|
-
elementContext
|
|
437
|
-
});
|
|
438
|
-
if (!target) {
|
|
439
|
-
return server.NextResponse.json(
|
|
440
|
-
{ success: false, error: "Could not locate target element" },
|
|
441
|
-
{ status: 400 }
|
|
442
|
-
);
|
|
443
|
-
}
|
|
444
|
-
const lines = fileContent.split("\n");
|
|
445
|
-
const targetCode = lines.slice(target.startLine - 1, target.endLine).join("\n");
|
|
446
|
-
const baseIndentation = ((_a = lines[target.startLine - 1].match(/^(\s*)/)) == null ? void 0 : _a[1]) || "";
|
|
447
|
-
if (target.componentStart <= 0 || target.componentEnd === Infinity) {
|
|
448
|
-
return server.NextResponse.json({
|
|
449
|
-
success: false,
|
|
450
|
-
error: `Could not determine component bounds. Component: ${target.componentStart}-${target.componentEnd}`
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
const componentLines = lines.slice(
|
|
454
|
-
target.componentStart - 1,
|
|
455
|
-
target.componentEnd
|
|
456
|
-
);
|
|
457
|
-
const annotatedLines = componentLines.map((line, idx) => {
|
|
458
|
-
const lineNum = target.componentStart + idx;
|
|
459
|
-
const isTargetStart = lineNum === target.startLine;
|
|
460
|
-
const isTargetEnd = lineNum === target.endLine;
|
|
461
|
-
const isWithinTarget = lineNum >= target.startLine && lineNum <= target.endLine;
|
|
462
|
-
let annotation = "";
|
|
463
|
-
if (isTargetStart && isTargetEnd) {
|
|
464
|
-
annotation = " // ← CLICKED ELEMENT (single line)";
|
|
465
|
-
} else if (isTargetStart) {
|
|
466
|
-
annotation = " // ← CLICKED ELEMENT STARTS";
|
|
467
|
-
} else if (isTargetEnd) {
|
|
468
|
-
annotation = " // ← CLICKED ELEMENT ENDS";
|
|
469
|
-
} else if (isWithinTarget) {
|
|
470
|
-
annotation = " // ← (clicked element)";
|
|
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;
|
|
471
447
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
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
|
-
});
|
|
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
|
+
};
|
|
472
|
+
}
|
|
504
473
|
}
|
|
505
|
-
const parentFileContent = await fs.readFile(parentAbsolutePath, "utf-8");
|
|
506
|
-
const parentLines = parentFileContent.split("\n");
|
|
507
|
-
const newParentLines = [...parentLines];
|
|
508
|
-
newParentLines.splice(
|
|
509
|
-
parentInstance.lineStart - 1,
|
|
510
|
-
parentInstance.lineEnd - parentInstance.lineStart + 1,
|
|
511
|
-
...parentCode.split("\n")
|
|
512
|
-
);
|
|
513
|
-
await fs.writeFile(parentAbsolutePath, newParentLines.join("\n"), "utf-8");
|
|
514
|
-
return server.NextResponse.json({
|
|
515
|
-
success: true,
|
|
516
|
-
fileSnapshot: parentFileContent,
|
|
517
|
-
generatedCode: parentCode,
|
|
518
|
-
modifiedLines: {
|
|
519
|
-
start: parentInstance.lineStart,
|
|
520
|
-
end: parentInstance.lineEnd
|
|
521
|
-
},
|
|
522
|
-
editedFile: parentInstance.filePath
|
|
523
|
-
// Indicate which file was edited
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
const fullComponentMatch = newCode.match(/\/\/ FULL_COMPONENT\s*\n([\s\S]+)/);
|
|
527
|
-
let codeToApply = newCode;
|
|
528
|
-
let startLineToReplace = target.startLine;
|
|
529
|
-
let endLineToReplace = target.endLine;
|
|
530
|
-
const isFullComponentDeclaration = /^(export\s+)?(default\s+)?function\s+\w+/.test(newCode.trim()) || /^(export\s+)?const\s+\w+\s*=/.test(newCode.trim());
|
|
531
|
-
if (fullComponentMatch) {
|
|
532
|
-
codeToApply = fullComponentMatch[1].trim();
|
|
533
|
-
startLineToReplace = target.componentStart;
|
|
534
|
-
endLineToReplace = target.componentEnd;
|
|
535
|
-
} else if (isFullComponentDeclaration && target.startLine !== target.componentStart) {
|
|
536
|
-
codeToApply = newCode;
|
|
537
|
-
startLineToReplace = target.componentStart;
|
|
538
|
-
endLineToReplace = target.componentEnd;
|
|
539
|
-
}
|
|
540
|
-
if (!validateGeneratedCode(codeToApply, targetCode, fileContent)) {
|
|
541
|
-
return server.NextResponse.json({
|
|
542
|
-
success: false,
|
|
543
|
-
error: "Generated code is invalid"
|
|
544
|
-
});
|
|
545
474
|
}
|
|
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
475
|
});
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
-
} catch (e) {
|
|
663
|
-
return null;
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
function cleanGeneratedCode(code, baseIndentation) {
|
|
667
|
-
var _a, _b, _c;
|
|
668
|
-
const isParentEdit = code.trim().startsWith("// EDIT_PARENT_INSTANCE");
|
|
669
|
-
if (isParentEdit) {
|
|
670
|
-
const marker2 = "// EDIT_PARENT_INSTANCE\n";
|
|
671
|
-
code = code.replace(/^\/\/ EDIT_PARENT_INSTANCE\n?/, "");
|
|
672
|
-
code = code.replace(/^```[\w]*\n?/gm, "").replace(/\n?```$/gm, "").replace(/\s*\/\/ ← COMPONENT USAGE/g, "").trim();
|
|
673
|
-
return marker2 + code;
|
|
674
|
-
}
|
|
675
|
-
const isFullComponent = code.trim().startsWith("// FULL_COMPONENT");
|
|
676
|
-
let marker = "";
|
|
677
|
-
if (isFullComponent) {
|
|
678
|
-
marker = "// FULL_COMPONENT\n";
|
|
679
|
-
code = code.replace(/^\/\/ FULL_COMPONENT\n?/, "");
|
|
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;
|
|
680
484
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
currentIndent.length + indentDiff
|
|
696
|
-
);
|
|
697
|
-
lines[i] = " ".repeat(newIndentLength) + lines[i].trimStart();
|
|
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, []);
|
|
698
499
|
}
|
|
500
|
+
allElementsByTag.get(tagName).push({ node: path2.node, startLine, endLine, score: 0 });
|
|
501
|
+
}
|
|
502
|
+
if (startLine === lineNumber) {
|
|
503
|
+
elementsAtLine.push({ node: path2.node, startLine, endLine, score: 0 });
|
|
699
504
|
}
|
|
700
|
-
code = lines.join("\n");
|
|
701
505
|
}
|
|
702
|
-
}
|
|
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(/\?[^:]*$/, "");
|
|
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
|
+
};
|
|
516
|
+
}
|
|
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
|
+
}
|
|
728
523
|
}
|
|
729
|
-
|
|
730
|
-
if (
|
|
731
|
-
|
|
732
|
-
|
|
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
|
+
};
|
|
733
532
|
}
|
|
734
533
|
}
|
|
534
|
+
return {
|
|
535
|
+
startLine: elementsAtLine[0].startLine,
|
|
536
|
+
endLine: elementsAtLine[0].endLine,
|
|
537
|
+
componentStart,
|
|
538
|
+
componentEnd
|
|
539
|
+
};
|
|
735
540
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
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
|
+
};
|
|
558
|
+
}
|
|
559
|
+
}
|
|
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
|
+
};
|
|
761
568
|
}
|
|
762
569
|
}
|
|
763
570
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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(/\?[^:]*$/, "");
|
|
792
|
-
}
|
|
793
|
-
filePath = pathUtils.cleanPath(filePath);
|
|
794
|
-
if (!pathUtils.shouldSkipPath(filePath)) {
|
|
795
|
-
positions.push({ filePath, line, column, chunkId });
|
|
796
|
-
if (positions.length >= 2) break;
|
|
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 });
|
|
797
582
|
}
|
|
798
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
|
+
};
|
|
799
593
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
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
|
-
}
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
}
|
|
594
|
+
if (componentNode && componentStart > 0) {
|
|
595
|
+
return {
|
|
596
|
+
startLine: componentStart,
|
|
597
|
+
endLine: componentEnd,
|
|
598
|
+
componentStart,
|
|
599
|
+
componentEnd
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
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;
|
|
839
610
|
} else {
|
|
840
|
-
|
|
611
|
+
return 0;
|
|
841
612
|
}
|
|
842
|
-
|
|
843
|
-
const
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
compiledFilePath
|
|
849
|
-
);
|
|
850
|
-
if (!compiledExists) {
|
|
851
|
-
console.error(
|
|
852
|
-
`Compiled file not found: ${compiledFilePath} (from parsed: ${compiledPos.filePath})`
|
|
853
|
-
);
|
|
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;
|
|
854
619
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
const
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
)
|
|
863
|
-
|
|
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);
|
|
875
|
-
}
|
|
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;
|
|
876
629
|
}
|
|
877
|
-
return null;
|
|
878
630
|
}
|
|
879
|
-
return await resolveFromSourceMap(sourceMapPath, compiledPos, projectRoot);
|
|
880
|
-
} catch (error) {
|
|
881
|
-
console.error("Error resolving source map:", error);
|
|
882
|
-
return null;
|
|
883
631
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
console.warn(
|
|
891
|
-
"Empty source map detected, cannot resolve position:",
|
|
892
|
-
sourceMapPath
|
|
893
|
-
);
|
|
894
|
-
return null;
|
|
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;
|
|
895
638
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
const sectionMap = matchingSection.map;
|
|
909
|
-
const offset = matchingSection.offset;
|
|
910
|
-
let consumer2;
|
|
911
|
-
try {
|
|
912
|
-
consumer2 = await new sourceMap.SourceMapConsumer(sectionMap);
|
|
913
|
-
} catch (error) {
|
|
914
|
-
console.error("Error creating SourceMapConsumer:", error);
|
|
915
|
-
return null;
|
|
916
|
-
}
|
|
917
|
-
try {
|
|
918
|
-
const adjustedLine = compiledPos.line - offset.line;
|
|
919
|
-
const adjustedColumn = compiledPos.line === offset.line + 1 ? compiledPos.column - offset.column : compiledPos.column;
|
|
920
|
-
const originalPos = consumer2.originalPositionFor({
|
|
921
|
-
line: adjustedLine,
|
|
922
|
-
column: adjustedColumn
|
|
923
|
-
});
|
|
924
|
-
if (originalPos.source && originalPos.line !== null) {
|
|
925
|
-
const source = pathUtils.normalizeSourcePath(
|
|
926
|
-
originalPos.source || "",
|
|
927
|
-
projectRoot
|
|
928
|
-
);
|
|
929
|
-
return {
|
|
930
|
-
source,
|
|
931
|
-
line: originalPos.line,
|
|
932
|
-
column: originalPos.column ?? 0
|
|
933
|
-
};
|
|
934
|
-
}
|
|
935
|
-
} finally {
|
|
936
|
-
consumer2.destroy();
|
|
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;
|
|
937
651
|
}
|
|
938
652
|
}
|
|
939
|
-
return null;
|
|
940
|
-
}
|
|
941
|
-
const consumer = await new sourceMap.SourceMapConsumer(sourceMap$1);
|
|
942
|
-
try {
|
|
943
|
-
const originalPos = consumer.originalPositionFor({
|
|
944
|
-
line: compiledPos.line,
|
|
945
|
-
column: compiledPos.column
|
|
946
|
-
});
|
|
947
|
-
if (originalPos.source && originalPos.line !== null) {
|
|
948
|
-
const source = pathUtils.normalizeSourcePath(
|
|
949
|
-
originalPos.source || "",
|
|
950
|
-
projectRoot
|
|
951
|
-
);
|
|
952
|
-
return {
|
|
953
|
-
source,
|
|
954
|
-
line: originalPos.line,
|
|
955
|
-
column: originalPos.column ?? 0
|
|
956
|
-
};
|
|
957
|
-
}
|
|
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;
|
|
@@ -1890,14 +1381,19 @@ async function handleChat(req) {
|
|
|
1890
1381
|
);
|
|
1891
1382
|
}
|
|
1892
1383
|
}
|
|
1384
|
+
async function handleConfig() {
|
|
1385
|
+
const enabled = process.env.NEXT_PUBLIC_AI_EDITOR_ENABLED === "true" || process.env.VITE_AI_EDITOR_ENABLED === "true" || process.env.REACT_APP_AI_EDITOR_ENABLED === "true";
|
|
1386
|
+
return server.NextResponse.json({
|
|
1387
|
+
enabled,
|
|
1388
|
+
// Include NODE_ENV for debugging
|
|
1389
|
+
nodeEnv: process.env.NODE_ENV
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1893
1392
|
async function handleAIEditorRequest(req, context) {
|
|
1894
1393
|
const { path: path2 } = await context.params;
|
|
1895
1394
|
const endpoint = path2[0];
|
|
1896
1395
|
const method = req.method;
|
|
1897
1396
|
switch (endpoint) {
|
|
1898
|
-
case "edit":
|
|
1899
|
-
if (method === "POST") return handleEdit(req);
|
|
1900
|
-
break;
|
|
1901
1397
|
case "read":
|
|
1902
1398
|
if (method === "GET") return handleRead(req);
|
|
1903
1399
|
break;
|
|
@@ -1913,12 +1409,12 @@ async function handleAIEditorRequest(req, context) {
|
|
|
1913
1409
|
case "validate-session":
|
|
1914
1410
|
if (method === "POST") return handleValidateSession(req);
|
|
1915
1411
|
break;
|
|
1916
|
-
case "suggestions":
|
|
1917
|
-
if (method === "GET") return handleSuggestions(req);
|
|
1918
|
-
break;
|
|
1919
1412
|
case "chat":
|
|
1920
1413
|
if (method === "POST") return handleChat(req);
|
|
1921
1414
|
break;
|
|
1415
|
+
case "config":
|
|
1416
|
+
if (method === "GET") return handleConfig();
|
|
1417
|
+
break;
|
|
1922
1418
|
}
|
|
1923
1419
|
return server.NextResponse.json(
|
|
1924
1420
|
{ error: `Unknown endpoint: ${endpoint}` },
|
|
@@ -1999,10 +1495,8 @@ exports.getOriginalPositionFromDebugStack = getOriginalPositionFromDebugStack;
|
|
|
1999
1495
|
exports.handleAIEditorRequest = handleAIEditorRequest;
|
|
2000
1496
|
exports.handleAbsolutePath = handleAbsolutePath;
|
|
2001
1497
|
exports.handleCommentsRequest = handleCommentsRequest;
|
|
2002
|
-
exports.handleEdit = handleEdit;
|
|
2003
1498
|
exports.handleRead = handleRead;
|
|
2004
1499
|
exports.handleResolve = handleResolve;
|
|
2005
|
-
exports.handleSuggestions = handleSuggestions;
|
|
2006
1500
|
exports.handleUndo = handleUndo;
|
|
2007
1501
|
exports.handleValidateSession = handleValidateSession;
|
|
2008
1502
|
exports.isPathSecure = isPathSecure;
|
|
@@ -2015,4 +1509,4 @@ exports.resolveOriginalPosition = resolveOriginalPosition;
|
|
|
2015
1509
|
exports.scoreElementMatch = scoreElementMatch;
|
|
2016
1510
|
exports.validateDevMode = validateDevMode;
|
|
2017
1511
|
exports.validateGeneratedCode = validateGeneratedCode;
|
|
2018
|
-
//# sourceMappingURL=comments-
|
|
1512
|
+
//# sourceMappingURL=comments-2HX-AAwu.cjs.map
|