next-ai-editor 0.2.6 → 1.0.0
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/README.md +2 -2
- package/dist/index.cjs +2108 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +187 -4
- package/dist/index.js +2108 -34
- package/dist/index.js.map +1 -1
- package/dist/next-ai-editor.css +489 -112
- package/package.json +4 -22
- package/client/package.json +0 -5
- package/dist/AIEditorProvider-CLgf1Vwa.cjs +0 -1488
- package/dist/AIEditorProvider-CLgf1Vwa.cjs.map +0 -1
- package/dist/AIEditorProvider-DWId5Qmv.js +0 -1489
- package/dist/AIEditorProvider-DWId5Qmv.js.map +0 -1
- package/dist/client/AIEditorProvider.d.ts +0 -21
- package/dist/client/AIEditorProvider.d.ts.map +0 -1
- package/dist/client/components/ChatPanel.d.ts +0 -18
- package/dist/client/components/ChatPanel.d.ts.map +0 -1
- package/dist/client/components/ControlPill.d.ts +0 -8
- package/dist/client/components/ControlPill.d.ts.map +0 -1
- package/dist/client/components/MessageItem.d.ts +0 -7
- package/dist/client/components/MessageItem.d.ts.map +0 -1
- package/dist/client/components/MessageList.d.ts +0 -7
- package/dist/client/components/MessageList.d.ts.map +0 -1
- package/dist/client/components/TaskHistoryPanel.d.ts +0 -10
- package/dist/client/components/TaskHistoryPanel.d.ts.map +0 -1
- package/dist/client/components/index.d.ts +0 -11
- package/dist/client/components/index.d.ts.map +0 -1
- package/dist/client/fiber-utils.d.ts +0 -35
- package/dist/client/fiber-utils.d.ts.map +0 -1
- package/dist/client/hooks/index.d.ts +0 -3
- package/dist/client/hooks/index.d.ts.map +0 -1
- package/dist/client/hooks/useChatStream.d.ts +0 -66
- package/dist/client/hooks/useChatStream.d.ts.map +0 -1
- package/dist/client/hooks/useHotReload.d.ts +0 -10
- package/dist/client/hooks/useHotReload.d.ts.map +0 -1
- package/dist/client/index.d.ts +0 -6
- package/dist/client/index.d.ts.map +0 -1
- package/dist/client/query-params.d.ts +0 -9
- package/dist/client/query-params.d.ts.map +0 -1
- package/dist/client.cjs +0 -12
- package/dist/client.cjs.map +0 -1
- package/dist/client.js +0 -12
- package/dist/client.js.map +0 -1
- package/dist/comments-BQ6AQ-eC.cjs +0 -1570
- package/dist/comments-BQ6AQ-eC.cjs.map +0 -1
- package/dist/comments-CjBQxjDP.js +0 -1553
- package/dist/comments-CjBQxjDP.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/path-utils-Bai2xKx9.js +0 -36
- package/dist/path-utils-Bai2xKx9.js.map +0 -1
- package/dist/path-utils-DYzEWUGy.cjs +0 -35
- package/dist/path-utils-DYzEWUGy.cjs.map +0 -1
- package/dist/server/agent/sdk-client.d.ts +0 -54
- package/dist/server/agent/sdk-client.d.ts.map +0 -1
- package/dist/server/agent/session-store.d.ts +0 -101
- package/dist/server/agent/session-store.d.ts.map +0 -1
- package/dist/server/handlers/absolute-path.d.ts +0 -3
- package/dist/server/handlers/absolute-path.d.ts.map +0 -1
- package/dist/server/handlers/chat.d.ts +0 -6
- package/dist/server/handlers/chat.d.ts.map +0 -1
- package/dist/server/handlers/comments.d.ts +0 -10
- package/dist/server/handlers/comments.d.ts.map +0 -1
- package/dist/server/handlers/config.d.ts +0 -7
- package/dist/server/handlers/config.d.ts.map +0 -1
- package/dist/server/handlers/index.d.ts +0 -18
- package/dist/server/handlers/index.d.ts.map +0 -1
- package/dist/server/handlers/pages-router.d.ts +0 -21
- package/dist/server/handlers/pages-router.d.ts.map +0 -1
- package/dist/server/handlers/read.d.ts +0 -3
- package/dist/server/handlers/read.d.ts.map +0 -1
- package/dist/server/handlers/resolve.d.ts +0 -3
- package/dist/server/handlers/resolve.d.ts.map +0 -1
- package/dist/server/handlers/undo.d.ts +0 -3
- package/dist/server/handlers/undo.d.ts.map +0 -1
- package/dist/server/handlers/validate-session.d.ts +0 -3
- package/dist/server/handlers/validate-session.d.ts.map +0 -1
- package/dist/server/index.d.ts +0 -12
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/utils/ast.d.ts +0 -49
- package/dist/server/utils/ast.d.ts.map +0 -1
- package/dist/server/utils/file-system.d.ts +0 -24
- package/dist/server/utils/file-system.d.ts.map +0 -1
- package/dist/server/utils/source-map.d.ts +0 -39
- package/dist/server/utils/source-map.d.ts.map +0 -1
- package/dist/server.cjs +0 -29
- package/dist/server.cjs.map +0 -1
- package/dist/server.js +0 -29
- package/dist/server.js.map +0 -1
- package/dist/shared/comment-types.d.ts +0 -140
- package/dist/shared/comment-types.d.ts.map +0 -1
- package/dist/shared/path-utils.d.ts +0 -24
- package/dist/shared/path-utils.d.ts.map +0 -1
- package/dist/shared/storage.d.ts +0 -53
- package/dist/shared/storage.d.ts.map +0 -1
- package/dist/shared/types.d.ts +0 -74
- package/dist/shared/types.d.ts.map +0 -1
- package/server/package.json +0 -5
|
@@ -1,1570 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
const server = require("next/server");
|
|
3
|
-
const fs = require("fs/promises");
|
|
4
|
-
const path = require("path");
|
|
5
|
-
const sourceMap = require("@jridgewell/source-map");
|
|
6
|
-
const pathUtils = require("./path-utils-DYzEWUGy.cjs");
|
|
7
|
-
const parser = require("@babel/parser");
|
|
8
|
-
const traverse = require("@babel/traverse");
|
|
9
|
-
const t = require("@babel/types");
|
|
10
|
-
const crypto = require("crypto");
|
|
11
|
-
const claudeAgentSdk = require("@anthropic-ai/claude-agent-sdk");
|
|
12
|
-
const fs$1 = require("fs");
|
|
13
|
-
function _interopNamespaceDefault(e) {
|
|
14
|
-
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
15
|
-
if (e) {
|
|
16
|
-
for (const k in e) {
|
|
17
|
-
if (k !== "default") {
|
|
18
|
-
const d = Object.getOwnPropertyDescriptor(e, k);
|
|
19
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
20
|
-
enumerable: true,
|
|
21
|
-
get: () => e[k]
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
n.default = e;
|
|
27
|
-
return Object.freeze(n);
|
|
28
|
-
}
|
|
29
|
-
const parser__namespace = /* @__PURE__ */ _interopNamespaceDefault(parser);
|
|
30
|
-
const t__namespace = /* @__PURE__ */ _interopNamespaceDefault(t);
|
|
31
|
-
function validateDevMode() {
|
|
32
|
-
if (process.env.NODE_ENV !== "development") {
|
|
33
|
-
return server.NextResponse.json(
|
|
34
|
-
{ success: false, error: "Development mode only" },
|
|
35
|
-
{ status: 403 }
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
function normalizePath(filePath) {
|
|
41
|
-
return filePath.replace(/^webpack-internal:\/\/\/\([^)]+\)\/\.?/, "").replace(/^webpack:\/\/[^/]*\//, "").replace(/^\.\//, "").replace(/\?.*$/, "");
|
|
42
|
-
}
|
|
43
|
-
async function resolveFilePath(projectRoot, normalizedPath) {
|
|
44
|
-
const candidates = [
|
|
45
|
-
path.resolve(projectRoot, normalizedPath),
|
|
46
|
-
path.resolve(projectRoot, "src", normalizedPath),
|
|
47
|
-
path.resolve(projectRoot, "app", normalizedPath)
|
|
48
|
-
];
|
|
49
|
-
for (const candidate of candidates) {
|
|
50
|
-
if (!candidate.startsWith(projectRoot)) continue;
|
|
51
|
-
try {
|
|
52
|
-
await fs.access(candidate);
|
|
53
|
-
return candidate;
|
|
54
|
-
} catch {
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
function isPathSecure(absolutePath, projectRoot) {
|
|
60
|
-
return absolutePath.startsWith(projectRoot);
|
|
61
|
-
}
|
|
62
|
-
async function fileExists$1(filePath) {
|
|
63
|
-
try {
|
|
64
|
-
await fs.access(filePath);
|
|
65
|
-
return true;
|
|
66
|
-
} catch {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
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(/\?[^:]*$/, "");
|
|
93
|
-
}
|
|
94
|
-
filePath = pathUtils.cleanPath(filePath);
|
|
95
|
-
if (!pathUtils.shouldSkipPath(filePath)) {
|
|
96
|
-
console.log("parseDebugStack extracted:", { filePath, line, column });
|
|
97
|
-
return { filePath, line, column, chunkId };
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
console.log(
|
|
102
|
-
"parseDebugStack: no valid frame found in stack:",
|
|
103
|
-
stackStr.substring(0, 200)
|
|
104
|
-
);
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
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;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
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(/\?[^:]*$/, "");
|
|
157
|
-
}
|
|
158
|
-
filePath = pathUtils.cleanPath(filePath);
|
|
159
|
-
if (!pathUtils.shouldSkipPath(filePath)) {
|
|
160
|
-
positions.push({ filePath, line, column, chunkId });
|
|
161
|
-
if (positions.length >= 2) break;
|
|
162
|
-
}
|
|
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
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
} else {
|
|
205
|
-
compiledFilePath = path.normalize(compiledFilePath);
|
|
206
|
-
}
|
|
207
|
-
console.log("Normalized compiled file path:", compiledFilePath);
|
|
208
|
-
const compiledExists = await fileExists(compiledFilePath);
|
|
209
|
-
console.log(
|
|
210
|
-
"Compiled file exists:",
|
|
211
|
-
compiledExists,
|
|
212
|
-
"at:",
|
|
213
|
-
compiledFilePath
|
|
214
|
-
);
|
|
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);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
return null;
|
|
243
|
-
}
|
|
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;
|
|
260
|
-
}
|
|
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;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
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;
|
|
281
|
-
}
|
|
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();
|
|
302
|
-
}
|
|
303
|
-
}
|
|
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
|
-
);
|
|
317
|
-
return {
|
|
318
|
-
source,
|
|
319
|
-
line: originalPos.line,
|
|
320
|
-
column: originalPos.column ?? 0
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
} finally {
|
|
324
|
-
consumer.destroy();
|
|
325
|
-
}
|
|
326
|
-
return null;
|
|
327
|
-
} catch (error) {
|
|
328
|
-
console.error("Error resolving source map:", error);
|
|
329
|
-
return null;
|
|
330
|
-
}
|
|
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;
|
|
365
|
-
traverse(ast, {
|
|
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
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
});
|
|
393
|
-
return componentName;
|
|
394
|
-
}
|
|
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;
|
|
406
|
-
}
|
|
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
|
-
});
|
|
418
|
-
}
|
|
419
|
-
} catch (e) {
|
|
420
|
-
console.error("Generated code parse error:", e);
|
|
421
|
-
return false;
|
|
422
|
-
}
|
|
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
|
-
);
|
|
431
|
-
}
|
|
432
|
-
return true;
|
|
433
|
-
}
|
|
434
|
-
function findTargetElement(ast, fileContent, options) {
|
|
435
|
-
const { componentName, lineNumber, elementContext } = options;
|
|
436
|
-
let componentNode = null;
|
|
437
|
-
let componentStart = 0;
|
|
438
|
-
let componentEnd = Infinity;
|
|
439
|
-
let fallbackExportDefault = null;
|
|
440
|
-
traverse(ast, {
|
|
441
|
-
FunctionDeclaration(path2) {
|
|
442
|
-
var _a, _b, _c;
|
|
443
|
-
if (((_a = path2.node.id) == null ? void 0 : _a.name) === componentName) {
|
|
444
|
-
componentNode = path2.node;
|
|
445
|
-
componentStart = ((_b = path2.node.loc) == null ? void 0 : _b.start.line) || 0;
|
|
446
|
-
componentEnd = ((_c = path2.node.loc) == null ? void 0 : _c.end.line) || Infinity;
|
|
447
|
-
}
|
|
448
|
-
},
|
|
449
|
-
VariableDeclarator(path2) {
|
|
450
|
-
var _a, _b;
|
|
451
|
-
if (t__namespace.isIdentifier(path2.node.id) && path2.node.id.name === componentName) {
|
|
452
|
-
componentNode = path2.node;
|
|
453
|
-
const parent = path2.parentPath.parent;
|
|
454
|
-
componentStart = ((_a = parent == null ? void 0 : parent.loc) == null ? void 0 : _a.start.line) || 0;
|
|
455
|
-
componentEnd = ((_b = parent == null ? void 0 : parent.loc) == null ? void 0 : _b.end.line) || Infinity;
|
|
456
|
-
}
|
|
457
|
-
},
|
|
458
|
-
ExportDefaultDeclaration(path2) {
|
|
459
|
-
var _a, _b, _c, _d, _e;
|
|
460
|
-
if (t__namespace.isFunctionDeclaration(path2.node.declaration)) {
|
|
461
|
-
const funcName = (_a = path2.node.declaration.id) == null ? void 0 : _a.name;
|
|
462
|
-
if (funcName === componentName || !funcName) {
|
|
463
|
-
componentNode = path2.node;
|
|
464
|
-
componentStart = ((_b = path2.node.loc) == null ? void 0 : _b.start.line) || 0;
|
|
465
|
-
componentEnd = ((_c = path2.node.loc) == null ? void 0 : _c.end.line) || Infinity;
|
|
466
|
-
} else if (!componentNode) {
|
|
467
|
-
fallbackExportDefault = {
|
|
468
|
-
node: path2.node,
|
|
469
|
-
start: ((_d = path2.node.loc) == null ? void 0 : _d.start.line) || 0,
|
|
470
|
-
end: ((_e = path2.node.loc) == null ? void 0 : _e.end.line) || Infinity
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
});
|
|
476
|
-
if (!componentNode && fallbackExportDefault !== null) {
|
|
477
|
-
const fallback = fallbackExportDefault;
|
|
478
|
-
console.log(
|
|
479
|
-
`⚠️ Component "${componentName}" not found, using export default function as fallback`
|
|
480
|
-
);
|
|
481
|
-
componentNode = fallback.node;
|
|
482
|
-
componentStart = fallback.start;
|
|
483
|
-
componentEnd = fallback.end;
|
|
484
|
-
}
|
|
485
|
-
const allElementsByTag = /* @__PURE__ */ new Map();
|
|
486
|
-
const elementsAtLine = [];
|
|
487
|
-
traverse(ast, {
|
|
488
|
-
JSXElement(path2) {
|
|
489
|
-
const loc = path2.node.loc;
|
|
490
|
-
if (!loc) return;
|
|
491
|
-
const startLine = loc.start.line;
|
|
492
|
-
const endLine = loc.end.line;
|
|
493
|
-
if (startLine < componentStart || endLine > componentEnd) return;
|
|
494
|
-
const opening = path2.node.openingElement;
|
|
495
|
-
if (t__namespace.isJSXIdentifier(opening.name)) {
|
|
496
|
-
const tagName = opening.name.name;
|
|
497
|
-
if (!allElementsByTag.has(tagName)) {
|
|
498
|
-
allElementsByTag.set(tagName, []);
|
|
499
|
-
}
|
|
500
|
-
allElementsByTag.get(tagName).push({ node: path2.node, startLine, endLine, score: 0 });
|
|
501
|
-
}
|
|
502
|
-
if (startLine === lineNumber) {
|
|
503
|
-
elementsAtLine.push({ node: path2.node, startLine, endLine, score: 0 });
|
|
504
|
-
}
|
|
505
|
-
}
|
|
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
|
-
}
|
|
523
|
-
}
|
|
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
|
-
};
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
return {
|
|
535
|
-
startLine: elementsAtLine[0].startLine,
|
|
536
|
-
endLine: elementsAtLine[0].endLine,
|
|
537
|
-
componentStart,
|
|
538
|
-
componentEnd
|
|
539
|
-
};
|
|
540
|
-
}
|
|
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
|
-
};
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
const nearbyElements = [];
|
|
572
|
-
traverse(ast, {
|
|
573
|
-
JSXElement(path2) {
|
|
574
|
-
const loc = path2.node.loc;
|
|
575
|
-
if (!loc) return;
|
|
576
|
-
const startLine = loc.start.line;
|
|
577
|
-
const endLine = loc.end.line;
|
|
578
|
-
if (startLine < componentStart || endLine > componentEnd) return;
|
|
579
|
-
if (Math.abs(startLine - lineNumber) <= 5) {
|
|
580
|
-
const score = elementContext ? scoreElementMatch(path2.node, elementContext, fileContent) : 100 - Math.abs(startLine - lineNumber);
|
|
581
|
-
nearbyElements.push({ node: path2.node, startLine, endLine, score });
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
});
|
|
585
|
-
if (nearbyElements.length > 0) {
|
|
586
|
-
nearbyElements.sort((a, b) => b.score - a.score);
|
|
587
|
-
return {
|
|
588
|
-
startLine: nearbyElements[0].startLine,
|
|
589
|
-
endLine: nearbyElements[0].endLine,
|
|
590
|
-
componentStart,
|
|
591
|
-
componentEnd
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
if (componentNode && componentStart > 0) {
|
|
595
|
-
return {
|
|
596
|
-
startLine: componentStart,
|
|
597
|
-
endLine: componentEnd,
|
|
598
|
-
componentStart,
|
|
599
|
-
componentEnd
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
return null;
|
|
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;
|
|
610
|
-
} else {
|
|
611
|
-
return 0;
|
|
612
|
-
}
|
|
613
|
-
} else if (t__namespace.isJSXMemberExpression(opening.name)) {
|
|
614
|
-
const fullName = getJSXMemberName(opening.name);
|
|
615
|
-
if (fullName === context.tagName || fullName.endsWith(`.${context.tagName}`)) {
|
|
616
|
-
score += 50;
|
|
617
|
-
} else {
|
|
618
|
-
return 0;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
if (context.className) {
|
|
622
|
-
const classAttr = opening.attributes.find(
|
|
623
|
-
(attr) => t__namespace.isJSXAttribute(attr) && t__namespace.isJSXIdentifier(attr.name) && attr.name.name === "className"
|
|
624
|
-
);
|
|
625
|
-
if (classAttr && t__namespace.isJSXAttribute(classAttr)) {
|
|
626
|
-
const classValue = getAttributeValue(classAttr);
|
|
627
|
-
if (classValue && context.className.split(/\s+/).some((c) => classValue.includes(c))) {
|
|
628
|
-
score += 20;
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
if (context.textContent && node.loc) {
|
|
633
|
-
const elementCode = fileContent.split("\n").slice(node.loc.start.line - 1, node.loc.end.line).join("\n");
|
|
634
|
-
const normalizedContent = context.textContent.toLowerCase().trim();
|
|
635
|
-
const normalizedElement = elementCode.toLowerCase();
|
|
636
|
-
if (normalizedElement.includes(normalizedContent)) {
|
|
637
|
-
score += 30;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
if (context.props) {
|
|
641
|
-
for (const [key, value] of Object.entries(context.props)) {
|
|
642
|
-
if (key.startsWith("_")) continue;
|
|
643
|
-
const attr = opening.attributes.find(
|
|
644
|
-
(a) => t__namespace.isJSXAttribute(a) && t__namespace.isJSXIdentifier(a.name) && a.name.name === key
|
|
645
|
-
);
|
|
646
|
-
if (attr) {
|
|
647
|
-
score += 5;
|
|
648
|
-
if (typeof value === "string" && t__namespace.isJSXAttribute(attr)) {
|
|
649
|
-
const attrValue = getAttributeValue(attr);
|
|
650
|
-
if (attrValue === value) score += 10;
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
return score;
|
|
656
|
-
}
|
|
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}`;
|
|
663
|
-
}
|
|
664
|
-
return node.property.name;
|
|
665
|
-
}
|
|
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;
|
|
673
|
-
}
|
|
674
|
-
async function handleRead(req) {
|
|
675
|
-
const devModeError = validateDevMode();
|
|
676
|
-
if (devModeError) return devModeError;
|
|
677
|
-
const { searchParams } = new URL(req.url);
|
|
678
|
-
let filePath = searchParams.get("path") || "";
|
|
679
|
-
let lineNumber = parseInt(searchParams.get("line") || "1");
|
|
680
|
-
const debugStack = searchParams.get("debugStack") || "";
|
|
681
|
-
const tagName = searchParams.get("tagName") || "";
|
|
682
|
-
const nthOfType = parseInt(searchParams.get("nthOfType") || "0");
|
|
683
|
-
const textContent = searchParams.get("textContent") || "";
|
|
684
|
-
const className = searchParams.get("className") || "";
|
|
685
|
-
const elementContext = tagName ? {
|
|
686
|
-
tagName,
|
|
687
|
-
nthOfType: nthOfType > 0 ? nthOfType : void 0,
|
|
688
|
-
textContent: textContent || void 0,
|
|
689
|
-
className: className || void 0
|
|
690
|
-
} : void 0;
|
|
691
|
-
const parentFilePath = searchParams.get("parentFilePath") || "";
|
|
692
|
-
const parentLine = parseInt(searchParams.get("parentLine") || "0");
|
|
693
|
-
const parentComponentName = searchParams.get("parentComponentName") || "";
|
|
694
|
-
const parentDebugStack = searchParams.get("parentDebugStack") || "";
|
|
695
|
-
const childKey = searchParams.get("childKey") || "";
|
|
696
|
-
const projectRoot = process.cwd();
|
|
697
|
-
if (debugStack) {
|
|
698
|
-
const compiledPos = parseDebugStack(debugStack);
|
|
699
|
-
if (compiledPos) {
|
|
700
|
-
const originalPos = await resolveOriginalPosition(
|
|
701
|
-
compiledPos,
|
|
702
|
-
projectRoot
|
|
703
|
-
);
|
|
704
|
-
if (originalPos) {
|
|
705
|
-
filePath = originalPos.source;
|
|
706
|
-
lineNumber = originalPos.line;
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
const normalizedPath = normalizePath(filePath);
|
|
711
|
-
const absolutePath = await resolveFilePath(projectRoot, normalizedPath);
|
|
712
|
-
if (!absolutePath) {
|
|
713
|
-
return server.NextResponse.json(
|
|
714
|
-
{ success: false, error: "File not found" },
|
|
715
|
-
{ status: 404 }
|
|
716
|
-
);
|
|
717
|
-
}
|
|
718
|
-
const content = await fs.readFile(absolutePath, "utf-8");
|
|
719
|
-
const ast = parseFile(content);
|
|
720
|
-
if (!ast) {
|
|
721
|
-
return server.NextResponse.json(
|
|
722
|
-
{ success: false, error: "Failed to parse file" },
|
|
723
|
-
{ status: 400 }
|
|
724
|
-
);
|
|
725
|
-
}
|
|
726
|
-
const componentName = extractComponentName(ast);
|
|
727
|
-
if (!componentName) {
|
|
728
|
-
return server.NextResponse.json({
|
|
729
|
-
success: true,
|
|
730
|
-
content,
|
|
731
|
-
lineStart: 1,
|
|
732
|
-
lineEnd: content.split("\n").length
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
|
-
const target = findTargetElement(ast, content, {
|
|
736
|
-
componentName,
|
|
737
|
-
lineNumber,
|
|
738
|
-
elementContext
|
|
739
|
-
});
|
|
740
|
-
if (!target) {
|
|
741
|
-
return server.NextResponse.json({
|
|
742
|
-
success: true,
|
|
743
|
-
content,
|
|
744
|
-
lineStart: 1,
|
|
745
|
-
lineEnd: content.split("\n").length
|
|
746
|
-
});
|
|
747
|
-
}
|
|
748
|
-
const lines = content.split("\n");
|
|
749
|
-
const componentLines = lines.slice(
|
|
750
|
-
target.componentStart - 1,
|
|
751
|
-
target.componentEnd
|
|
752
|
-
);
|
|
753
|
-
const preview = componentLines.join("\n");
|
|
754
|
-
let parentInstance = null;
|
|
755
|
-
if (parentDebugStack) {
|
|
756
|
-
try {
|
|
757
|
-
let resolvedParentPath = parentFilePath;
|
|
758
|
-
let resolvedParentLine = parentLine;
|
|
759
|
-
let resolvedParentComponentName = parentComponentName;
|
|
760
|
-
if (!resolvedParentComponentName && parentDebugStack) {
|
|
761
|
-
resolvedParentComponentName = extractComponentNameFromStack(parentDebugStack) || "";
|
|
762
|
-
}
|
|
763
|
-
if (parentDebugStack) {
|
|
764
|
-
const compiledPos = parseDebugStack(parentDebugStack);
|
|
765
|
-
if (compiledPos) {
|
|
766
|
-
const originalPos = await resolveOriginalPosition(
|
|
767
|
-
compiledPos,
|
|
768
|
-
projectRoot
|
|
769
|
-
);
|
|
770
|
-
if (originalPos) {
|
|
771
|
-
resolvedParentPath = originalPos.source;
|
|
772
|
-
resolvedParentLine = originalPos.line;
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
const normalizedParentPath = normalizePath(resolvedParentPath);
|
|
777
|
-
const absoluteParentPath = await resolveFilePath(
|
|
778
|
-
projectRoot,
|
|
779
|
-
normalizedParentPath
|
|
780
|
-
);
|
|
781
|
-
if (absoluteParentPath && resolvedParentComponentName) {
|
|
782
|
-
const parentContent = await fs.readFile(absoluteParentPath, "utf-8");
|
|
783
|
-
const parentAst = parseFile(parentContent);
|
|
784
|
-
if (parentAst) {
|
|
785
|
-
let nthOfType2 = void 0;
|
|
786
|
-
if (childKey) {
|
|
787
|
-
const keyAsNumber = parseInt(childKey, 10);
|
|
788
|
-
if (!isNaN(keyAsNumber)) {
|
|
789
|
-
nthOfType2 = keyAsNumber + 1;
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
const parentTarget = findTargetElement(parentAst, parentContent, {
|
|
793
|
-
componentName: resolvedParentComponentName,
|
|
794
|
-
lineNumber: 0,
|
|
795
|
-
// Don't use line number - rely on element context to find correct instance
|
|
796
|
-
elementContext: {
|
|
797
|
-
tagName: componentName,
|
|
798
|
-
// Search for child component usage
|
|
799
|
-
nthOfType: nthOfType2,
|
|
800
|
-
// Find specific instance if key is numeric
|
|
801
|
-
textContent: textContent || void 0
|
|
802
|
-
// Use text content to match the specific instance
|
|
803
|
-
}
|
|
804
|
-
});
|
|
805
|
-
if (parentTarget) {
|
|
806
|
-
const parentLines = parentContent.split("\n");
|
|
807
|
-
const parentComponentLines = parentLines.slice(
|
|
808
|
-
parentTarget.componentStart - 1,
|
|
809
|
-
parentTarget.componentEnd
|
|
810
|
-
);
|
|
811
|
-
parentInstance = {
|
|
812
|
-
filePath: resolvedParentPath,
|
|
813
|
-
content: parentComponentLines.join("\n"),
|
|
814
|
-
lineStart: parentTarget.componentStart,
|
|
815
|
-
lineEnd: parentTarget.componentEnd,
|
|
816
|
-
usageLineStart: parentTarget.startLine,
|
|
817
|
-
usageLineEnd: parentTarget.endLine,
|
|
818
|
-
componentName: resolvedParentComponentName
|
|
819
|
-
};
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
} catch (error) {
|
|
824
|
-
console.error("Error resolving parent instance:", error);
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
return server.NextResponse.json({
|
|
828
|
-
success: true,
|
|
829
|
-
content: preview,
|
|
830
|
-
lineStart: target.componentStart,
|
|
831
|
-
lineEnd: target.componentEnd,
|
|
832
|
-
targetStartLine: target.startLine,
|
|
833
|
-
targetEndLine: target.endLine,
|
|
834
|
-
componentName,
|
|
835
|
-
// Return the actual component name parsed from code
|
|
836
|
-
parentInstance
|
|
837
|
-
// Optional: where this component is used
|
|
838
|
-
});
|
|
839
|
-
}
|
|
840
|
-
async function handleUndo(req) {
|
|
841
|
-
const devModeError = validateDevMode();
|
|
842
|
-
if (devModeError) return devModeError;
|
|
843
|
-
try {
|
|
844
|
-
const body = await req.json();
|
|
845
|
-
const { filePath, content } = body;
|
|
846
|
-
if (!filePath || typeof content !== "string") {
|
|
847
|
-
return server.NextResponse.json(
|
|
848
|
-
{
|
|
849
|
-
success: false,
|
|
850
|
-
error: "Invalid request: filePath and content required"
|
|
851
|
-
},
|
|
852
|
-
{ status: 400 }
|
|
853
|
-
);
|
|
854
|
-
}
|
|
855
|
-
const projectRoot = process.cwd();
|
|
856
|
-
const normalizedPath = normalizePath(filePath);
|
|
857
|
-
const absolutePath = await resolveFilePath(projectRoot, normalizedPath);
|
|
858
|
-
if (!absolutePath) {
|
|
859
|
-
return server.NextResponse.json(
|
|
860
|
-
{ success: false, error: `File not found: ${normalizedPath}` },
|
|
861
|
-
{ status: 404 }
|
|
862
|
-
);
|
|
863
|
-
}
|
|
864
|
-
if (!isPathSecure(absolutePath, projectRoot)) {
|
|
865
|
-
return server.NextResponse.json(
|
|
866
|
-
{ success: false, error: "Access denied: File outside project root" },
|
|
867
|
-
{ status: 403 }
|
|
868
|
-
);
|
|
869
|
-
}
|
|
870
|
-
await fs.writeFile(absolutePath, content, "utf-8");
|
|
871
|
-
console.log(`✅ Undo: Restored ${normalizedPath}`);
|
|
872
|
-
return server.NextResponse.json({ success: true });
|
|
873
|
-
} catch (error) {
|
|
874
|
-
console.error("Undo error:", error);
|
|
875
|
-
return server.NextResponse.json(
|
|
876
|
-
{ success: false, error: String(error) },
|
|
877
|
-
{ status: 500 }
|
|
878
|
-
);
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
async function handleResolve(req) {
|
|
882
|
-
const devModeError = validateDevMode();
|
|
883
|
-
if (devModeError) return devModeError;
|
|
884
|
-
try {
|
|
885
|
-
const body = await req.json();
|
|
886
|
-
const debugStack = body == null ? void 0 : body.debugStack;
|
|
887
|
-
if (!debugStack || typeof debugStack !== "string") {
|
|
888
|
-
return server.NextResponse.json(
|
|
889
|
-
{ success: false, error: "Missing debugStack" },
|
|
890
|
-
{ status: 400 }
|
|
891
|
-
);
|
|
892
|
-
}
|
|
893
|
-
const compiledFrames = parseDebugStackFrames(debugStack);
|
|
894
|
-
if (compiledFrames.length === 0) {
|
|
895
|
-
const compiledPos = parseDebugStack(debugStack);
|
|
896
|
-
if (!compiledPos) {
|
|
897
|
-
console.error("Could not parse debug stack:", debugStack);
|
|
898
|
-
return server.NextResponse.json(
|
|
899
|
-
{ success: false, error: "Could not parse stack" },
|
|
900
|
-
{ status: 422 }
|
|
901
|
-
);
|
|
902
|
-
}
|
|
903
|
-
const originalPos = await resolveOriginalPosition(
|
|
904
|
-
compiledPos,
|
|
905
|
-
process.cwd()
|
|
906
|
-
);
|
|
907
|
-
if (!originalPos) {
|
|
908
|
-
return server.NextResponse.json(
|
|
909
|
-
{ success: false, error: "Source map lookup failed" },
|
|
910
|
-
{ status: 404 }
|
|
911
|
-
);
|
|
912
|
-
}
|
|
913
|
-
return server.NextResponse.json({
|
|
914
|
-
success: true,
|
|
915
|
-
filePath: originalPos.source,
|
|
916
|
-
lineNumber: originalPos.line,
|
|
917
|
-
columnNumber: originalPos.column ?? 0
|
|
918
|
-
});
|
|
919
|
-
}
|
|
920
|
-
const resolvedFrames = [];
|
|
921
|
-
for (const frame of compiledFrames) {
|
|
922
|
-
const originalPos = await resolveOriginalPosition(frame, process.cwd());
|
|
923
|
-
if (originalPos) {
|
|
924
|
-
resolvedFrames.push({
|
|
925
|
-
filePath: originalPos.source,
|
|
926
|
-
lineNumber: originalPos.line,
|
|
927
|
-
columnNumber: originalPos.column ?? 0
|
|
928
|
-
});
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
if (resolvedFrames.length === 0) {
|
|
932
|
-
return server.NextResponse.json(
|
|
933
|
-
{ success: false, error: "Source map lookup failed for all frames" },
|
|
934
|
-
{ status: 404 }
|
|
935
|
-
);
|
|
936
|
-
}
|
|
937
|
-
console.log("Resolved frames:", resolvedFrames);
|
|
938
|
-
return server.NextResponse.json({
|
|
939
|
-
success: true,
|
|
940
|
-
filePath: resolvedFrames[0].filePath,
|
|
941
|
-
lineNumber: resolvedFrames[0].lineNumber,
|
|
942
|
-
columnNumber: resolvedFrames[0].columnNumber,
|
|
943
|
-
frames: resolvedFrames
|
|
944
|
-
// [componentDefinition, parentInstance]
|
|
945
|
-
});
|
|
946
|
-
} catch (error) {
|
|
947
|
-
console.error("Source resolve error:", error);
|
|
948
|
-
return server.NextResponse.json(
|
|
949
|
-
{ success: false, error: "Internal error" },
|
|
950
|
-
{ status: 500 }
|
|
951
|
-
);
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
async function handleAbsolutePath(req) {
|
|
955
|
-
const devModeError = validateDevMode();
|
|
956
|
-
if (devModeError) return devModeError;
|
|
957
|
-
const { searchParams } = new URL(req.url);
|
|
958
|
-
const filePath = searchParams.get("path") || "";
|
|
959
|
-
if (!filePath) {
|
|
960
|
-
return server.NextResponse.json(
|
|
961
|
-
{ success: false, error: "Missing path" },
|
|
962
|
-
{ status: 400 }
|
|
963
|
-
);
|
|
964
|
-
}
|
|
965
|
-
const projectRoot = process.cwd();
|
|
966
|
-
const normalizedPath = normalizePath(filePath);
|
|
967
|
-
const absolutePath = await resolveFilePath(projectRoot, normalizedPath);
|
|
968
|
-
if (!absolutePath) {
|
|
969
|
-
return server.NextResponse.json(
|
|
970
|
-
{ success: false, error: "File not found" },
|
|
971
|
-
{ status: 404 }
|
|
972
|
-
);
|
|
973
|
-
}
|
|
974
|
-
return server.NextResponse.json({
|
|
975
|
-
success: true,
|
|
976
|
-
absolutePath
|
|
977
|
-
});
|
|
978
|
-
}
|
|
979
|
-
async function handleValidateSession(req) {
|
|
980
|
-
const devModeError = validateDevMode();
|
|
981
|
-
if (devModeError) return devModeError;
|
|
982
|
-
try {
|
|
983
|
-
const body = await req.json();
|
|
984
|
-
const { filePath, lastKnownHash, lastKnownSize } = body;
|
|
985
|
-
if (!filePath || !lastKnownHash || lastKnownSize === void 0) {
|
|
986
|
-
return server.NextResponse.json(
|
|
987
|
-
{ success: false, error: "Missing required fields" },
|
|
988
|
-
{ status: 400 }
|
|
989
|
-
);
|
|
990
|
-
}
|
|
991
|
-
const projectRoot = process.cwd();
|
|
992
|
-
const normalizedPath = normalizePath(filePath);
|
|
993
|
-
const absolutePath = await resolveFilePath(projectRoot, normalizedPath);
|
|
994
|
-
if (!absolutePath) {
|
|
995
|
-
return server.NextResponse.json(
|
|
996
|
-
{ success: false, error: "File not found" },
|
|
997
|
-
{ status: 404 }
|
|
998
|
-
);
|
|
999
|
-
}
|
|
1000
|
-
if (!isPathSecure(absolutePath, projectRoot)) {
|
|
1001
|
-
return server.NextResponse.json(
|
|
1002
|
-
{ success: false, error: "Access denied" },
|
|
1003
|
-
{ status: 403 }
|
|
1004
|
-
);
|
|
1005
|
-
}
|
|
1006
|
-
const content = await fs.readFile(absolutePath, "utf-8");
|
|
1007
|
-
const hash = crypto.createHash("sha256").update(content).digest("hex");
|
|
1008
|
-
const stats = await fs.stat(absolutePath);
|
|
1009
|
-
const isValid = hash === lastKnownHash && content.length === lastKnownSize;
|
|
1010
|
-
console.log(`[validate-session] ${filePath}:`);
|
|
1011
|
-
console.log(` Hash match: ${hash === lastKnownHash}`);
|
|
1012
|
-
console.log(` Size match: ${content.length === lastKnownSize}`);
|
|
1013
|
-
console.log(` Is valid: ${isValid}`);
|
|
1014
|
-
return server.NextResponse.json({
|
|
1015
|
-
success: true,
|
|
1016
|
-
isValid,
|
|
1017
|
-
currentHash: hash,
|
|
1018
|
-
currentSize: content.length,
|
|
1019
|
-
lastModifiedTime: stats.mtimeMs
|
|
1020
|
-
});
|
|
1021
|
-
} catch (error) {
|
|
1022
|
-
console.error("Validation error:", error);
|
|
1023
|
-
return server.NextResponse.json(
|
|
1024
|
-
{ success: false, error: String(error) },
|
|
1025
|
-
{ status: 500 }
|
|
1026
|
-
);
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
class AIEditorAgent {
|
|
1030
|
-
constructor(session, projectRoot) {
|
|
1031
|
-
this.session = session;
|
|
1032
|
-
this.projectRoot = projectRoot;
|
|
1033
|
-
}
|
|
1034
|
-
/**
|
|
1035
|
-
* Create a new agent instance
|
|
1036
|
-
*/
|
|
1037
|
-
static async create(config) {
|
|
1038
|
-
const sessionOptions = {
|
|
1039
|
-
model: "claude-sonnet-4-5-20250929",
|
|
1040
|
-
// Enable auto-apply for edits without permission prompts
|
|
1041
|
-
permissionMode: "acceptEdits",
|
|
1042
|
-
env: {
|
|
1043
|
-
...process.env,
|
|
1044
|
-
ANTHROPIC_API_KEY: config.apiKey,
|
|
1045
|
-
// Set working directory
|
|
1046
|
-
PWD: config.projectRoot
|
|
1047
|
-
},
|
|
1048
|
-
// Explicitly allow core development tools
|
|
1049
|
-
allowedTools: config.allowedTools || ["Read", "Edit", "Glob", "Grep"],
|
|
1050
|
-
disallowedTools: config.disallowedTools || []
|
|
1051
|
-
};
|
|
1052
|
-
let session;
|
|
1053
|
-
if (config.sessionId) {
|
|
1054
|
-
session = claudeAgentSdk.unstable_v2_resumeSession(config.sessionId, sessionOptions);
|
|
1055
|
-
} else {
|
|
1056
|
-
session = claudeAgentSdk.unstable_v2_createSession(sessionOptions);
|
|
1057
|
-
}
|
|
1058
|
-
return new AIEditorAgent(session, config.projectRoot);
|
|
1059
|
-
}
|
|
1060
|
-
/**
|
|
1061
|
-
* Send a message to the agent and stream responses
|
|
1062
|
-
*/
|
|
1063
|
-
async *sendMessage(message, componentContext) {
|
|
1064
|
-
let fullMessage = `<response_style>
|
|
1065
|
-
KEEP RESPONSES EXTREMELY BRIEF AND STATUS-LIKE.
|
|
1066
|
-
- Use terse status messages: "Reading file.tsx", "Changing color", "Done"
|
|
1067
|
-
- NO conversational language (avoid: "I'll", "Let me", "Sure", "Great", "I can see")
|
|
1068
|
-
- NO explanations unless explicitly asked
|
|
1069
|
-
- State ONLY what you're doing, nothing more
|
|
1070
|
-
- Format: Action + target (e.g., "Updating StatsCard.tsx:24")
|
|
1071
|
-
</response_style>
|
|
1072
|
-
|
|
1073
|
-
`;
|
|
1074
|
-
if (componentContext) {
|
|
1075
|
-
fullMessage += `[Component: ${componentContext.componentName} at ${componentContext.filePath}:${componentContext.lineNumber}]
|
|
1076
|
-
|
|
1077
|
-
`;
|
|
1078
|
-
}
|
|
1079
|
-
fullMessage += message;
|
|
1080
|
-
await this.session.send(fullMessage);
|
|
1081
|
-
for await (const event of this.session.stream()) {
|
|
1082
|
-
yield event;
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
/**
|
|
1086
|
-
* Get the session ID
|
|
1087
|
-
*/
|
|
1088
|
-
getSessionId() {
|
|
1089
|
-
return this.session.sessionId;
|
|
1090
|
-
}
|
|
1091
|
-
/**
|
|
1092
|
-
* Close the session
|
|
1093
|
-
*/
|
|
1094
|
-
close() {
|
|
1095
|
-
this.session.close();
|
|
1096
|
-
}
|
|
1097
|
-
/**
|
|
1098
|
-
* Async disposal support
|
|
1099
|
-
*/
|
|
1100
|
-
async [Symbol.asyncDispose]() {
|
|
1101
|
-
await this.session[Symbol.asyncDispose]();
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
class AgentSessionStore {
|
|
1105
|
-
// 5 minutes
|
|
1106
|
-
constructor() {
|
|
1107
|
-
this.sessions = /* @__PURE__ */ new Map();
|
|
1108
|
-
this.SESSION_TIMEOUT = 1e3 * 60 * 30;
|
|
1109
|
-
this.CLEANUP_INTERVAL = 1e3 * 60 * 5;
|
|
1110
|
-
this.startCleanupTimer();
|
|
1111
|
-
}
|
|
1112
|
-
/**
|
|
1113
|
-
* Create or resume a session
|
|
1114
|
-
*/
|
|
1115
|
-
async createSession(config) {
|
|
1116
|
-
const sessionId = config.sessionId || this.generateSessionId();
|
|
1117
|
-
const existing = this.sessions.get(sessionId);
|
|
1118
|
-
if (existing) {
|
|
1119
|
-
existing.lastActive = Date.now();
|
|
1120
|
-
existing.threadId = config.threadId;
|
|
1121
|
-
return existing;
|
|
1122
|
-
}
|
|
1123
|
-
const agent = await AIEditorAgent.create({
|
|
1124
|
-
apiKey: config.apiKey,
|
|
1125
|
-
projectRoot: config.projectRoot,
|
|
1126
|
-
sessionId: config.sessionId,
|
|
1127
|
-
allowedTools: config.allowedTools,
|
|
1128
|
-
disallowedTools: config.disallowedTools
|
|
1129
|
-
});
|
|
1130
|
-
const session = {
|
|
1131
|
-
sessionId,
|
|
1132
|
-
threadId: config.threadId,
|
|
1133
|
-
agent,
|
|
1134
|
-
createdAt: Date.now(),
|
|
1135
|
-
lastActive: Date.now(),
|
|
1136
|
-
isLocked: false
|
|
1137
|
-
};
|
|
1138
|
-
this.sessions.set(sessionId, session);
|
|
1139
|
-
return session;
|
|
1140
|
-
}
|
|
1141
|
-
/**
|
|
1142
|
-
* Get a session by ID
|
|
1143
|
-
*/
|
|
1144
|
-
getSession(sessionId) {
|
|
1145
|
-
const session = this.sessions.get(sessionId);
|
|
1146
|
-
if (session) {
|
|
1147
|
-
session.lastActive = Date.now();
|
|
1148
|
-
}
|
|
1149
|
-
return session;
|
|
1150
|
-
}
|
|
1151
|
-
/**
|
|
1152
|
-
* Acquire a lock on a session to prevent concurrent edits
|
|
1153
|
-
* Returns true if lock was acquired, false if already locked
|
|
1154
|
-
*/
|
|
1155
|
-
acquireLock(sessionId) {
|
|
1156
|
-
const session = this.sessions.get(sessionId);
|
|
1157
|
-
if (!session) {
|
|
1158
|
-
throw new Error(`Session ${sessionId} not found`);
|
|
1159
|
-
}
|
|
1160
|
-
if (session.isLocked) {
|
|
1161
|
-
return false;
|
|
1162
|
-
}
|
|
1163
|
-
session.isLocked = true;
|
|
1164
|
-
session.lastActive = Date.now();
|
|
1165
|
-
return true;
|
|
1166
|
-
}
|
|
1167
|
-
/**
|
|
1168
|
-
* Release a lock on a session
|
|
1169
|
-
*/
|
|
1170
|
-
releaseLock(sessionId) {
|
|
1171
|
-
const session = this.sessions.get(sessionId);
|
|
1172
|
-
if (session) {
|
|
1173
|
-
session.isLocked = false;
|
|
1174
|
-
session.lastActive = Date.now();
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
/**
|
|
1178
|
-
* Check if any session is currently locked (to prevent concurrent edits)
|
|
1179
|
-
*/
|
|
1180
|
-
hasActiveLock() {
|
|
1181
|
-
for (const session of this.sessions.values()) {
|
|
1182
|
-
if (session.isLocked) {
|
|
1183
|
-
return true;
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
return false;
|
|
1187
|
-
}
|
|
1188
|
-
/**
|
|
1189
|
-
* Get all active sessions for a thread
|
|
1190
|
-
*/
|
|
1191
|
-
getSessionsByThread(threadId) {
|
|
1192
|
-
const sessions = [];
|
|
1193
|
-
for (const session of this.sessions.values()) {
|
|
1194
|
-
if (session.threadId === threadId) {
|
|
1195
|
-
sessions.push(session);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
return sessions;
|
|
1199
|
-
}
|
|
1200
|
-
/**
|
|
1201
|
-
* Delete a session
|
|
1202
|
-
*/
|
|
1203
|
-
deleteSession(sessionId) {
|
|
1204
|
-
const session = this.sessions.get(sessionId);
|
|
1205
|
-
if (session) {
|
|
1206
|
-
session.agent.close();
|
|
1207
|
-
this.sessions.delete(sessionId);
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
/**
|
|
1211
|
-
* Delete all sessions for a thread
|
|
1212
|
-
*/
|
|
1213
|
-
deleteSessionsByThread(threadId) {
|
|
1214
|
-
for (const [sessionId, session] of this.sessions.entries()) {
|
|
1215
|
-
if (session.threadId === threadId) {
|
|
1216
|
-
session.agent.close();
|
|
1217
|
-
this.sessions.delete(sessionId);
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
/**
|
|
1222
|
-
* Get all active sessions
|
|
1223
|
-
*/
|
|
1224
|
-
getAllSessions() {
|
|
1225
|
-
return Array.from(this.sessions.values());
|
|
1226
|
-
}
|
|
1227
|
-
/**
|
|
1228
|
-
* Clean up expired sessions
|
|
1229
|
-
*/
|
|
1230
|
-
cleanupExpiredSessions() {
|
|
1231
|
-
const now = Date.now();
|
|
1232
|
-
const expiredSessions = [];
|
|
1233
|
-
for (const [sessionId, session] of this.sessions.entries()) {
|
|
1234
|
-
const inactiveTime = now - session.lastActive;
|
|
1235
|
-
if (inactiveTime > this.SESSION_TIMEOUT && !session.isLocked) {
|
|
1236
|
-
expiredSessions.push(sessionId);
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
for (const sessionId of expiredSessions) {
|
|
1240
|
-
console.log(
|
|
1241
|
-
`[AgentSessionStore] Cleaning up expired session: ${sessionId}`
|
|
1242
|
-
);
|
|
1243
|
-
this.deleteSession(sessionId);
|
|
1244
|
-
}
|
|
1245
|
-
if (expiredSessions.length > 0) {
|
|
1246
|
-
console.log(
|
|
1247
|
-
`[AgentSessionStore] Cleaned up ${expiredSessions.length} expired sessions`
|
|
1248
|
-
);
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
/**
|
|
1252
|
-
* Start periodic cleanup timer
|
|
1253
|
-
*/
|
|
1254
|
-
startCleanupTimer() {
|
|
1255
|
-
setInterval(() => {
|
|
1256
|
-
this.cleanupExpiredSessions();
|
|
1257
|
-
}, this.CLEANUP_INTERVAL);
|
|
1258
|
-
}
|
|
1259
|
-
/**
|
|
1260
|
-
* Generate a unique session ID
|
|
1261
|
-
*/
|
|
1262
|
-
generateSessionId() {
|
|
1263
|
-
return `session-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
1264
|
-
}
|
|
1265
|
-
/**
|
|
1266
|
-
* Get statistics about the session store
|
|
1267
|
-
*/
|
|
1268
|
-
getStats() {
|
|
1269
|
-
return {
|
|
1270
|
-
totalSessions: this.sessions.size,
|
|
1271
|
-
lockedSessions: Array.from(this.sessions.values()).filter(
|
|
1272
|
-
(s) => s.isLocked
|
|
1273
|
-
).length,
|
|
1274
|
-
sessions: Array.from(this.sessions.values()).map((s) => ({
|
|
1275
|
-
sessionId: s.sessionId,
|
|
1276
|
-
threadId: s.threadId,
|
|
1277
|
-
isLocked: s.isLocked,
|
|
1278
|
-
ageMinutes: Math.floor((Date.now() - s.createdAt) / 1e3 / 60),
|
|
1279
|
-
inactiveMinutes: Math.floor((Date.now() - s.lastActive) / 1e3 / 60)
|
|
1280
|
-
}))
|
|
1281
|
-
};
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
|
-
let sessionStore = null;
|
|
1285
|
-
function getSessionStore() {
|
|
1286
|
-
if (!sessionStore) {
|
|
1287
|
-
sessionStore = new AgentSessionStore();
|
|
1288
|
-
}
|
|
1289
|
-
return sessionStore;
|
|
1290
|
-
}
|
|
1291
|
-
async function handleChat(req) {
|
|
1292
|
-
const devModeError = validateDevMode();
|
|
1293
|
-
if (devModeError) return devModeError;
|
|
1294
|
-
try {
|
|
1295
|
-
const body = await req.json();
|
|
1296
|
-
const { sessionId, threadId, message, componentContext } = body;
|
|
1297
|
-
if (!threadId || !message) {
|
|
1298
|
-
return server.NextResponse.json(
|
|
1299
|
-
{ error: "threadId and message are required" },
|
|
1300
|
-
{ status: 400 }
|
|
1301
|
-
);
|
|
1302
|
-
}
|
|
1303
|
-
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
1304
|
-
if (!apiKey) {
|
|
1305
|
-
return server.NextResponse.json(
|
|
1306
|
-
{ error: "ANTHROPIC_API_KEY not configured" },
|
|
1307
|
-
{ status: 500 }
|
|
1308
|
-
);
|
|
1309
|
-
}
|
|
1310
|
-
const sessionStore2 = getSessionStore();
|
|
1311
|
-
const projectRoot = process.cwd();
|
|
1312
|
-
const session = await sessionStore2.createSession({
|
|
1313
|
-
sessionId,
|
|
1314
|
-
threadId,
|
|
1315
|
-
apiKey,
|
|
1316
|
-
projectRoot
|
|
1317
|
-
});
|
|
1318
|
-
if (!sessionStore2.acquireLock(session.sessionId)) {
|
|
1319
|
-
return server.NextResponse.json(
|
|
1320
|
-
{
|
|
1321
|
-
error: "Another operation is in progress. Please wait for it to complete."
|
|
1322
|
-
},
|
|
1323
|
-
{ status: 409 }
|
|
1324
|
-
);
|
|
1325
|
-
}
|
|
1326
|
-
const encoder = new TextEncoder();
|
|
1327
|
-
const stream = new ReadableStream({
|
|
1328
|
-
async start(controller) {
|
|
1329
|
-
try {
|
|
1330
|
-
controller.enqueue(
|
|
1331
|
-
encoder.encode(
|
|
1332
|
-
`data: ${JSON.stringify({ type: "connected", sessionId: session.sessionId })}
|
|
1333
|
-
|
|
1334
|
-
`
|
|
1335
|
-
)
|
|
1336
|
-
);
|
|
1337
|
-
for await (const event of session.agent.sendMessage(
|
|
1338
|
-
message,
|
|
1339
|
-
componentContext
|
|
1340
|
-
)) {
|
|
1341
|
-
console.log("[AGENT EVENT]", event.type, JSON.stringify(event, null, 2));
|
|
1342
|
-
controller.enqueue(
|
|
1343
|
-
encoder.encode(`data: ${JSON.stringify(event)}
|
|
1344
|
-
|
|
1345
|
-
`)
|
|
1346
|
-
);
|
|
1347
|
-
}
|
|
1348
|
-
controller.enqueue(
|
|
1349
|
-
encoder.encode(`data: ${JSON.stringify({ type: "done" })}
|
|
1350
|
-
|
|
1351
|
-
`)
|
|
1352
|
-
);
|
|
1353
|
-
controller.close();
|
|
1354
|
-
} catch (error) {
|
|
1355
|
-
console.error("[handleChat] Error:", error);
|
|
1356
|
-
controller.enqueue(
|
|
1357
|
-
encoder.encode(
|
|
1358
|
-
`data: ${JSON.stringify({ type: "error", error: error.message || String(error) })}
|
|
1359
|
-
|
|
1360
|
-
`
|
|
1361
|
-
)
|
|
1362
|
-
);
|
|
1363
|
-
controller.close();
|
|
1364
|
-
} finally {
|
|
1365
|
-
sessionStore2.releaseLock(session.sessionId);
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
});
|
|
1369
|
-
return new Response(stream, {
|
|
1370
|
-
headers: {
|
|
1371
|
-
"Content-Type": "text/event-stream",
|
|
1372
|
-
"Cache-Control": "no-cache",
|
|
1373
|
-
Connection: "keep-alive"
|
|
1374
|
-
}
|
|
1375
|
-
});
|
|
1376
|
-
} catch (error) {
|
|
1377
|
-
console.error("[handleChat] Request error:", error);
|
|
1378
|
-
return server.NextResponse.json(
|
|
1379
|
-
{ error: String(error) },
|
|
1380
|
-
{ status: 500 }
|
|
1381
|
-
);
|
|
1382
|
-
}
|
|
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
|
-
}
|
|
1392
|
-
async function handleAIEditorRequest(req, context) {
|
|
1393
|
-
const { path: path2 } = await context.params;
|
|
1394
|
-
const endpoint = path2[0];
|
|
1395
|
-
const method = req.method;
|
|
1396
|
-
switch (endpoint) {
|
|
1397
|
-
case "read":
|
|
1398
|
-
if (method === "GET") return handleRead(req);
|
|
1399
|
-
break;
|
|
1400
|
-
case "undo":
|
|
1401
|
-
if (method === "POST") return handleUndo(req);
|
|
1402
|
-
break;
|
|
1403
|
-
case "resolve":
|
|
1404
|
-
if (method === "POST") return handleResolve(req);
|
|
1405
|
-
break;
|
|
1406
|
-
case "absolute-path":
|
|
1407
|
-
if (method === "GET") return handleAbsolutePath(req);
|
|
1408
|
-
break;
|
|
1409
|
-
case "validate-session":
|
|
1410
|
-
if (method === "POST") return handleValidateSession(req);
|
|
1411
|
-
break;
|
|
1412
|
-
case "chat":
|
|
1413
|
-
if (method === "POST") return handleChat(req);
|
|
1414
|
-
break;
|
|
1415
|
-
case "config":
|
|
1416
|
-
if (method === "GET") return handleConfig();
|
|
1417
|
-
break;
|
|
1418
|
-
}
|
|
1419
|
-
return server.NextResponse.json(
|
|
1420
|
-
{ error: `Unknown endpoint: ${endpoint}` },
|
|
1421
|
-
{ status: 404 }
|
|
1422
|
-
);
|
|
1423
|
-
}
|
|
1424
|
-
async function handleAIEditorPagesRequest(req, res) {
|
|
1425
|
-
const { path: path2 } = req.query;
|
|
1426
|
-
const endpoint = Array.isArray(path2) ? path2[0] : path2;
|
|
1427
|
-
const method = req.method;
|
|
1428
|
-
const url = `http://localhost:3000${req.url}`;
|
|
1429
|
-
const headers = new Headers();
|
|
1430
|
-
Object.entries(req.headers).forEach(([key, value]) => {
|
|
1431
|
-
if (value) {
|
|
1432
|
-
headers.set(key, Array.isArray(value) ? value.join(", ") : value);
|
|
1433
|
-
}
|
|
1434
|
-
});
|
|
1435
|
-
const nextRequest = new Request(url, {
|
|
1436
|
-
method: req.method || "GET",
|
|
1437
|
-
headers,
|
|
1438
|
-
body: method !== "GET" && method !== "HEAD" ? JSON.stringify(req.body) : void 0
|
|
1439
|
-
});
|
|
1440
|
-
let response;
|
|
1441
|
-
switch (endpoint) {
|
|
1442
|
-
case "read":
|
|
1443
|
-
if (method === "GET") response = await handleRead(nextRequest);
|
|
1444
|
-
else break;
|
|
1445
|
-
break;
|
|
1446
|
-
case "undo":
|
|
1447
|
-
if (method === "POST") response = await handleUndo(nextRequest);
|
|
1448
|
-
else break;
|
|
1449
|
-
break;
|
|
1450
|
-
case "resolve":
|
|
1451
|
-
if (method === "POST") response = await handleResolve(nextRequest);
|
|
1452
|
-
else break;
|
|
1453
|
-
break;
|
|
1454
|
-
case "absolute-path":
|
|
1455
|
-
if (method === "GET") response = await handleAbsolutePath(nextRequest);
|
|
1456
|
-
else break;
|
|
1457
|
-
break;
|
|
1458
|
-
case "validate-session":
|
|
1459
|
-
if (method === "POST") response = await handleValidateSession(nextRequest);
|
|
1460
|
-
else break;
|
|
1461
|
-
break;
|
|
1462
|
-
case "chat":
|
|
1463
|
-
if (method === "POST") response = await handleChat(nextRequest);
|
|
1464
|
-
else break;
|
|
1465
|
-
break;
|
|
1466
|
-
case "config":
|
|
1467
|
-
if (method === "GET") response = await handleConfig();
|
|
1468
|
-
else break;
|
|
1469
|
-
break;
|
|
1470
|
-
default:
|
|
1471
|
-
res.status(404).json({ error: `Unknown endpoint: ${endpoint}` });
|
|
1472
|
-
return;
|
|
1473
|
-
}
|
|
1474
|
-
if (!response) {
|
|
1475
|
-
res.status(405).json({ error: `Method ${method} not allowed for ${endpoint}` });
|
|
1476
|
-
return;
|
|
1477
|
-
}
|
|
1478
|
-
const data = await response.json();
|
|
1479
|
-
res.status(response.status).json(data);
|
|
1480
|
-
}
|
|
1481
|
-
const STORAGE_DIR = ".ai-editor";
|
|
1482
|
-
const STORAGE_FILE = "comments.json";
|
|
1483
|
-
function getProjectRoot() {
|
|
1484
|
-
return process.cwd();
|
|
1485
|
-
}
|
|
1486
|
-
function getStoragePath() {
|
|
1487
|
-
return path.join(getProjectRoot(), STORAGE_DIR, STORAGE_FILE);
|
|
1488
|
-
}
|
|
1489
|
-
async function ensureStorageDir() {
|
|
1490
|
-
const dir = path.join(getProjectRoot(), STORAGE_DIR);
|
|
1491
|
-
if (!fs$1.existsSync(dir)) {
|
|
1492
|
-
await fs.mkdir(dir, { recursive: true });
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
1495
|
-
async function handleCommentsRequest(request) {
|
|
1496
|
-
if (process.env.NODE_ENV !== "development") {
|
|
1497
|
-
return server.NextResponse.json(
|
|
1498
|
-
{ error: "Comments API only available in development" },
|
|
1499
|
-
{ status: 403 }
|
|
1500
|
-
);
|
|
1501
|
-
}
|
|
1502
|
-
try {
|
|
1503
|
-
if (request.method === "GET") {
|
|
1504
|
-
const storagePath = getStoragePath();
|
|
1505
|
-
if (!fs$1.existsSync(storagePath)) {
|
|
1506
|
-
return server.NextResponse.json({ comments: [] });
|
|
1507
|
-
}
|
|
1508
|
-
const data = await fs.readFile(storagePath, "utf-8");
|
|
1509
|
-
const comments = JSON.parse(data);
|
|
1510
|
-
return server.NextResponse.json({ comments });
|
|
1511
|
-
}
|
|
1512
|
-
if (request.method === "POST") {
|
|
1513
|
-
const body = await request.json();
|
|
1514
|
-
const { action } = body;
|
|
1515
|
-
if (action === "save") {
|
|
1516
|
-
const { comments } = body;
|
|
1517
|
-
await ensureStorageDir();
|
|
1518
|
-
const storagePath = getStoragePath();
|
|
1519
|
-
await fs.writeFile(storagePath, JSON.stringify(comments, null, 2), "utf-8");
|
|
1520
|
-
return server.NextResponse.json({ success: true });
|
|
1521
|
-
}
|
|
1522
|
-
if (action === "clear") {
|
|
1523
|
-
await ensureStorageDir();
|
|
1524
|
-
const storagePath = getStoragePath();
|
|
1525
|
-
await fs.writeFile(storagePath, JSON.stringify([]), "utf-8");
|
|
1526
|
-
return server.NextResponse.json({ success: true });
|
|
1527
|
-
}
|
|
1528
|
-
return server.NextResponse.json(
|
|
1529
|
-
{ error: "Invalid action" },
|
|
1530
|
-
{ status: 400 }
|
|
1531
|
-
);
|
|
1532
|
-
}
|
|
1533
|
-
return server.NextResponse.json(
|
|
1534
|
-
{ error: "Method not allowed" },
|
|
1535
|
-
{ status: 405 }
|
|
1536
|
-
);
|
|
1537
|
-
} catch (error) {
|
|
1538
|
-
console.error("Comment storage error:", error);
|
|
1539
|
-
return server.NextResponse.json(
|
|
1540
|
-
{ error: "Internal server error" },
|
|
1541
|
-
{ status: 500 }
|
|
1542
|
-
);
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
exports.extractComponentName = extractComponentName;
|
|
1546
|
-
exports.extractComponentNameFromStack = extractComponentNameFromStack;
|
|
1547
|
-
exports.fileExists = fileExists$1;
|
|
1548
|
-
exports.findTargetElement = findTargetElement;
|
|
1549
|
-
exports.getAttributeValue = getAttributeValue;
|
|
1550
|
-
exports.getJSXMemberName = getJSXMemberName;
|
|
1551
|
-
exports.getOriginalPositionFromDebugStack = getOriginalPositionFromDebugStack;
|
|
1552
|
-
exports.handleAIEditorPagesRequest = handleAIEditorPagesRequest;
|
|
1553
|
-
exports.handleAIEditorRequest = handleAIEditorRequest;
|
|
1554
|
-
exports.handleAbsolutePath = handleAbsolutePath;
|
|
1555
|
-
exports.handleCommentsRequest = handleCommentsRequest;
|
|
1556
|
-
exports.handleRead = handleRead;
|
|
1557
|
-
exports.handleResolve = handleResolve;
|
|
1558
|
-
exports.handleUndo = handleUndo;
|
|
1559
|
-
exports.handleValidateSession = handleValidateSession;
|
|
1560
|
-
exports.isPathSecure = isPathSecure;
|
|
1561
|
-
exports.normalizePath = normalizePath;
|
|
1562
|
-
exports.parseDebugStack = parseDebugStack;
|
|
1563
|
-
exports.parseDebugStackFrames = parseDebugStackFrames;
|
|
1564
|
-
exports.parseFile = parseFile;
|
|
1565
|
-
exports.resolveFilePath = resolveFilePath;
|
|
1566
|
-
exports.resolveOriginalPosition = resolveOriginalPosition;
|
|
1567
|
-
exports.scoreElementMatch = scoreElementMatch;
|
|
1568
|
-
exports.validateDevMode = validateDevMode;
|
|
1569
|
-
exports.validateGeneratedCode = validateGeneratedCode;
|
|
1570
|
-
//# sourceMappingURL=comments-BQ6AQ-eC.cjs.map
|