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