next-ai-editor 0.1.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/LICENSE +21 -0
- package/README.md +213 -0
- package/dist/AIEditorProvider-Bs9zUVrL.cjs +1722 -0
- package/dist/AIEditorProvider-Bs9zUVrL.cjs.map +1 -0
- package/dist/AIEditorProvider-D-w9-GZb.js +1723 -0
- package/dist/AIEditorProvider-D-w9-GZb.js.map +1 -0
- package/dist/client/AIEditorProvider.d.ts +52 -0
- package/dist/client/AIEditorProvider.d.ts.map +1 -0
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client.cjs +6 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.js +6 -0
- package/dist/client.js.map +1 -0
- package/dist/index-BFa7H-uO.js +1145 -0
- package/dist/index-BFa7H-uO.js.map +1 -0
- package/dist/index-DnoYi4f8.cjs +1162 -0
- package/dist/index-DnoYi4f8.cjs.map +1 -0
- package/dist/index.cjs +26 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/server/handlers/absolute-path.d.ts +3 -0
- package/dist/server/handlers/absolute-path.d.ts.map +1 -0
- package/dist/server/handlers/edit.d.ts +3 -0
- package/dist/server/handlers/edit.d.ts.map +1 -0
- package/dist/server/handlers/index.d.ts +18 -0
- package/dist/server/handlers/index.d.ts.map +1 -0
- package/dist/server/handlers/read.d.ts +3 -0
- package/dist/server/handlers/read.d.ts.map +1 -0
- package/dist/server/handlers/resolve.d.ts +3 -0
- package/dist/server/handlers/resolve.d.ts.map +1 -0
- package/dist/server/handlers/undo.d.ts +3 -0
- package/dist/server/handlers/undo.d.ts.map +1 -0
- package/dist/server/handlers/validate-session.d.ts +3 -0
- package/dist/server/handlers/validate-session.d.ts.map +1 -0
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/utils/ast.d.ts +39 -0
- package/dist/server/utils/ast.d.ts.map +1 -0
- package/dist/server/utils/file-system.d.ts +24 -0
- package/dist/server/utils/file-system.d.ts.map +1 -0
- package/dist/server/utils/source-map.d.ts +29 -0
- package/dist/server/utils/source-map.d.ts.map +1 -0
- package/dist/server.cjs +24 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.js +24 -0
- package/dist/server.js.map +1 -0
- package/dist/shared/storage.d.ts +53 -0
- package/dist/shared/storage.d.ts.map +1 -0
- package/dist/shared/types.d.ts +44 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/package.json +87 -0
|
@@ -0,0 +1,1723 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, Fragment, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useState, useEffect, useCallback, useContext } from "react";
|
|
4
|
+
import { Prism } from "react-syntax-highlighter";
|
|
5
|
+
import { vscDarkPlus, vs } from "react-syntax-highlighter/dist/esm/styles/prism";
|
|
6
|
+
const _AIEditorStorage = class _AIEditorStorage {
|
|
7
|
+
// 5MB
|
|
8
|
+
/**
|
|
9
|
+
* Generate storage key from file path and component name
|
|
10
|
+
* Format: "ai-editor:session:components/Header.tsx#Header"
|
|
11
|
+
*/
|
|
12
|
+
static getSessionKey(filePath, componentName) {
|
|
13
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
14
|
+
return `${this.STORAGE_PREFIX}${normalizedPath}#${componentName}`;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Save session to localStorage
|
|
18
|
+
*/
|
|
19
|
+
static saveSession(session) {
|
|
20
|
+
try {
|
|
21
|
+
const key = this.getSessionKey(
|
|
22
|
+
session.sourceLocation.filePath,
|
|
23
|
+
session.sourceLocation.componentName
|
|
24
|
+
);
|
|
25
|
+
if (this.getStorageSize() > this.MAX_STORAGE_SIZE) {
|
|
26
|
+
console.warn("Storage size exceeded, pruning old sessions...");
|
|
27
|
+
this.pruneOldSessions(7 * 24 * 60 * 60 * 1e3);
|
|
28
|
+
}
|
|
29
|
+
const serialized = JSON.stringify(session);
|
|
30
|
+
localStorage.setItem(key, serialized);
|
|
31
|
+
return true;
|
|
32
|
+
} catch (e) {
|
|
33
|
+
if (e instanceof Error && e.name === "QuotaExceededError") {
|
|
34
|
+
console.error("localStorage quota exceeded");
|
|
35
|
+
this.pruneOldSessions(7 * 24 * 60 * 60 * 1e3);
|
|
36
|
+
try {
|
|
37
|
+
const key = this.getSessionKey(
|
|
38
|
+
session.sourceLocation.filePath,
|
|
39
|
+
session.sourceLocation.componentName
|
|
40
|
+
);
|
|
41
|
+
const serialized = JSON.stringify(session);
|
|
42
|
+
localStorage.setItem(key, serialized);
|
|
43
|
+
return true;
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
console.error("Failed to save session:", e);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Load session by filePath + componentName
|
|
54
|
+
*/
|
|
55
|
+
static loadSession(filePath, componentName) {
|
|
56
|
+
try {
|
|
57
|
+
const key = this.getSessionKey(filePath, componentName);
|
|
58
|
+
const stored = localStorage.getItem(key);
|
|
59
|
+
if (!stored) return null;
|
|
60
|
+
const session = JSON.parse(stored);
|
|
61
|
+
if (!session.version || !session.sourceLocation || !session.editHistory) {
|
|
62
|
+
console.warn("Invalid session schema, removing corrupted data");
|
|
63
|
+
localStorage.removeItem(key);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return session;
|
|
67
|
+
} catch (e) {
|
|
68
|
+
console.error("Failed to load session:", e);
|
|
69
|
+
const key = this.getSessionKey(filePath, componentName);
|
|
70
|
+
localStorage.removeItem(key);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Delete specific session
|
|
76
|
+
*/
|
|
77
|
+
static deleteSession(filePath, componentName) {
|
|
78
|
+
try {
|
|
79
|
+
const key = this.getSessionKey(filePath, componentName);
|
|
80
|
+
localStorage.removeItem(key);
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.error("Failed to delete session:", e);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get all session keys
|
|
87
|
+
*/
|
|
88
|
+
static getAllSessionKeys() {
|
|
89
|
+
const keys = [];
|
|
90
|
+
try {
|
|
91
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
92
|
+
const key = localStorage.key(i);
|
|
93
|
+
if (key && key.startsWith(this.STORAGE_PREFIX)) {
|
|
94
|
+
keys.push(key);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} catch (e) {
|
|
98
|
+
console.error("Failed to get session keys:", e);
|
|
99
|
+
}
|
|
100
|
+
return keys;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Clear all sessions
|
|
104
|
+
*/
|
|
105
|
+
static clearAllSessions() {
|
|
106
|
+
try {
|
|
107
|
+
const keys = this.getAllSessionKeys();
|
|
108
|
+
keys.forEach((key) => localStorage.removeItem(key));
|
|
109
|
+
console.log(`Cleared ${keys.length} session(s)`);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
console.error("Failed to clear sessions:", e);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Get total storage usage in bytes
|
|
116
|
+
*/
|
|
117
|
+
static getStorageSize() {
|
|
118
|
+
let total = 0;
|
|
119
|
+
try {
|
|
120
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
121
|
+
const key = localStorage.key(i);
|
|
122
|
+
if (key && key.startsWith(this.STORAGE_PREFIX)) {
|
|
123
|
+
const value = localStorage.getItem(key);
|
|
124
|
+
if (value) {
|
|
125
|
+
total += (key.length + value.length) * 2;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} catch (e) {
|
|
130
|
+
console.error("Failed to calculate storage size:", e);
|
|
131
|
+
}
|
|
132
|
+
return total;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Remove sessions older than maxAge milliseconds
|
|
136
|
+
*/
|
|
137
|
+
static pruneOldSessions(maxAge) {
|
|
138
|
+
try {
|
|
139
|
+
const now = Date.now();
|
|
140
|
+
const keys = this.getAllSessionKeys();
|
|
141
|
+
keys.forEach((key) => {
|
|
142
|
+
const value = localStorage.getItem(key);
|
|
143
|
+
if (!value) return;
|
|
144
|
+
try {
|
|
145
|
+
const session = JSON.parse(value);
|
|
146
|
+
const age = now - session.lastModified;
|
|
147
|
+
if (age > maxAge) {
|
|
148
|
+
console.log(`Pruning old session: ${key} (age: ${Math.round(age / (24 * 60 * 60 * 1e3))} days)`);
|
|
149
|
+
localStorage.removeItem(key);
|
|
150
|
+
}
|
|
151
|
+
} catch (e) {
|
|
152
|
+
console.warn(`Removing corrupted session: ${key}`);
|
|
153
|
+
localStorage.removeItem(key);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
} catch (e) {
|
|
157
|
+
console.error("Failed to prune sessions:", e);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Compute SHA-256 hash of file content using Web Crypto API
|
|
162
|
+
*/
|
|
163
|
+
static async hashFileContent(content) {
|
|
164
|
+
try {
|
|
165
|
+
const encoder = new TextEncoder();
|
|
166
|
+
const data = encoder.encode(content);
|
|
167
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
168
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
169
|
+
const hashHex = hashArray.map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
170
|
+
return hashHex;
|
|
171
|
+
} catch (e) {
|
|
172
|
+
console.error("Failed to hash content:", e);
|
|
173
|
+
return this.simpleHash(content);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Simple hash fallback (for environments without Web Crypto API)
|
|
178
|
+
*/
|
|
179
|
+
static simpleHash(str) {
|
|
180
|
+
let hash = 0;
|
|
181
|
+
for (let i = 0; i < str.length; i++) {
|
|
182
|
+
const char = str.charCodeAt(i);
|
|
183
|
+
hash = (hash << 5) - hash + char;
|
|
184
|
+
hash = hash & hash;
|
|
185
|
+
}
|
|
186
|
+
return hash.toString(16);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Get most recently modified session
|
|
190
|
+
*/
|
|
191
|
+
static getLastActiveSession() {
|
|
192
|
+
try {
|
|
193
|
+
const keys = this.getAllSessionKeys();
|
|
194
|
+
if (keys.length === 0) return null;
|
|
195
|
+
let latestSession = null;
|
|
196
|
+
let latestTime = 0;
|
|
197
|
+
keys.forEach((key) => {
|
|
198
|
+
const value = localStorage.getItem(key);
|
|
199
|
+
if (!value) return;
|
|
200
|
+
try {
|
|
201
|
+
const session = JSON.parse(value);
|
|
202
|
+
if (!session.version || !session.sourceLocation || !session.editHistory) {
|
|
203
|
+
console.warn(`Skipping invalid session: ${key}`);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (session.lastModified > latestTime) {
|
|
207
|
+
latestTime = session.lastModified;
|
|
208
|
+
latestSession = session;
|
|
209
|
+
}
|
|
210
|
+
} catch (e) {
|
|
211
|
+
console.warn(`Skipping corrupted session: ${key}`);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
return latestSession;
|
|
215
|
+
} catch (e) {
|
|
216
|
+
console.error("Failed to get last active session:", e);
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
_AIEditorStorage.STORAGE_PREFIX = "ai-editor:session:";
|
|
222
|
+
_AIEditorStorage.VERSION = "1.0.0";
|
|
223
|
+
_AIEditorStorage.MAX_STORAGE_SIZE = 5 * 1024 * 1024;
|
|
224
|
+
let AIEditorStorage = _AIEditorStorage;
|
|
225
|
+
const ENABLE_SESSION_PERSISTENCE = false;
|
|
226
|
+
const sourceResolutionCache = /* @__PURE__ */ new Map();
|
|
227
|
+
const inflightSourceResolutions = /* @__PURE__ */ new Map();
|
|
228
|
+
function inferComponentNameFromPath(filePath) {
|
|
229
|
+
const fileName = filePath.split("/").pop() || "";
|
|
230
|
+
const nameWithoutExt = fileName.replace(/\.(tsx?|jsx?)$/, "");
|
|
231
|
+
return nameWithoutExt.charAt(0).toUpperCase() + nameWithoutExt.slice(1);
|
|
232
|
+
}
|
|
233
|
+
async function resolveSourceLocation(source) {
|
|
234
|
+
if (typeof window === "undefined" || process.env.NODE_ENV !== "development") {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
if (!source.debugStack) {
|
|
238
|
+
console.warn("No debugStack available for resolution:", source);
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
const cacheKey = source.debugStack;
|
|
242
|
+
const cached = sourceResolutionCache.get(cacheKey);
|
|
243
|
+
if (cached) {
|
|
244
|
+
const resolved2 = { ...source, ...cached };
|
|
245
|
+
if (resolved2.componentName === "Unknown" && resolved2.filePath) {
|
|
246
|
+
resolved2.componentName = inferComponentNameFromPath(resolved2.filePath);
|
|
247
|
+
}
|
|
248
|
+
return resolved2;
|
|
249
|
+
}
|
|
250
|
+
let inflight = inflightSourceResolutions.get(cacheKey);
|
|
251
|
+
if (!inflight) {
|
|
252
|
+
inflight = fetch("/api/ai-editor/resolve", {
|
|
253
|
+
method: "POST",
|
|
254
|
+
headers: { "Content-Type": "application/json" },
|
|
255
|
+
body: JSON.stringify({ debugStack: cacheKey })
|
|
256
|
+
}).then(async (res) => {
|
|
257
|
+
if (!res.ok) {
|
|
258
|
+
const errorText = await res.text();
|
|
259
|
+
console.error(
|
|
260
|
+
`Resolve API error ${res.status}:`,
|
|
261
|
+
errorText,
|
|
262
|
+
"for stack:",
|
|
263
|
+
cacheKey.substring(0, 200)
|
|
264
|
+
);
|
|
265
|
+
return inferFilePathFromComponentName(source.componentName);
|
|
266
|
+
}
|
|
267
|
+
const data = await res.json();
|
|
268
|
+
if ((data == null ? void 0 : data.success) && data.filePath && data.lineNumber) {
|
|
269
|
+
const resolved2 = {
|
|
270
|
+
filePath: data.filePath,
|
|
271
|
+
lineNumber: data.lineNumber,
|
|
272
|
+
columnNumber: typeof data.columnNumber === "number" ? data.columnNumber : source.columnNumber
|
|
273
|
+
};
|
|
274
|
+
sourceResolutionCache.set(cacheKey, resolved2);
|
|
275
|
+
return resolved2;
|
|
276
|
+
}
|
|
277
|
+
console.warn("Resolve API returned unsuccessful response:", data);
|
|
278
|
+
return inferFilePathFromComponentName(source.componentName);
|
|
279
|
+
}).catch((err) => {
|
|
280
|
+
console.error("Error calling resolve API:", err);
|
|
281
|
+
return inferFilePathFromComponentName(source.componentName);
|
|
282
|
+
}).finally(() => {
|
|
283
|
+
inflightSourceResolutions.delete(cacheKey);
|
|
284
|
+
});
|
|
285
|
+
inflightSourceResolutions.set(cacheKey, inflight);
|
|
286
|
+
}
|
|
287
|
+
const resolvedInfo = await inflight;
|
|
288
|
+
if (!resolvedInfo) return null;
|
|
289
|
+
const resolved = {
|
|
290
|
+
...source,
|
|
291
|
+
filePath: resolvedInfo.filePath,
|
|
292
|
+
lineNumber: resolvedInfo.lineNumber,
|
|
293
|
+
columnNumber: resolvedInfo.columnNumber
|
|
294
|
+
};
|
|
295
|
+
if (resolved.componentName === "Unknown" && resolved.filePath) {
|
|
296
|
+
resolved.componentName = inferComponentNameFromPath(resolved.filePath);
|
|
297
|
+
}
|
|
298
|
+
return resolved;
|
|
299
|
+
}
|
|
300
|
+
async function inferFilePathFromComponentName(componentName) {
|
|
301
|
+
if (!componentName || componentName === "Unknown") return null;
|
|
302
|
+
const possiblePaths = [
|
|
303
|
+
`components/${componentName}.tsx`,
|
|
304
|
+
`components/${componentName}.jsx`,
|
|
305
|
+
`app/${componentName}.tsx`,
|
|
306
|
+
`app/${componentName}.jsx`,
|
|
307
|
+
`src/components/${componentName}.tsx`,
|
|
308
|
+
`src/components/${componentName}.jsx`
|
|
309
|
+
];
|
|
310
|
+
for (const tryPath of possiblePaths) {
|
|
311
|
+
try {
|
|
312
|
+
const response = await fetch(
|
|
313
|
+
`/api/ai-editor/absolute-path?path=${encodeURIComponent(tryPath)}`
|
|
314
|
+
);
|
|
315
|
+
if (response.ok) {
|
|
316
|
+
const data = await response.json();
|
|
317
|
+
if (data.success) {
|
|
318
|
+
console.log(
|
|
319
|
+
`Inferred file path for ${componentName}: ${tryPath}`
|
|
320
|
+
);
|
|
321
|
+
return {
|
|
322
|
+
filePath: tryPath,
|
|
323
|
+
lineNumber: 1,
|
|
324
|
+
columnNumber: 0
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
} catch (err) {
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
console.warn(`Could not infer file path for component: ${componentName}`);
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
const EditorContext = createContext(null);
|
|
335
|
+
function useAIEditor() {
|
|
336
|
+
const ctx = useContext(EditorContext);
|
|
337
|
+
if (!ctx) throw new Error("useAIEditor must be used within AIEditorProvider");
|
|
338
|
+
return ctx;
|
|
339
|
+
}
|
|
340
|
+
function getFiberFromElement(element) {
|
|
341
|
+
const key = Object.keys(element).find(
|
|
342
|
+
(k) => k.startsWith("__reactFiber$") || k.startsWith("__reactInternalInstance$")
|
|
343
|
+
);
|
|
344
|
+
return key ? element[key] : null;
|
|
345
|
+
}
|
|
346
|
+
function getComponentName(fiber) {
|
|
347
|
+
var _a;
|
|
348
|
+
if (!fiber) return "Unknown";
|
|
349
|
+
const type = fiber.type;
|
|
350
|
+
if (typeof type === "string") return type;
|
|
351
|
+
if (typeof type === "function")
|
|
352
|
+
return type.displayName || type.name || "Anonymous";
|
|
353
|
+
if (type == null ? void 0 : type.displayName) return type.displayName;
|
|
354
|
+
if ((_a = type == null ? void 0 : type.render) == null ? void 0 : _a.name) return type.render.name;
|
|
355
|
+
return "Unknown";
|
|
356
|
+
}
|
|
357
|
+
function isUserComponent(fiber) {
|
|
358
|
+
if (fiber.tag === 5 || fiber.tag === 6 || fiber.tag === 3) return false;
|
|
359
|
+
const type = fiber.type;
|
|
360
|
+
if (!type || typeof type === "string") return false;
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
function captureElementContext(element, fiber) {
|
|
364
|
+
const tagName = element.tagName.toLowerCase();
|
|
365
|
+
let textContent;
|
|
366
|
+
const directText = Array.from(element.childNodes).filter((n) => n.nodeType === Node.TEXT_NODE).map((n) => {
|
|
367
|
+
var _a;
|
|
368
|
+
return (_a = n.textContent) == null ? void 0 : _a.trim();
|
|
369
|
+
}).filter(Boolean).join(" ");
|
|
370
|
+
if (directText) {
|
|
371
|
+
textContent = directText.slice(0, 50);
|
|
372
|
+
} else if (element.textContent) {
|
|
373
|
+
textContent = element.textContent.trim().slice(0, 50);
|
|
374
|
+
}
|
|
375
|
+
const className = typeof element.className === "string" ? element.className.slice(0, 100) : void 0;
|
|
376
|
+
const nthOfType = countNthOfType(element, tagName);
|
|
377
|
+
const key = (fiber == null ? void 0 : fiber.key) ? String(fiber.key) : void 0;
|
|
378
|
+
const props = extractProps(fiber);
|
|
379
|
+
return { tagName, textContent, className, key, nthOfType, props };
|
|
380
|
+
}
|
|
381
|
+
function countNthOfType(element, tagName) {
|
|
382
|
+
let boundary = element.parentElement;
|
|
383
|
+
while (boundary) {
|
|
384
|
+
const fiber = getFiberFromElement(boundary);
|
|
385
|
+
if (fiber && isUserComponent(fiber)) break;
|
|
386
|
+
boundary = boundary.parentElement;
|
|
387
|
+
}
|
|
388
|
+
if (!boundary) boundary = element.parentElement;
|
|
389
|
+
if (!boundary) return 1;
|
|
390
|
+
const sameTagElements = [];
|
|
391
|
+
if (boundary.tagName.toLowerCase() === tagName.toLowerCase()) {
|
|
392
|
+
sameTagElements.push(boundary);
|
|
393
|
+
}
|
|
394
|
+
sameTagElements.push(...Array.from(boundary.querySelectorAll(tagName)));
|
|
395
|
+
let count = 1;
|
|
396
|
+
for (const el of sameTagElements) {
|
|
397
|
+
if (el === element) break;
|
|
398
|
+
count++;
|
|
399
|
+
}
|
|
400
|
+
console.log(
|
|
401
|
+
`[countNthOfType] tagName=${tagName}, found ${sameTagElements.length} elements (including boundary), this is #${count}`
|
|
402
|
+
);
|
|
403
|
+
return count;
|
|
404
|
+
}
|
|
405
|
+
function extractProps(fiber) {
|
|
406
|
+
if (!fiber) return void 0;
|
|
407
|
+
const fiberProps = fiber.memoizedProps || fiber.pendingProps;
|
|
408
|
+
if (!fiberProps || typeof fiberProps !== "object") return void 0;
|
|
409
|
+
const props = {};
|
|
410
|
+
const identifyingKeys = [
|
|
411
|
+
"id",
|
|
412
|
+
"name",
|
|
413
|
+
"type",
|
|
414
|
+
"href",
|
|
415
|
+
"src",
|
|
416
|
+
"alt",
|
|
417
|
+
"role",
|
|
418
|
+
"aria-label",
|
|
419
|
+
"data-testid"
|
|
420
|
+
];
|
|
421
|
+
for (const key of identifyingKeys) {
|
|
422
|
+
if (key in fiberProps && typeof fiberProps[key] === "string") {
|
|
423
|
+
props[key] = fiberProps[key];
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (fiberProps.style && typeof fiberProps.style === "object") {
|
|
427
|
+
props._styleKeys = Object.keys(fiberProps.style).slice(0, 5);
|
|
428
|
+
}
|
|
429
|
+
return Object.keys(props).length > 0 ? props : void 0;
|
|
430
|
+
}
|
|
431
|
+
function findSourceFromFiber(fiber) {
|
|
432
|
+
if (!fiber) return null;
|
|
433
|
+
let current = fiber;
|
|
434
|
+
const visited = /* @__PURE__ */ new Set();
|
|
435
|
+
while (current && !visited.has(current)) {
|
|
436
|
+
visited.add(current);
|
|
437
|
+
const sourceData = current._debugSource || extractFromDebugStack(current) || extractFromDebugOwner(current);
|
|
438
|
+
if (sourceData == null ? void 0 : sourceData.fileName) {
|
|
439
|
+
const filePath = cleanPath(sourceData.fileName);
|
|
440
|
+
if (!shouldSkip(filePath)) {
|
|
441
|
+
let componentFiber = current;
|
|
442
|
+
while (componentFiber) {
|
|
443
|
+
if (isUserComponent(componentFiber)) {
|
|
444
|
+
const componentName = getComponentName(componentFiber);
|
|
445
|
+
const isNextJSInternal = [
|
|
446
|
+
"Segment",
|
|
447
|
+
"Boundary",
|
|
448
|
+
"Router",
|
|
449
|
+
"Handler",
|
|
450
|
+
"Context",
|
|
451
|
+
"Layout",
|
|
452
|
+
"Template",
|
|
453
|
+
"Scroll",
|
|
454
|
+
"Focus",
|
|
455
|
+
"Loading",
|
|
456
|
+
"Error"
|
|
457
|
+
].some((pattern) => componentName.includes(pattern));
|
|
458
|
+
if (!isNextJSInternal) {
|
|
459
|
+
const rawDebugStack = current._debugStack ? String(current._debugStack.stack || current._debugStack) : void 0;
|
|
460
|
+
return {
|
|
461
|
+
filePath,
|
|
462
|
+
lineNumber: sourceData.lineNumber || 1,
|
|
463
|
+
columnNumber: sourceData.columnNumber || 0,
|
|
464
|
+
componentName,
|
|
465
|
+
debugStack: rawDebugStack
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
componentFiber = componentFiber.return || componentFiber._debugOwner || null;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
current = current.return || current._debugOwner || null;
|
|
474
|
+
}
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
function extractFromDebugStack(fiber) {
|
|
478
|
+
const stack = fiber._debugStack;
|
|
479
|
+
if (!stack) return null;
|
|
480
|
+
const stackStr = stack.stack || String(stack);
|
|
481
|
+
const frames = stackStr.split("\n");
|
|
482
|
+
const skipPatterns = [
|
|
483
|
+
"node_modules",
|
|
484
|
+
"SegmentViewNode",
|
|
485
|
+
"LayoutRouter",
|
|
486
|
+
"ErrorBoundary",
|
|
487
|
+
"fakeJSXCallSite"
|
|
488
|
+
];
|
|
489
|
+
for (const frame of frames) {
|
|
490
|
+
if (skipPatterns.some((p) => frame.includes(p))) continue;
|
|
491
|
+
const match = frame.match(/at\s+(\w+)\s+\((.+?):(\d+):(\d+)\)?$/);
|
|
492
|
+
if (match) {
|
|
493
|
+
const fileName = cleanPath(match[2].replace(/\?[^:]*$/, ""));
|
|
494
|
+
if (!shouldSkip(fileName)) {
|
|
495
|
+
return {
|
|
496
|
+
fileName,
|
|
497
|
+
lineNumber: parseInt(match[3], 10),
|
|
498
|
+
columnNumber: parseInt(match[4], 10)
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
function extractFromDebugOwner(fiber) {
|
|
506
|
+
var _a, _b;
|
|
507
|
+
const owner = fiber._debugOwner;
|
|
508
|
+
if (!owner) return null;
|
|
509
|
+
if ((_a = owner._debugSource) == null ? void 0 : _a.fileName) {
|
|
510
|
+
return owner._debugSource;
|
|
511
|
+
}
|
|
512
|
+
const stack = owner.stack;
|
|
513
|
+
if (Array.isArray(stack) && ((_b = stack[0]) == null ? void 0 : _b.fileName)) {
|
|
514
|
+
return stack[0];
|
|
515
|
+
}
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
function cleanPath(p) {
|
|
519
|
+
let cleaned = p;
|
|
520
|
+
if (cleaned.startsWith("file://")) {
|
|
521
|
+
cleaned = cleaned.replace(/^file:\/\//, "");
|
|
522
|
+
}
|
|
523
|
+
try {
|
|
524
|
+
if (cleaned.includes("%")) {
|
|
525
|
+
cleaned = decodeURIComponent(cleaned);
|
|
526
|
+
}
|
|
527
|
+
} catch (e) {
|
|
528
|
+
}
|
|
529
|
+
cleaned = cleaned.replace(/^webpack-internal:\/\/\/\([^)]+\)\/\.\//, "").replace(/^webpack-internal:\/\/\/\([^)]+\)\//, "").replace(/^webpack-internal:\/\//, "").replace(/^webpack:\/\/[^/]*\//, "").replace(/^about:\/\/React\/Server\//, "").replace(/^\([^)]+\)\//, "").replace(/^\.\//, "").replace(/\?.*$/, "");
|
|
530
|
+
return cleaned;
|
|
531
|
+
}
|
|
532
|
+
function shouldSkip(p) {
|
|
533
|
+
if (!p) return true;
|
|
534
|
+
return ["node_modules", "next/dist", "react-dom", "ai-editor-provider"].some(
|
|
535
|
+
(s) => p.includes(s)
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
function getSourceFromElement(element) {
|
|
539
|
+
let current = element;
|
|
540
|
+
let fiber = null;
|
|
541
|
+
while (current && current !== document.body) {
|
|
542
|
+
fiber = getFiberFromElement(current);
|
|
543
|
+
if (fiber) break;
|
|
544
|
+
current = current.parentElement;
|
|
545
|
+
}
|
|
546
|
+
if (!fiber) return null;
|
|
547
|
+
const source = findSourceFromFiber(fiber);
|
|
548
|
+
if (!source) return null;
|
|
549
|
+
const elementContext = captureElementContext(element, fiber);
|
|
550
|
+
return { ...source, elementContext };
|
|
551
|
+
}
|
|
552
|
+
if (typeof window !== "undefined") {
|
|
553
|
+
window.__getSource = getSourceFromElement;
|
|
554
|
+
}
|
|
555
|
+
function AIEditorProvider({
|
|
556
|
+
children,
|
|
557
|
+
theme = "dark"
|
|
558
|
+
}) {
|
|
559
|
+
const [isEnabled, setEnabled] = useState(false);
|
|
560
|
+
const [selectedSource, setSelectedSource] = useState(
|
|
561
|
+
null
|
|
562
|
+
);
|
|
563
|
+
const [selectedDOMElement, setSelectedDOMElement] = useState(
|
|
564
|
+
null
|
|
565
|
+
);
|
|
566
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
567
|
+
const [editHistory, setEditHistory] = useState([]);
|
|
568
|
+
const [showStaleWarning, setShowStaleWarning] = useState(false);
|
|
569
|
+
const [staleSession, setStaleSession] = useState(null);
|
|
570
|
+
const [hasActiveSession, setHasActiveSession] = useState(false);
|
|
571
|
+
useEffect(() => {
|
|
572
|
+
const handleKey = (e) => {
|
|
573
|
+
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === "e") {
|
|
574
|
+
e.preventDefault();
|
|
575
|
+
setEnabled((p) => !p);
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
window.addEventListener("keydown", handleKey);
|
|
579
|
+
return () => window.removeEventListener("keydown", handleKey);
|
|
580
|
+
}, []);
|
|
581
|
+
useEffect(() => {
|
|
582
|
+
return;
|
|
583
|
+
}, []);
|
|
584
|
+
const restoreSession = useCallback(async (session) => {
|
|
585
|
+
const sourceLocation = session.sourceLocation;
|
|
586
|
+
setSelectedSource(sourceLocation);
|
|
587
|
+
setEditHistory(session.editHistory);
|
|
588
|
+
if (sourceLocation.debugStack) {
|
|
589
|
+
const resolved = await resolveSourceLocation(sourceLocation);
|
|
590
|
+
if (resolved) {
|
|
591
|
+
setSelectedSource(resolved);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}, []);
|
|
595
|
+
const handleClearSession = useCallback(() => {
|
|
596
|
+
if (selectedSource) {
|
|
597
|
+
AIEditorStorage.deleteSession(
|
|
598
|
+
selectedSource.filePath,
|
|
599
|
+
selectedSource.componentName
|
|
600
|
+
);
|
|
601
|
+
setHasActiveSession(false);
|
|
602
|
+
setSelectedSource(null);
|
|
603
|
+
setEditHistory([]);
|
|
604
|
+
}
|
|
605
|
+
}, [selectedSource]);
|
|
606
|
+
const submitEdit = useCallback(
|
|
607
|
+
async (suggestion) => {
|
|
608
|
+
if (!selectedSource)
|
|
609
|
+
return { success: false, error: "No element selected" };
|
|
610
|
+
setIsLoading(true);
|
|
611
|
+
try {
|
|
612
|
+
const res = await fetch("/api/ai-editor/edit", {
|
|
613
|
+
method: "POST",
|
|
614
|
+
headers: { "Content-Type": "application/json" },
|
|
615
|
+
body: JSON.stringify({
|
|
616
|
+
filePath: selectedSource.filePath,
|
|
617
|
+
lineNumber: selectedSource.lineNumber,
|
|
618
|
+
componentName: selectedSource.componentName,
|
|
619
|
+
suggestion,
|
|
620
|
+
// NEW: Pass element context for precise matching
|
|
621
|
+
elementContext: selectedSource.elementContext,
|
|
622
|
+
// Pass debugStack for server-side source map resolution
|
|
623
|
+
debugStack: selectedSource.debugStack,
|
|
624
|
+
// Pass edit history for context
|
|
625
|
+
editHistory: editHistory.map((item) => ({
|
|
626
|
+
suggestion: item.suggestion,
|
|
627
|
+
success: item.success
|
|
628
|
+
}))
|
|
629
|
+
})
|
|
630
|
+
});
|
|
631
|
+
const result = await res.json();
|
|
632
|
+
const editId = Date.now().toString(36) + Math.random().toString(36).substring(2);
|
|
633
|
+
const newHistoryItem = {
|
|
634
|
+
id: editId,
|
|
635
|
+
suggestion,
|
|
636
|
+
success: result.success,
|
|
637
|
+
error: result.error,
|
|
638
|
+
timestamp: Date.now(),
|
|
639
|
+
fileSnapshot: result.fileSnapshot,
|
|
640
|
+
generatedCode: result.generatedCode,
|
|
641
|
+
modifiedLines: result.modifiedLines
|
|
642
|
+
};
|
|
643
|
+
setEditHistory((prev) => [...prev, newHistoryItem]);
|
|
644
|
+
if (ENABLE_SESSION_PERSISTENCE && selectedSource && result.fileSnapshot) ;
|
|
645
|
+
return result;
|
|
646
|
+
} catch (err) {
|
|
647
|
+
const error = String(err);
|
|
648
|
+
const editId = Date.now().toString(36) + Math.random().toString(36).substring(2);
|
|
649
|
+
setEditHistory((prev) => [
|
|
650
|
+
...prev,
|
|
651
|
+
{
|
|
652
|
+
id: editId,
|
|
653
|
+
suggestion,
|
|
654
|
+
success: false,
|
|
655
|
+
error,
|
|
656
|
+
timestamp: Date.now()
|
|
657
|
+
}
|
|
658
|
+
]);
|
|
659
|
+
return { success: false, error };
|
|
660
|
+
} finally {
|
|
661
|
+
setIsLoading(false);
|
|
662
|
+
}
|
|
663
|
+
},
|
|
664
|
+
[selectedSource, editHistory]
|
|
665
|
+
);
|
|
666
|
+
const handleSelect = useCallback(
|
|
667
|
+
(element, source) => {
|
|
668
|
+
setSelectedDOMElement(element);
|
|
669
|
+
setSelectedSource(source);
|
|
670
|
+
setEditHistory([]);
|
|
671
|
+
resolveSourceLocation(source).then((resolved) => {
|
|
672
|
+
if (resolved) {
|
|
673
|
+
setSelectedSource((prev) => prev === source ? resolved : prev);
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
},
|
|
677
|
+
[setSelectedDOMElement, setSelectedSource]
|
|
678
|
+
);
|
|
679
|
+
if (process.env.NODE_ENV !== "development") {
|
|
680
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
681
|
+
}
|
|
682
|
+
return /* @__PURE__ */ jsxs(
|
|
683
|
+
EditorContext.Provider,
|
|
684
|
+
{
|
|
685
|
+
value: {
|
|
686
|
+
isEnabled,
|
|
687
|
+
setEnabled,
|
|
688
|
+
selectedSource,
|
|
689
|
+
setSelectedSource,
|
|
690
|
+
selectedDOMElement,
|
|
691
|
+
isLoading,
|
|
692
|
+
setIsLoading,
|
|
693
|
+
editHistory,
|
|
694
|
+
setEditHistory,
|
|
695
|
+
submitEdit
|
|
696
|
+
},
|
|
697
|
+
children: [
|
|
698
|
+
children,
|
|
699
|
+
isEnabled && /* @__PURE__ */ jsx(
|
|
700
|
+
EditorOverlay,
|
|
701
|
+
{
|
|
702
|
+
theme,
|
|
703
|
+
onSelect: handleSelect,
|
|
704
|
+
showStaleWarning,
|
|
705
|
+
staleSession,
|
|
706
|
+
hasActiveSession,
|
|
707
|
+
setShowStaleWarning,
|
|
708
|
+
setStaleSession,
|
|
709
|
+
setHasActiveSession,
|
|
710
|
+
restoreSession,
|
|
711
|
+
handleClearSession
|
|
712
|
+
}
|
|
713
|
+
)
|
|
714
|
+
]
|
|
715
|
+
}
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
function EditorOverlay({
|
|
719
|
+
theme,
|
|
720
|
+
onSelect,
|
|
721
|
+
showStaleWarning,
|
|
722
|
+
staleSession,
|
|
723
|
+
hasActiveSession,
|
|
724
|
+
setShowStaleWarning,
|
|
725
|
+
setStaleSession,
|
|
726
|
+
setHasActiveSession,
|
|
727
|
+
restoreSession,
|
|
728
|
+
handleClearSession
|
|
729
|
+
}) {
|
|
730
|
+
var _a;
|
|
731
|
+
const {
|
|
732
|
+
selectedSource,
|
|
733
|
+
setSelectedSource,
|
|
734
|
+
submitEdit,
|
|
735
|
+
isLoading,
|
|
736
|
+
setIsLoading,
|
|
737
|
+
setEnabled,
|
|
738
|
+
selectedDOMElement,
|
|
739
|
+
editHistory,
|
|
740
|
+
setEditHistory
|
|
741
|
+
} = useAIEditor();
|
|
742
|
+
const [hoveredSource, setHoveredSource] = useState(
|
|
743
|
+
null
|
|
744
|
+
);
|
|
745
|
+
const [hoveredElement, setHoveredElement] = useState(null);
|
|
746
|
+
const [hoveredRect, setHoveredRect] = useState(null);
|
|
747
|
+
const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
|
|
748
|
+
const [isResolvingSource, setIsResolvingSource] = useState(false);
|
|
749
|
+
const [suggestion, setSuggestion] = useState("");
|
|
750
|
+
const [result, setResult] = useState(null);
|
|
751
|
+
const [code, setCode] = useState("");
|
|
752
|
+
const [codeLineStart, setCodeLineStart] = useState(1);
|
|
753
|
+
const [targetStartLine, setTargetStartLine] = useState(null);
|
|
754
|
+
const [targetEndLine, setTargetEndLine] = useState(null);
|
|
755
|
+
const [absolutePath, setAbsolutePath] = useState(null);
|
|
756
|
+
const [parsedComponentName, setParsedComponentName] = useState(
|
|
757
|
+
null
|
|
758
|
+
);
|
|
759
|
+
const isDark = theme === "dark";
|
|
760
|
+
useEffect(() => {
|
|
761
|
+
var _a2, _b, _c, _d;
|
|
762
|
+
if (selectedSource) {
|
|
763
|
+
const params = {
|
|
764
|
+
path: selectedSource.filePath,
|
|
765
|
+
line: String(selectedSource.lineNumber),
|
|
766
|
+
tagName: ((_a2 = selectedSource.elementContext) == null ? void 0 : _a2.tagName) || "",
|
|
767
|
+
nthOfType: String(((_b = selectedSource.elementContext) == null ? void 0 : _b.nthOfType) || 0),
|
|
768
|
+
textContent: ((_c = selectedSource.elementContext) == null ? void 0 : _c.textContent) || "",
|
|
769
|
+
className: ((_d = selectedSource.elementContext) == null ? void 0 : _d.className) || ""
|
|
770
|
+
};
|
|
771
|
+
if (selectedSource.debugStack) {
|
|
772
|
+
params.debugStack = selectedSource.debugStack;
|
|
773
|
+
}
|
|
774
|
+
fetch(`/api/ai-editor/read?` + new URLSearchParams(params)).then((r) => r.json()).then((d) => {
|
|
775
|
+
if (d.success) {
|
|
776
|
+
console.log("[AI Editor] Read response:", {
|
|
777
|
+
lineStart: d.lineStart,
|
|
778
|
+
targetStartLine: d.targetStartLine,
|
|
779
|
+
targetEndLine: d.targetEndLine,
|
|
780
|
+
componentName: d.componentName
|
|
781
|
+
});
|
|
782
|
+
setCode(d.content);
|
|
783
|
+
setCodeLineStart(d.lineStart || 1);
|
|
784
|
+
setTargetStartLine(d.targetStartLine || null);
|
|
785
|
+
setTargetEndLine(d.targetEndLine || null);
|
|
786
|
+
if (d.componentName) {
|
|
787
|
+
setParsedComponentName(d.componentName);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}).catch(() => {
|
|
791
|
+
setCode("// Could not load");
|
|
792
|
+
setCodeLineStart(1);
|
|
793
|
+
setTargetStartLine(null);
|
|
794
|
+
setTargetEndLine(null);
|
|
795
|
+
});
|
|
796
|
+
fetch(
|
|
797
|
+
`/api/ai-editor/absolute-path?` + new URLSearchParams({ path: selectedSource.filePath })
|
|
798
|
+
).then((r) => r.json()).then((d) => d.success && setAbsolutePath(d.absolutePath)).catch(() => setAbsolutePath(null));
|
|
799
|
+
} else {
|
|
800
|
+
setAbsolutePath(null);
|
|
801
|
+
setParsedComponentName(null);
|
|
802
|
+
}
|
|
803
|
+
}, [selectedSource]);
|
|
804
|
+
useEffect(() => {
|
|
805
|
+
let lastEl = null;
|
|
806
|
+
let raf;
|
|
807
|
+
const onMove = (e) => {
|
|
808
|
+
setMousePos({ x: e.clientX, y: e.clientY });
|
|
809
|
+
if (selectedSource) return;
|
|
810
|
+
cancelAnimationFrame(raf);
|
|
811
|
+
raf = requestAnimationFrame(() => {
|
|
812
|
+
const el = document.elementFromPoint(e.clientX, e.clientY);
|
|
813
|
+
if (!el || el.closest(".ai-editor-ui") || el === lastEl) return;
|
|
814
|
+
lastEl = el;
|
|
815
|
+
const source = getSourceFromElement(el);
|
|
816
|
+
if (source) {
|
|
817
|
+
setHoveredElement(el);
|
|
818
|
+
setHoveredRect(el.getBoundingClientRect());
|
|
819
|
+
const isCompiledPath = source.filePath.includes(".next/") || source.filePath.includes("/chunks/") || source.filePath.startsWith("file://") || source.filePath.endsWith(".js") && (source.filePath.includes("/server/") || source.filePath.includes("/static/"));
|
|
820
|
+
if (isCompiledPath && source.debugStack) {
|
|
821
|
+
setIsResolvingSource(true);
|
|
822
|
+
resolveSourceLocation(source).then((resolved) => {
|
|
823
|
+
setIsResolvingSource(false);
|
|
824
|
+
if (resolved) {
|
|
825
|
+
console.log("Resolved source location:", resolved);
|
|
826
|
+
setHoveredSource(resolved);
|
|
827
|
+
} else {
|
|
828
|
+
console.warn(
|
|
829
|
+
"Failed to resolve source location for:",
|
|
830
|
+
source
|
|
831
|
+
);
|
|
832
|
+
setHoveredSource(source);
|
|
833
|
+
}
|
|
834
|
+
}).catch((err) => {
|
|
835
|
+
console.error("Error resolving source location:", err);
|
|
836
|
+
setIsResolvingSource(false);
|
|
837
|
+
setHoveredSource(source);
|
|
838
|
+
});
|
|
839
|
+
} else if (isCompiledPath && !source.debugStack) {
|
|
840
|
+
console.warn("Compiled path but no debugStack:", source);
|
|
841
|
+
setIsResolvingSource(false);
|
|
842
|
+
setHoveredSource(source);
|
|
843
|
+
} else {
|
|
844
|
+
setIsResolvingSource(false);
|
|
845
|
+
setHoveredSource(source);
|
|
846
|
+
}
|
|
847
|
+
} else {
|
|
848
|
+
setHoveredSource(null);
|
|
849
|
+
setHoveredElement(null);
|
|
850
|
+
setHoveredRect(null);
|
|
851
|
+
setIsResolvingSource(false);
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
};
|
|
855
|
+
const onClick = (e) => {
|
|
856
|
+
if (e.target.closest(".ai-editor-ui")) return;
|
|
857
|
+
if (hoveredSource && hoveredElement) {
|
|
858
|
+
e.preventDefault();
|
|
859
|
+
e.stopPropagation();
|
|
860
|
+
onSelect(hoveredElement, hoveredSource);
|
|
861
|
+
setSuggestion("");
|
|
862
|
+
setResult(null);
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
document.addEventListener("mousemove", onMove, { passive: true });
|
|
866
|
+
document.addEventListener("click", onClick, true);
|
|
867
|
+
return () => {
|
|
868
|
+
document.removeEventListener("mousemove", onMove);
|
|
869
|
+
document.removeEventListener("click", onClick, true);
|
|
870
|
+
cancelAnimationFrame(raf);
|
|
871
|
+
};
|
|
872
|
+
}, [hoveredSource, hoveredElement, selectedSource, onSelect]);
|
|
873
|
+
const handleSubmit = async () => {
|
|
874
|
+
var _a2, _b, _c, _d;
|
|
875
|
+
if (!suggestion.trim()) return;
|
|
876
|
+
const res = await submitEdit(suggestion);
|
|
877
|
+
setResult(res);
|
|
878
|
+
if (res.success) {
|
|
879
|
+
setSuggestion("");
|
|
880
|
+
if (selectedSource) {
|
|
881
|
+
const params = {
|
|
882
|
+
path: selectedSource.filePath,
|
|
883
|
+
line: String(selectedSource.lineNumber),
|
|
884
|
+
tagName: ((_a2 = selectedSource.elementContext) == null ? void 0 : _a2.tagName) || "",
|
|
885
|
+
nthOfType: String(((_b = selectedSource.elementContext) == null ? void 0 : _b.nthOfType) || 0),
|
|
886
|
+
textContent: ((_c = selectedSource.elementContext) == null ? void 0 : _c.textContent) || "",
|
|
887
|
+
className: ((_d = selectedSource.elementContext) == null ? void 0 : _d.className) || ""
|
|
888
|
+
};
|
|
889
|
+
if (selectedSource.debugStack) {
|
|
890
|
+
params.debugStack = selectedSource.debugStack;
|
|
891
|
+
}
|
|
892
|
+
fetch(`/api/ai-editor/read?` + new URLSearchParams(params)).then((r) => r.json()).then((d) => {
|
|
893
|
+
if (d.success) {
|
|
894
|
+
setCode(d.content);
|
|
895
|
+
setCodeLineStart(d.lineStart || 1);
|
|
896
|
+
setTargetStartLine(d.targetStartLine || null);
|
|
897
|
+
setTargetEndLine(d.targetEndLine || null);
|
|
898
|
+
if (d.componentName) {
|
|
899
|
+
setParsedComponentName(d.componentName);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}).catch(console.error);
|
|
903
|
+
}
|
|
904
|
+
setTimeout(() => setResult(null), 3e3);
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
const handleUndo = async () => {
|
|
908
|
+
var _a2, _b, _c, _d;
|
|
909
|
+
if (editHistory.length === 0 || !selectedSource) return;
|
|
910
|
+
const lastSuccessfulEdit = [...editHistory].reverse().find((edit) => edit.success && edit.fileSnapshot);
|
|
911
|
+
if (!lastSuccessfulEdit) {
|
|
912
|
+
console.warn("No successful edit with snapshot found to undo");
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
setIsLoading(true);
|
|
916
|
+
try {
|
|
917
|
+
const response = await fetch("/api/ai-editor/undo", {
|
|
918
|
+
method: "POST",
|
|
919
|
+
headers: { "Content-Type": "application/json" },
|
|
920
|
+
body: JSON.stringify({
|
|
921
|
+
filePath: selectedSource.filePath,
|
|
922
|
+
content: lastSuccessfulEdit.fileSnapshot
|
|
923
|
+
})
|
|
924
|
+
});
|
|
925
|
+
const result2 = await response.json();
|
|
926
|
+
if (!result2.success) {
|
|
927
|
+
throw new Error(result2.error || "Undo failed");
|
|
928
|
+
}
|
|
929
|
+
setEditHistory((prev) => {
|
|
930
|
+
const lastIndex = prev.lastIndexOf(lastSuccessfulEdit);
|
|
931
|
+
return prev.filter((_, idx) => idx !== lastIndex);
|
|
932
|
+
});
|
|
933
|
+
const params = {
|
|
934
|
+
path: selectedSource.filePath,
|
|
935
|
+
line: String(selectedSource.lineNumber),
|
|
936
|
+
tagName: ((_a2 = selectedSource.elementContext) == null ? void 0 : _a2.tagName) || "",
|
|
937
|
+
nthOfType: String(((_b = selectedSource.elementContext) == null ? void 0 : _b.nthOfType) || 0),
|
|
938
|
+
textContent: ((_c = selectedSource.elementContext) == null ? void 0 : _c.textContent) || "",
|
|
939
|
+
className: ((_d = selectedSource.elementContext) == null ? void 0 : _d.className) || ""
|
|
940
|
+
};
|
|
941
|
+
if (selectedSource.debugStack) {
|
|
942
|
+
params.debugStack = selectedSource.debugStack;
|
|
943
|
+
}
|
|
944
|
+
fetch(`/api/ai-editor/read?` + new URLSearchParams(params)).then((r) => r.json()).then((d) => {
|
|
945
|
+
if (d.success) {
|
|
946
|
+
setCode(d.content);
|
|
947
|
+
setCodeLineStart(d.lineStart || 1);
|
|
948
|
+
setTargetStartLine(d.targetStartLine || null);
|
|
949
|
+
setTargetEndLine(d.targetEndLine || null);
|
|
950
|
+
if (d.componentName) {
|
|
951
|
+
setParsedComponentName(d.componentName);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}).catch(console.error);
|
|
955
|
+
setResult({ success: true, error: void 0 });
|
|
956
|
+
setTimeout(() => setResult(null), 3e3);
|
|
957
|
+
} catch (error) {
|
|
958
|
+
console.error("Undo error:", error);
|
|
959
|
+
setResult({ success: false, error: String(error) });
|
|
960
|
+
} finally {
|
|
961
|
+
setIsLoading(false);
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
const handleRetry = async (editIndex) => {
|
|
965
|
+
var _a2, _b, _c, _d;
|
|
966
|
+
const editToRetry = editHistory[editIndex];
|
|
967
|
+
if (!editToRetry) return;
|
|
968
|
+
const res = await submitEdit(editToRetry.suggestion);
|
|
969
|
+
if (res.success && selectedSource) {
|
|
970
|
+
const params = {
|
|
971
|
+
path: selectedSource.filePath,
|
|
972
|
+
line: String(selectedSource.lineNumber),
|
|
973
|
+
tagName: ((_a2 = selectedSource.elementContext) == null ? void 0 : _a2.tagName) || "",
|
|
974
|
+
nthOfType: String(((_b = selectedSource.elementContext) == null ? void 0 : _b.nthOfType) || 0),
|
|
975
|
+
textContent: ((_c = selectedSource.elementContext) == null ? void 0 : _c.textContent) || "",
|
|
976
|
+
className: ((_d = selectedSource.elementContext) == null ? void 0 : _d.className) || ""
|
|
977
|
+
};
|
|
978
|
+
if (selectedSource.debugStack) {
|
|
979
|
+
params.debugStack = selectedSource.debugStack;
|
|
980
|
+
}
|
|
981
|
+
fetch(`/api/ai-editor/read?` + new URLSearchParams(params)).then((r) => r.json()).then((d) => {
|
|
982
|
+
if (d.success) {
|
|
983
|
+
setCode(d.content);
|
|
984
|
+
setCodeLineStart(d.lineStart || 1);
|
|
985
|
+
setTargetStartLine(d.targetStartLine || null);
|
|
986
|
+
setTargetEndLine(d.targetEndLine || null);
|
|
987
|
+
if (d.componentName) {
|
|
988
|
+
setParsedComponentName(d.componentName);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}).catch(console.error);
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
const handleUndoAndRetry = async () => {
|
|
995
|
+
var _a2, _b, _c, _d;
|
|
996
|
+
if (editHistory.length === 0 || !selectedSource) return;
|
|
997
|
+
const lastSuccessfulEdit = [...editHistory].reverse().find((edit) => edit.success && edit.fileSnapshot);
|
|
998
|
+
if (!lastSuccessfulEdit) {
|
|
999
|
+
console.warn("No successful edit with snapshot found to undo and retry");
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
setIsLoading(true);
|
|
1003
|
+
try {
|
|
1004
|
+
const undoResponse = await fetch("/api/ai-editor/undo", {
|
|
1005
|
+
method: "POST",
|
|
1006
|
+
headers: { "Content-Type": "application/json" },
|
|
1007
|
+
body: JSON.stringify({
|
|
1008
|
+
filePath: selectedSource.filePath,
|
|
1009
|
+
content: lastSuccessfulEdit.fileSnapshot
|
|
1010
|
+
})
|
|
1011
|
+
});
|
|
1012
|
+
const undoResult = await undoResponse.json();
|
|
1013
|
+
if (!undoResult.success) {
|
|
1014
|
+
throw new Error(undoResult.error || "Undo failed");
|
|
1015
|
+
}
|
|
1016
|
+
setEditHistory((prev) => {
|
|
1017
|
+
const lastIndex = prev.lastIndexOf(lastSuccessfulEdit);
|
|
1018
|
+
return prev.filter((_, idx) => idx !== lastIndex);
|
|
1019
|
+
});
|
|
1020
|
+
const params = {
|
|
1021
|
+
path: selectedSource.filePath,
|
|
1022
|
+
line: String(selectedSource.lineNumber),
|
|
1023
|
+
tagName: ((_a2 = selectedSource.elementContext) == null ? void 0 : _a2.tagName) || "",
|
|
1024
|
+
nthOfType: String(((_b = selectedSource.elementContext) == null ? void 0 : _b.nthOfType) || 0),
|
|
1025
|
+
textContent: ((_c = selectedSource.elementContext) == null ? void 0 : _c.textContent) || "",
|
|
1026
|
+
className: ((_d = selectedSource.elementContext) == null ? void 0 : _d.className) || ""
|
|
1027
|
+
};
|
|
1028
|
+
if (selectedSource.debugStack) {
|
|
1029
|
+
params.debugStack = selectedSource.debugStack;
|
|
1030
|
+
}
|
|
1031
|
+
await fetch(`/api/ai-editor/read?` + new URLSearchParams(params)).then((r) => r.json()).then((d) => {
|
|
1032
|
+
if (d.success) {
|
|
1033
|
+
setCode(d.content);
|
|
1034
|
+
setCodeLineStart(d.lineStart || 1);
|
|
1035
|
+
setTargetStartLine(d.targetStartLine || null);
|
|
1036
|
+
setTargetEndLine(d.targetEndLine || null);
|
|
1037
|
+
if (d.componentName) {
|
|
1038
|
+
setParsedComponentName(d.componentName);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}).catch(console.error);
|
|
1042
|
+
setIsLoading(true);
|
|
1043
|
+
const retryResult = await submitEdit(lastSuccessfulEdit.suggestion);
|
|
1044
|
+
if (retryResult.success && selectedSource) {
|
|
1045
|
+
await fetch(`/api/ai-editor/read?` + new URLSearchParams(params)).then((r) => r.json()).then((d) => {
|
|
1046
|
+
if (d.success) {
|
|
1047
|
+
setCode(d.content);
|
|
1048
|
+
setCodeLineStart(d.lineStart || 1);
|
|
1049
|
+
setTargetStartLine(d.targetStartLine || null);
|
|
1050
|
+
setTargetEndLine(d.targetEndLine || null);
|
|
1051
|
+
if (d.componentName) {
|
|
1052
|
+
setParsedComponentName(d.componentName);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
}).catch(console.error);
|
|
1056
|
+
}
|
|
1057
|
+
if (retryResult.success) {
|
|
1058
|
+
setResult({ success: true, error: void 0 });
|
|
1059
|
+
} else {
|
|
1060
|
+
setResult({ success: false, error: retryResult.error });
|
|
1061
|
+
}
|
|
1062
|
+
setTimeout(() => setResult(null), 3e3);
|
|
1063
|
+
} catch (error) {
|
|
1064
|
+
console.error("Undo and retry error:", error);
|
|
1065
|
+
setResult({ success: false, error: String(error) });
|
|
1066
|
+
} finally {
|
|
1067
|
+
setIsLoading(false);
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
const handleDone = () => {
|
|
1071
|
+
window.location.reload();
|
|
1072
|
+
};
|
|
1073
|
+
const c = {
|
|
1074
|
+
bg: isDark ? "#0d0d14" : "#fff",
|
|
1075
|
+
bgAlt: isDark ? "#16162a" : "#f5f5f5",
|
|
1076
|
+
text: isDark ? "#e4e4e7" : "#18181b",
|
|
1077
|
+
muted: isDark ? "#71717a" : "#a1a1aa",
|
|
1078
|
+
accent: "#818cf8",
|
|
1079
|
+
border: isDark ? "#27273f" : "#e4e4e7",
|
|
1080
|
+
success: "#34d399",
|
|
1081
|
+
error: "#f87171"
|
|
1082
|
+
};
|
|
1083
|
+
const elCtx = selectedSource == null ? void 0 : selectedSource.elementContext;
|
|
1084
|
+
return /* @__PURE__ */ jsxs(
|
|
1085
|
+
"div",
|
|
1086
|
+
{
|
|
1087
|
+
className: "ai-editor-ui",
|
|
1088
|
+
style: { fontFamily: "system-ui, sans-serif" },
|
|
1089
|
+
children: [
|
|
1090
|
+
hoveredRect && !selectedSource && /* @__PURE__ */ jsx(
|
|
1091
|
+
"div",
|
|
1092
|
+
{
|
|
1093
|
+
style: {
|
|
1094
|
+
position: "fixed",
|
|
1095
|
+
left: hoveredRect.left - 2,
|
|
1096
|
+
top: hoveredRect.top - 2,
|
|
1097
|
+
width: hoveredRect.width + 4,
|
|
1098
|
+
height: hoveredRect.height + 4,
|
|
1099
|
+
border: `2px solid ${c.accent}`,
|
|
1100
|
+
borderRadius: 6,
|
|
1101
|
+
background: `${c.accent}15`,
|
|
1102
|
+
pointerEvents: "none",
|
|
1103
|
+
zIndex: 99998
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
),
|
|
1107
|
+
hoveredSource && !selectedSource && !isResolvingSource && /* @__PURE__ */ jsxs(
|
|
1108
|
+
"div",
|
|
1109
|
+
{
|
|
1110
|
+
style: {
|
|
1111
|
+
position: "fixed",
|
|
1112
|
+
left: Math.min(mousePos.x + 14, window.innerWidth - 340),
|
|
1113
|
+
top: mousePos.y + 14,
|
|
1114
|
+
background: c.bg,
|
|
1115
|
+
color: c.text,
|
|
1116
|
+
border: `1px solid ${c.border}`,
|
|
1117
|
+
borderRadius: 10,
|
|
1118
|
+
padding: "12px 16px",
|
|
1119
|
+
fontSize: 12,
|
|
1120
|
+
fontFamily: "ui-monospace, monospace",
|
|
1121
|
+
zIndex: 99999,
|
|
1122
|
+
boxShadow: `0 8px 30px rgba(0,0,0,${isDark ? 0.5 : 0.15})`,
|
|
1123
|
+
maxWidth: 320,
|
|
1124
|
+
pointerEvents: "none"
|
|
1125
|
+
},
|
|
1126
|
+
children: [
|
|
1127
|
+
/* @__PURE__ */ jsxs(
|
|
1128
|
+
"div",
|
|
1129
|
+
{
|
|
1130
|
+
style: {
|
|
1131
|
+
fontWeight: 700,
|
|
1132
|
+
color: c.accent,
|
|
1133
|
+
marginBottom: 4,
|
|
1134
|
+
fontSize: 14
|
|
1135
|
+
},
|
|
1136
|
+
children: [
|
|
1137
|
+
"<",
|
|
1138
|
+
hoveredSource.componentName,
|
|
1139
|
+
" />"
|
|
1140
|
+
]
|
|
1141
|
+
}
|
|
1142
|
+
),
|
|
1143
|
+
/* @__PURE__ */ jsxs("div", { style: { color: c.muted, fontSize: 11 }, children: [
|
|
1144
|
+
hoveredSource.filePath,
|
|
1145
|
+
":",
|
|
1146
|
+
hoveredSource.lineNumber
|
|
1147
|
+
] }),
|
|
1148
|
+
((_a = hoveredSource.elementContext) == null ? void 0 : _a.textContent) && /* @__PURE__ */ jsxs(
|
|
1149
|
+
"div",
|
|
1150
|
+
{
|
|
1151
|
+
style: {
|
|
1152
|
+
color: c.muted,
|
|
1153
|
+
fontSize: 10,
|
|
1154
|
+
marginTop: 4,
|
|
1155
|
+
fontStyle: "italic"
|
|
1156
|
+
},
|
|
1157
|
+
children: [
|
|
1158
|
+
'"',
|
|
1159
|
+
hoveredSource.elementContext.textContent,
|
|
1160
|
+
'"'
|
|
1161
|
+
]
|
|
1162
|
+
}
|
|
1163
|
+
)
|
|
1164
|
+
]
|
|
1165
|
+
}
|
|
1166
|
+
),
|
|
1167
|
+
ENABLE_SESSION_PERSISTENCE,
|
|
1168
|
+
selectedSource && /* @__PURE__ */ jsxs(
|
|
1169
|
+
"div",
|
|
1170
|
+
{
|
|
1171
|
+
style: {
|
|
1172
|
+
position: "fixed",
|
|
1173
|
+
bottom: 20,
|
|
1174
|
+
right: 20,
|
|
1175
|
+
background: c.bg,
|
|
1176
|
+
color: c.text,
|
|
1177
|
+
borderRadius: 16,
|
|
1178
|
+
padding: 24,
|
|
1179
|
+
width: 560,
|
|
1180
|
+
maxWidth: "calc(100vw - 40px)",
|
|
1181
|
+
maxHeight: "calc(100vh - 40px)",
|
|
1182
|
+
overflowY: "auto",
|
|
1183
|
+
overflowX: "hidden",
|
|
1184
|
+
zIndex: 1e5,
|
|
1185
|
+
boxShadow: `0 24px 80px rgba(0,0,0,${isDark ? 0.6 : 0.25})`,
|
|
1186
|
+
border: `1px solid ${c.border}`
|
|
1187
|
+
},
|
|
1188
|
+
children: [
|
|
1189
|
+
/* @__PURE__ */ jsxs(
|
|
1190
|
+
"div",
|
|
1191
|
+
{
|
|
1192
|
+
style: {
|
|
1193
|
+
display: "flex",
|
|
1194
|
+
justifyContent: "space-between",
|
|
1195
|
+
marginBottom: 20
|
|
1196
|
+
},
|
|
1197
|
+
children: [
|
|
1198
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1199
|
+
/* @__PURE__ */ jsx(
|
|
1200
|
+
"div",
|
|
1201
|
+
{
|
|
1202
|
+
style: {
|
|
1203
|
+
fontSize: 11,
|
|
1204
|
+
textTransform: "uppercase",
|
|
1205
|
+
letterSpacing: 1,
|
|
1206
|
+
color: c.muted
|
|
1207
|
+
},
|
|
1208
|
+
children: "Editing"
|
|
1209
|
+
}
|
|
1210
|
+
),
|
|
1211
|
+
/* @__PURE__ */ jsxs("div", { style: { fontSize: 20, fontWeight: 700, color: c.accent }, children: [
|
|
1212
|
+
"<",
|
|
1213
|
+
parsedComponentName || selectedSource.componentName,
|
|
1214
|
+
" />"
|
|
1215
|
+
] }),
|
|
1216
|
+
/* @__PURE__ */ jsx(
|
|
1217
|
+
"div",
|
|
1218
|
+
{
|
|
1219
|
+
style: {
|
|
1220
|
+
fontSize: 12,
|
|
1221
|
+
color: c.muted,
|
|
1222
|
+
fontFamily: "monospace",
|
|
1223
|
+
marginTop: 4
|
|
1224
|
+
},
|
|
1225
|
+
children: /* @__PURE__ */ jsxs(
|
|
1226
|
+
"a",
|
|
1227
|
+
{
|
|
1228
|
+
href: absolutePath ? `cursor://file/${absolutePath}:${selectedSource.lineNumber}` : `cursor://file/${selectedSource.filePath}:${selectedSource.lineNumber}`,
|
|
1229
|
+
onClick: (e) => {
|
|
1230
|
+
e.preventDefault();
|
|
1231
|
+
e.stopPropagation();
|
|
1232
|
+
const path = absolutePath || selectedSource.filePath;
|
|
1233
|
+
const cursorPath = path.startsWith("/") ? path : `/${path}`;
|
|
1234
|
+
window.location.href = `cursor://file${cursorPath}:${selectedSource.lineNumber}`;
|
|
1235
|
+
},
|
|
1236
|
+
style: {
|
|
1237
|
+
color: c.accent,
|
|
1238
|
+
textDecoration: "none",
|
|
1239
|
+
cursor: "pointer",
|
|
1240
|
+
borderBottom: `1px solid ${c.accent}40`
|
|
1241
|
+
},
|
|
1242
|
+
onMouseEnter: (e) => {
|
|
1243
|
+
e.currentTarget.style.borderBottomColor = c.accent;
|
|
1244
|
+
},
|
|
1245
|
+
onMouseLeave: (e) => {
|
|
1246
|
+
e.currentTarget.style.borderBottomColor = `${c.accent}40`;
|
|
1247
|
+
},
|
|
1248
|
+
children: [
|
|
1249
|
+
selectedSource.filePath,
|
|
1250
|
+
":",
|
|
1251
|
+
selectedSource.lineNumber
|
|
1252
|
+
]
|
|
1253
|
+
}
|
|
1254
|
+
)
|
|
1255
|
+
}
|
|
1256
|
+
),
|
|
1257
|
+
(elCtx == null ? void 0 : elCtx.textContent) && /* @__PURE__ */ jsxs(
|
|
1258
|
+
"div",
|
|
1259
|
+
{
|
|
1260
|
+
style: {
|
|
1261
|
+
fontSize: 11,
|
|
1262
|
+
color: c.muted,
|
|
1263
|
+
marginTop: 4,
|
|
1264
|
+
fontStyle: "italic"
|
|
1265
|
+
},
|
|
1266
|
+
children: [
|
|
1267
|
+
'Element text: "',
|
|
1268
|
+
elCtx.textContent,
|
|
1269
|
+
'"'
|
|
1270
|
+
]
|
|
1271
|
+
}
|
|
1272
|
+
)
|
|
1273
|
+
] }),
|
|
1274
|
+
/* @__PURE__ */ jsx(
|
|
1275
|
+
"button",
|
|
1276
|
+
{
|
|
1277
|
+
onClick: () => setSelectedSource(null),
|
|
1278
|
+
style: {
|
|
1279
|
+
width: 36,
|
|
1280
|
+
height: 36,
|
|
1281
|
+
borderRadius: 10,
|
|
1282
|
+
border: "none",
|
|
1283
|
+
background: c.bgAlt,
|
|
1284
|
+
color: c.muted,
|
|
1285
|
+
fontSize: 20,
|
|
1286
|
+
cursor: "pointer"
|
|
1287
|
+
},
|
|
1288
|
+
children: "×"
|
|
1289
|
+
}
|
|
1290
|
+
)
|
|
1291
|
+
]
|
|
1292
|
+
}
|
|
1293
|
+
),
|
|
1294
|
+
code && /* @__PURE__ */ jsxs("div", { style: { marginBottom: 20 }, children: [
|
|
1295
|
+
/* @__PURE__ */ jsx(
|
|
1296
|
+
"div",
|
|
1297
|
+
{
|
|
1298
|
+
style: {
|
|
1299
|
+
fontSize: 11,
|
|
1300
|
+
fontWeight: 600,
|
|
1301
|
+
marginBottom: 8,
|
|
1302
|
+
color: c.muted,
|
|
1303
|
+
textTransform: "uppercase"
|
|
1304
|
+
},
|
|
1305
|
+
children: "Source Preview"
|
|
1306
|
+
}
|
|
1307
|
+
),
|
|
1308
|
+
/* @__PURE__ */ jsx(
|
|
1309
|
+
Prism,
|
|
1310
|
+
{
|
|
1311
|
+
language: "tsx",
|
|
1312
|
+
style: isDark ? vscDarkPlus : vs,
|
|
1313
|
+
showLineNumbers: true,
|
|
1314
|
+
startingLineNumber: codeLineStart,
|
|
1315
|
+
customStyle: {
|
|
1316
|
+
background: c.bgAlt,
|
|
1317
|
+
borderRadius: 10,
|
|
1318
|
+
fontSize: 14,
|
|
1319
|
+
margin: 0,
|
|
1320
|
+
maxHeight: 180,
|
|
1321
|
+
overflowX: "auto",
|
|
1322
|
+
border: `1px solid ${c.border}`
|
|
1323
|
+
},
|
|
1324
|
+
lineNumberStyle: {
|
|
1325
|
+
minWidth: "2.5em",
|
|
1326
|
+
paddingRight: "1em",
|
|
1327
|
+
color: c.muted,
|
|
1328
|
+
textAlign: "right",
|
|
1329
|
+
userSelect: "none"
|
|
1330
|
+
},
|
|
1331
|
+
wrapLines: true,
|
|
1332
|
+
lineProps: (lineNumber) => {
|
|
1333
|
+
const isHighlighted = targetStartLine !== null && targetEndLine !== null && lineNumber >= targetStartLine && lineNumber <= targetEndLine;
|
|
1334
|
+
return {
|
|
1335
|
+
style: {
|
|
1336
|
+
backgroundColor: isHighlighted ? isDark ? "rgba(102, 126, 234, 0.15)" : "rgba(147, 197, 253, 0.3)" : "transparent"
|
|
1337
|
+
}
|
|
1338
|
+
};
|
|
1339
|
+
},
|
|
1340
|
+
children: code
|
|
1341
|
+
}
|
|
1342
|
+
)
|
|
1343
|
+
] }),
|
|
1344
|
+
editHistory.length > 0 && /* @__PURE__ */ jsxs("div", { style: { marginBottom: 20 }, children: [
|
|
1345
|
+
/* @__PURE__ */ jsxs(
|
|
1346
|
+
"div",
|
|
1347
|
+
{
|
|
1348
|
+
style: {
|
|
1349
|
+
fontSize: 11,
|
|
1350
|
+
fontWeight: 600,
|
|
1351
|
+
marginBottom: 8,
|
|
1352
|
+
color: c.muted,
|
|
1353
|
+
textTransform: "uppercase"
|
|
1354
|
+
},
|
|
1355
|
+
children: [
|
|
1356
|
+
"Edit History (",
|
|
1357
|
+
editHistory.length,
|
|
1358
|
+
")"
|
|
1359
|
+
]
|
|
1360
|
+
}
|
|
1361
|
+
),
|
|
1362
|
+
/* @__PURE__ */ jsx(
|
|
1363
|
+
"div",
|
|
1364
|
+
{
|
|
1365
|
+
style: {
|
|
1366
|
+
background: c.bgAlt,
|
|
1367
|
+
borderRadius: 10,
|
|
1368
|
+
border: `1px solid ${c.border}`,
|
|
1369
|
+
maxHeight: 200,
|
|
1370
|
+
overflow: "auto"
|
|
1371
|
+
},
|
|
1372
|
+
children: editHistory.map((item, idx) => {
|
|
1373
|
+
const isLastEdit = idx === editHistory.length - 1;
|
|
1374
|
+
const hasSnapshot = item.success && item.fileSnapshot;
|
|
1375
|
+
return /* @__PURE__ */ jsx(
|
|
1376
|
+
"div",
|
|
1377
|
+
{
|
|
1378
|
+
style: {
|
|
1379
|
+
padding: "10px 14px",
|
|
1380
|
+
borderBottom: idx < editHistory.length - 1 ? `1px solid ${c.border}` : "none"
|
|
1381
|
+
},
|
|
1382
|
+
children: /* @__PURE__ */ jsxs(
|
|
1383
|
+
"div",
|
|
1384
|
+
{
|
|
1385
|
+
style: {
|
|
1386
|
+
display: "flex",
|
|
1387
|
+
alignItems: "flex-start",
|
|
1388
|
+
gap: 8
|
|
1389
|
+
},
|
|
1390
|
+
children: [
|
|
1391
|
+
/* @__PURE__ */ jsxs("div", { style: { flex: 1 }, children: [
|
|
1392
|
+
/* @__PURE__ */ jsxs(
|
|
1393
|
+
"div",
|
|
1394
|
+
{
|
|
1395
|
+
style: {
|
|
1396
|
+
display: "flex",
|
|
1397
|
+
alignItems: "center",
|
|
1398
|
+
gap: 8,
|
|
1399
|
+
marginBottom: 4
|
|
1400
|
+
},
|
|
1401
|
+
children: [
|
|
1402
|
+
/* @__PURE__ */ jsx(
|
|
1403
|
+
"span",
|
|
1404
|
+
{
|
|
1405
|
+
style: {
|
|
1406
|
+
color: item.success ? c.success : c.error,
|
|
1407
|
+
fontSize: 16
|
|
1408
|
+
},
|
|
1409
|
+
children: item.success ? "✓" : "✗"
|
|
1410
|
+
}
|
|
1411
|
+
),
|
|
1412
|
+
/* @__PURE__ */ jsxs(
|
|
1413
|
+
"span",
|
|
1414
|
+
{
|
|
1415
|
+
style: {
|
|
1416
|
+
fontSize: 11,
|
|
1417
|
+
color: c.muted
|
|
1418
|
+
},
|
|
1419
|
+
children: [
|
|
1420
|
+
"Edit ",
|
|
1421
|
+
idx + 1
|
|
1422
|
+
]
|
|
1423
|
+
}
|
|
1424
|
+
)
|
|
1425
|
+
]
|
|
1426
|
+
}
|
|
1427
|
+
),
|
|
1428
|
+
/* @__PURE__ */ jsx(
|
|
1429
|
+
"div",
|
|
1430
|
+
{
|
|
1431
|
+
style: {
|
|
1432
|
+
fontSize: 13,
|
|
1433
|
+
color: c.text,
|
|
1434
|
+
marginLeft: 24
|
|
1435
|
+
},
|
|
1436
|
+
children: item.suggestion
|
|
1437
|
+
}
|
|
1438
|
+
),
|
|
1439
|
+
item.error && /* @__PURE__ */ jsx(
|
|
1440
|
+
"div",
|
|
1441
|
+
{
|
|
1442
|
+
style: {
|
|
1443
|
+
fontSize: 11,
|
|
1444
|
+
color: c.error,
|
|
1445
|
+
marginLeft: 24,
|
|
1446
|
+
marginTop: 4
|
|
1447
|
+
},
|
|
1448
|
+
children: item.error
|
|
1449
|
+
}
|
|
1450
|
+
)
|
|
1451
|
+
] }),
|
|
1452
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: [
|
|
1453
|
+
isLastEdit && hasSnapshot && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1454
|
+
/* @__PURE__ */ jsx(
|
|
1455
|
+
"button",
|
|
1456
|
+
{
|
|
1457
|
+
onClick: handleUndo,
|
|
1458
|
+
disabled: isLoading,
|
|
1459
|
+
style: {
|
|
1460
|
+
padding: "4px 10px",
|
|
1461
|
+
fontSize: 11,
|
|
1462
|
+
borderRadius: 6,
|
|
1463
|
+
border: "none",
|
|
1464
|
+
background: isLoading ? c.muted : "#f59e0b",
|
|
1465
|
+
color: "#fff",
|
|
1466
|
+
fontWeight: 600,
|
|
1467
|
+
cursor: isLoading ? "wait" : "pointer",
|
|
1468
|
+
whiteSpace: "nowrap"
|
|
1469
|
+
},
|
|
1470
|
+
title: "Undo this edit",
|
|
1471
|
+
onMouseEnter: (e) => {
|
|
1472
|
+
if (!isLoading)
|
|
1473
|
+
e.currentTarget.style.background = "#d97706";
|
|
1474
|
+
},
|
|
1475
|
+
onMouseLeave: (e) => {
|
|
1476
|
+
if (!isLoading)
|
|
1477
|
+
e.currentTarget.style.background = "#f59e0b";
|
|
1478
|
+
},
|
|
1479
|
+
children: "↶ Undo"
|
|
1480
|
+
}
|
|
1481
|
+
),
|
|
1482
|
+
/* @__PURE__ */ jsx(
|
|
1483
|
+
"button",
|
|
1484
|
+
{
|
|
1485
|
+
onClick: handleUndoAndRetry,
|
|
1486
|
+
disabled: isLoading,
|
|
1487
|
+
style: {
|
|
1488
|
+
padding: "4px 10px",
|
|
1489
|
+
fontSize: 11,
|
|
1490
|
+
borderRadius: 6,
|
|
1491
|
+
border: "none",
|
|
1492
|
+
background: isLoading ? c.muted : "#8b5cf6",
|
|
1493
|
+
color: "#fff",
|
|
1494
|
+
fontWeight: 600,
|
|
1495
|
+
cursor: isLoading ? "wait" : "pointer",
|
|
1496
|
+
whiteSpace: "nowrap"
|
|
1497
|
+
},
|
|
1498
|
+
title: "Undo and retry with AI",
|
|
1499
|
+
onMouseEnter: (e) => {
|
|
1500
|
+
if (!isLoading)
|
|
1501
|
+
e.currentTarget.style.background = "#7c3aed";
|
|
1502
|
+
},
|
|
1503
|
+
onMouseLeave: (e) => {
|
|
1504
|
+
if (!isLoading)
|
|
1505
|
+
e.currentTarget.style.background = "#8b5cf6";
|
|
1506
|
+
},
|
|
1507
|
+
children: "↶↻ Undo & Retry"
|
|
1508
|
+
}
|
|
1509
|
+
)
|
|
1510
|
+
] }),
|
|
1511
|
+
/* @__PURE__ */ jsx(
|
|
1512
|
+
"button",
|
|
1513
|
+
{
|
|
1514
|
+
onClick: () => handleRetry(idx),
|
|
1515
|
+
disabled: isLoading,
|
|
1516
|
+
style: {
|
|
1517
|
+
padding: "4px 10px",
|
|
1518
|
+
fontSize: 11,
|
|
1519
|
+
borderRadius: 6,
|
|
1520
|
+
border: "none",
|
|
1521
|
+
background: isLoading ? c.muted : c.accent,
|
|
1522
|
+
color: "#fff",
|
|
1523
|
+
fontWeight: 600,
|
|
1524
|
+
cursor: isLoading ? "wait" : "pointer",
|
|
1525
|
+
whiteSpace: "nowrap"
|
|
1526
|
+
},
|
|
1527
|
+
title: "Retry this edit",
|
|
1528
|
+
onMouseEnter: (e) => {
|
|
1529
|
+
if (!isLoading)
|
|
1530
|
+
e.currentTarget.style.background = "#6366f1";
|
|
1531
|
+
},
|
|
1532
|
+
onMouseLeave: (e) => {
|
|
1533
|
+
if (!isLoading)
|
|
1534
|
+
e.currentTarget.style.background = c.accent;
|
|
1535
|
+
},
|
|
1536
|
+
children: "↻ Retry"
|
|
1537
|
+
}
|
|
1538
|
+
)
|
|
1539
|
+
] })
|
|
1540
|
+
]
|
|
1541
|
+
}
|
|
1542
|
+
)
|
|
1543
|
+
},
|
|
1544
|
+
idx
|
|
1545
|
+
);
|
|
1546
|
+
})
|
|
1547
|
+
}
|
|
1548
|
+
)
|
|
1549
|
+
] }),
|
|
1550
|
+
/* @__PURE__ */ jsxs("div", { style: { marginBottom: 20 }, children: [
|
|
1551
|
+
/* @__PURE__ */ jsx(
|
|
1552
|
+
"div",
|
|
1553
|
+
{
|
|
1554
|
+
style: {
|
|
1555
|
+
fontSize: 11,
|
|
1556
|
+
fontWeight: 600,
|
|
1557
|
+
marginBottom: 8,
|
|
1558
|
+
color: c.muted,
|
|
1559
|
+
textTransform: "uppercase"
|
|
1560
|
+
},
|
|
1561
|
+
children: "Describe Changes"
|
|
1562
|
+
}
|
|
1563
|
+
),
|
|
1564
|
+
/* @__PURE__ */ jsx(
|
|
1565
|
+
"textarea",
|
|
1566
|
+
{
|
|
1567
|
+
value: suggestion,
|
|
1568
|
+
onChange: (e) => setSuggestion(e.target.value),
|
|
1569
|
+
placeholder: `e.g., Make this ${(elCtx == null ? void 0 : elCtx.tagName) || "element"} blue with rounded corners...`,
|
|
1570
|
+
autoFocus: true,
|
|
1571
|
+
style: {
|
|
1572
|
+
width: "100%",
|
|
1573
|
+
height: 100,
|
|
1574
|
+
padding: 14,
|
|
1575
|
+
borderRadius: 10,
|
|
1576
|
+
border: `1px solid ${c.border}`,
|
|
1577
|
+
background: c.bgAlt,
|
|
1578
|
+
color: c.text,
|
|
1579
|
+
fontSize: 14,
|
|
1580
|
+
resize: "vertical",
|
|
1581
|
+
fontFamily: "inherit",
|
|
1582
|
+
boxSizing: "border-box"
|
|
1583
|
+
},
|
|
1584
|
+
onKeyDown: (e) => {
|
|
1585
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
1586
|
+
e.preventDefault();
|
|
1587
|
+
handleSubmit();
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
)
|
|
1592
|
+
] }),
|
|
1593
|
+
result && /* @__PURE__ */ jsx(
|
|
1594
|
+
"div",
|
|
1595
|
+
{
|
|
1596
|
+
style: {
|
|
1597
|
+
marginBottom: 20,
|
|
1598
|
+
padding: 14,
|
|
1599
|
+
borderRadius: 10,
|
|
1600
|
+
background: result.success ? `${c.success}15` : `${c.error}15`,
|
|
1601
|
+
color: result.success ? c.success : c.error,
|
|
1602
|
+
fontWeight: 500
|
|
1603
|
+
},
|
|
1604
|
+
children: result.success ? "✓ Applied!" : `✗ ${result.error}`
|
|
1605
|
+
}
|
|
1606
|
+
),
|
|
1607
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 12, flexWrap: "wrap" }, children: [
|
|
1608
|
+
/* @__PURE__ */ jsx(
|
|
1609
|
+
"button",
|
|
1610
|
+
{
|
|
1611
|
+
onClick: () => setSelectedSource(null),
|
|
1612
|
+
style: {
|
|
1613
|
+
flex: "1 1 auto",
|
|
1614
|
+
padding: "12px 20px",
|
|
1615
|
+
borderRadius: 10,
|
|
1616
|
+
border: `1px solid ${c.border}`,
|
|
1617
|
+
background: "transparent",
|
|
1618
|
+
color: c.text,
|
|
1619
|
+
cursor: "pointer"
|
|
1620
|
+
},
|
|
1621
|
+
children: "Cancel"
|
|
1622
|
+
}
|
|
1623
|
+
),
|
|
1624
|
+
ENABLE_SESSION_PERSISTENCE,
|
|
1625
|
+
editHistory.length > 0 && /* @__PURE__ */ jsx(
|
|
1626
|
+
"button",
|
|
1627
|
+
{
|
|
1628
|
+
onClick: handleDone,
|
|
1629
|
+
style: {
|
|
1630
|
+
flex: "1 1 auto",
|
|
1631
|
+
padding: "12px 20px",
|
|
1632
|
+
borderRadius: 10,
|
|
1633
|
+
border: `1px solid ${c.success}`,
|
|
1634
|
+
background: "transparent",
|
|
1635
|
+
color: c.success,
|
|
1636
|
+
fontWeight: 600,
|
|
1637
|
+
cursor: "pointer"
|
|
1638
|
+
},
|
|
1639
|
+
children: "Finish & Reload"
|
|
1640
|
+
}
|
|
1641
|
+
),
|
|
1642
|
+
/* @__PURE__ */ jsx(
|
|
1643
|
+
"button",
|
|
1644
|
+
{
|
|
1645
|
+
onClick: handleSubmit,
|
|
1646
|
+
disabled: isLoading || !suggestion.trim(),
|
|
1647
|
+
style: {
|
|
1648
|
+
flex: "2 1 auto",
|
|
1649
|
+
padding: "12px 20px",
|
|
1650
|
+
borderRadius: 10,
|
|
1651
|
+
border: "none",
|
|
1652
|
+
background: isLoading ? c.muted : c.accent,
|
|
1653
|
+
color: "#fff",
|
|
1654
|
+
fontWeight: 600,
|
|
1655
|
+
cursor: isLoading ? "wait" : "pointer",
|
|
1656
|
+
opacity: !suggestion.trim() ? 0.5 : 1
|
|
1657
|
+
},
|
|
1658
|
+
children: isLoading ? "Applying..." : "Apply Changes ⌘↵"
|
|
1659
|
+
}
|
|
1660
|
+
)
|
|
1661
|
+
] })
|
|
1662
|
+
]
|
|
1663
|
+
}
|
|
1664
|
+
),
|
|
1665
|
+
!selectedSource && /* @__PURE__ */ jsxs(
|
|
1666
|
+
"div",
|
|
1667
|
+
{
|
|
1668
|
+
style: {
|
|
1669
|
+
position: "fixed",
|
|
1670
|
+
bottom: 20,
|
|
1671
|
+
right: 20,
|
|
1672
|
+
background: c.bg,
|
|
1673
|
+
color: c.success,
|
|
1674
|
+
padding: "10px 16px",
|
|
1675
|
+
borderRadius: 20,
|
|
1676
|
+
fontSize: 13,
|
|
1677
|
+
fontWeight: 600,
|
|
1678
|
+
zIndex: 99997,
|
|
1679
|
+
display: "flex",
|
|
1680
|
+
alignItems: "center",
|
|
1681
|
+
gap: 8,
|
|
1682
|
+
border: `1px solid ${c.border}`,
|
|
1683
|
+
boxShadow: `0 4px 20px rgba(0,0,0,${isDark ? 0.4 : 0.1})`
|
|
1684
|
+
},
|
|
1685
|
+
children: [
|
|
1686
|
+
/* @__PURE__ */ jsx(
|
|
1687
|
+
"span",
|
|
1688
|
+
{
|
|
1689
|
+
style: {
|
|
1690
|
+
width: 8,
|
|
1691
|
+
height: 8,
|
|
1692
|
+
borderRadius: "50%",
|
|
1693
|
+
background: c.success
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
),
|
|
1697
|
+
"AI Editor",
|
|
1698
|
+
/* @__PURE__ */ jsx("span", { style: { color: c.muted, fontSize: 11 }, children: "⌘⇧E" }),
|
|
1699
|
+
ENABLE_SESSION_PERSISTENCE,
|
|
1700
|
+
/* @__PURE__ */ jsx(
|
|
1701
|
+
"button",
|
|
1702
|
+
{
|
|
1703
|
+
onClick: () => setEnabled(false),
|
|
1704
|
+
style: {
|
|
1705
|
+
background: "none",
|
|
1706
|
+
border: "none",
|
|
1707
|
+
color: c.muted,
|
|
1708
|
+
cursor: "pointer"
|
|
1709
|
+
},
|
|
1710
|
+
children: "×"
|
|
1711
|
+
}
|
|
1712
|
+
)
|
|
1713
|
+
]
|
|
1714
|
+
}
|
|
1715
|
+
)
|
|
1716
|
+
]
|
|
1717
|
+
}
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
export {
|
|
1721
|
+
AIEditorProvider as A
|
|
1722
|
+
};
|
|
1723
|
+
//# sourceMappingURL=AIEditorProvider-D-w9-GZb.js.map
|