next-ai-editor 0.1.1 → 0.1.3

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.
Files changed (50) hide show
  1. package/dist/{AIEditorProvider-Bs9zUVrL.cjs → AIEditorProvider-BGHm2xyU.cjs} +823 -378
  2. package/dist/AIEditorProvider-BGHm2xyU.cjs.map +1 -0
  3. package/dist/{AIEditorProvider-D-w9-GZb.js → AIEditorProvider-CxdGjdLL.js} +847 -402
  4. package/dist/AIEditorProvider-CxdGjdLL.js.map +1 -0
  5. package/dist/client/AIEditorProvider.d.ts +8 -16
  6. package/dist/client/AIEditorProvider.d.ts.map +1 -1
  7. package/dist/client/fiber-utils.d.ts +35 -0
  8. package/dist/client/fiber-utils.d.ts.map +1 -0
  9. package/dist/client/index.d.ts +1 -1
  10. package/dist/client/index.d.ts.map +1 -1
  11. package/dist/client/query-params.d.ts +9 -0
  12. package/dist/client/query-params.d.ts.map +1 -0
  13. package/dist/client.cjs +1 -1
  14. package/dist/client.js +1 -1
  15. package/dist/{index-DnoYi4f8.cjs → index-CNJqd4EQ.cjs} +656 -225
  16. package/dist/index-CNJqd4EQ.cjs.map +1 -0
  17. package/dist/{index-BFa7H-uO.js → index-DrmEf13c.js} +662 -231
  18. package/dist/index-DrmEf13c.js.map +1 -0
  19. package/dist/index.cjs +7 -2
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.js +15 -10
  22. package/dist/path-utils-Bai2xKx9.js +36 -0
  23. package/dist/path-utils-Bai2xKx9.js.map +1 -0
  24. package/dist/path-utils-DYzEWUGy.cjs +35 -0
  25. package/dist/path-utils-DYzEWUGy.cjs.map +1 -0
  26. package/dist/server/handlers/edit.d.ts.map +1 -1
  27. package/dist/server/handlers/index.d.ts +1 -0
  28. package/dist/server/handlers/index.d.ts.map +1 -1
  29. package/dist/server/handlers/read.d.ts.map +1 -1
  30. package/dist/server/handlers/resolve.d.ts.map +1 -1
  31. package/dist/server/handlers/suggestions.d.ts +3 -0
  32. package/dist/server/handlers/suggestions.d.ts.map +1 -0
  33. package/dist/server/index.d.ts +1 -0
  34. package/dist/server/index.d.ts.map +1 -1
  35. package/dist/server/utils/ast.d.ts +10 -0
  36. package/dist/server/utils/ast.d.ts.map +1 -1
  37. package/dist/server/utils/source-map.d.ts +10 -0
  38. package/dist/server/utils/source-map.d.ts.map +1 -1
  39. package/dist/server.cjs +6 -1
  40. package/dist/server.cjs.map +1 -1
  41. package/dist/server.js +14 -9
  42. package/dist/shared/path-utils.d.ts +24 -0
  43. package/dist/shared/path-utils.d.ts.map +1 -0
  44. package/dist/shared/types.d.ts +30 -0
  45. package/dist/shared/types.d.ts.map +1 -1
  46. package/package.json +1 -1
  47. package/dist/AIEditorProvider-Bs9zUVrL.cjs.map +0 -1
  48. package/dist/AIEditorProvider-D-w9-GZb.js.map +0 -1
  49. package/dist/index-BFa7H-uO.js.map +0 -1
  50. package/dist/index-DnoYi4f8.cjs.map +0 -1
@@ -4,6 +4,7 @@ const jsxRuntime = require("react/jsx-runtime");
4
4
  const react = require("react");
5
5
  const reactSyntaxHighlighter = require("react-syntax-highlighter");
6
6
  const prism = require("react-syntax-highlighter/dist/esm/styles/prism");
7
+ const pathUtils = require("./path-utils-DYzEWUGy.cjs");
7
8
  const _AIEditorStorage = class _AIEditorStorage {
8
9
  // 5MB
9
10
  /**
@@ -223,120 +224,31 @@ _AIEditorStorage.STORAGE_PREFIX = "ai-editor:session:";
223
224
  _AIEditorStorage.VERSION = "1.0.0";
224
225
  _AIEditorStorage.MAX_STORAGE_SIZE = 5 * 1024 * 1024;
225
226
  let AIEditorStorage = _AIEditorStorage;
226
- const ENABLE_SESSION_PERSISTENCE = false;
227
- const sourceResolutionCache = /* @__PURE__ */ new Map();
228
- const inflightSourceResolutions = /* @__PURE__ */ new Map();
229
- function inferComponentNameFromPath(filePath) {
230
- const fileName = filePath.split("/").pop() || "";
231
- const nameWithoutExt = fileName.replace(/\.(tsx?|jsx?)$/, "");
232
- return nameWithoutExt.charAt(0).toUpperCase() + nameWithoutExt.slice(1);
233
- }
234
- async function resolveSourceLocation(source) {
235
- if (typeof window === "undefined" || process.env.NODE_ENV !== "development") {
236
- return null;
237
- }
238
- if (!source.debugStack) {
239
- console.warn("No debugStack available for resolution:", source);
240
- return null;
241
- }
242
- const cacheKey = source.debugStack;
243
- const cached = sourceResolutionCache.get(cacheKey);
244
- if (cached) {
245
- const resolved2 = { ...source, ...cached };
246
- if (resolved2.componentName === "Unknown" && resolved2.filePath) {
247
- resolved2.componentName = inferComponentNameFromPath(resolved2.filePath);
248
- }
249
- return resolved2;
250
- }
251
- let inflight = inflightSourceResolutions.get(cacheKey);
252
- if (!inflight) {
253
- inflight = fetch("/api/ai-editor/resolve", {
254
- method: "POST",
255
- headers: { "Content-Type": "application/json" },
256
- body: JSON.stringify({ debugStack: cacheKey })
257
- }).then(async (res) => {
258
- if (!res.ok) {
259
- const errorText = await res.text();
260
- console.error(
261
- `Resolve API error ${res.status}:`,
262
- errorText,
263
- "for stack:",
264
- cacheKey.substring(0, 200)
265
- );
266
- return inferFilePathFromComponentName(source.componentName);
267
- }
268
- const data = await res.json();
269
- if ((data == null ? void 0 : data.success) && data.filePath && data.lineNumber) {
270
- const resolved2 = {
271
- filePath: data.filePath,
272
- lineNumber: data.lineNumber,
273
- columnNumber: typeof data.columnNumber === "number" ? data.columnNumber : source.columnNumber
274
- };
275
- sourceResolutionCache.set(cacheKey, resolved2);
276
- return resolved2;
277
- }
278
- console.warn("Resolve API returned unsuccessful response:", data);
279
- return inferFilePathFromComponentName(source.componentName);
280
- }).catch((err) => {
281
- console.error("Error calling resolve API:", err);
282
- return inferFilePathFromComponentName(source.componentName);
283
- }).finally(() => {
284
- inflightSourceResolutions.delete(cacheKey);
285
- });
286
- inflightSourceResolutions.set(cacheKey, inflight);
287
- }
288
- const resolvedInfo = await inflight;
289
- if (!resolvedInfo) return null;
290
- const resolved = {
291
- ...source,
292
- filePath: resolvedInfo.filePath,
293
- lineNumber: resolvedInfo.lineNumber,
294
- columnNumber: resolvedInfo.columnNumber
227
+ function buildReadQueryParams(selectedSource, includeParent = true) {
228
+ var _a, _b, _c, _d;
229
+ const params = {
230
+ path: selectedSource.filePath,
231
+ line: String(selectedSource.lineNumber),
232
+ tagName: ((_a = selectedSource.elementContext) == null ? void 0 : _a.tagName) || "",
233
+ nthOfType: String(((_b = selectedSource.elementContext) == null ? void 0 : _b.nthOfType) || 0),
234
+ textContent: ((_c = selectedSource.elementContext) == null ? void 0 : _c.textContent) || "",
235
+ className: ((_d = selectedSource.elementContext) == null ? void 0 : _d.className) || ""
295
236
  };
296
- if (resolved.componentName === "Unknown" && resolved.filePath) {
297
- resolved.componentName = inferComponentNameFromPath(resolved.filePath);
237
+ if (selectedSource.debugStack) {
238
+ params.debugStack = selectedSource.debugStack;
298
239
  }
299
- return resolved;
300
- }
301
- async function inferFilePathFromComponentName(componentName) {
302
- if (!componentName || componentName === "Unknown") return null;
303
- const possiblePaths = [
304
- `components/${componentName}.tsx`,
305
- `components/${componentName}.jsx`,
306
- `app/${componentName}.tsx`,
307
- `app/${componentName}.jsx`,
308
- `src/components/${componentName}.tsx`,
309
- `src/components/${componentName}.jsx`
310
- ];
311
- for (const tryPath of possiblePaths) {
312
- try {
313
- const response = await fetch(
314
- `/api/ai-editor/absolute-path?path=${encodeURIComponent(tryPath)}`
315
- );
316
- if (response.ok) {
317
- const data = await response.json();
318
- if (data.success) {
319
- console.log(
320
- `Inferred file path for ${componentName}: ${tryPath}`
321
- );
322
- return {
323
- filePath: tryPath,
324
- lineNumber: 1,
325
- columnNumber: 0
326
- };
327
- }
328
- }
329
- } catch (err) {
240
+ if (includeParent && selectedSource.parentComponent) {
241
+ params.parentFilePath = selectedSource.parentComponent.filePath;
242
+ params.parentLine = String(selectedSource.parentComponent.lineNumber);
243
+ params.parentComponentName = selectedSource.parentComponent.componentName;
244
+ if (selectedSource.parentComponent.debugStack) {
245
+ params.parentDebugStack = selectedSource.parentComponent.debugStack;
246
+ }
247
+ if (selectedSource.parentComponent.childKey) {
248
+ params.childKey = selectedSource.parentComponent.childKey;
330
249
  }
331
250
  }
332
- console.warn(`Could not infer file path for component: ${componentName}`);
333
- return null;
334
- }
335
- const EditorContext = react.createContext(null);
336
- function useAIEditor() {
337
- const ctx = react.useContext(EditorContext);
338
- if (!ctx) throw new Error("useAIEditor must be used within AIEditorProvider");
339
- return ctx;
251
+ return params;
340
252
  }
341
253
  function getFiberFromElement(element) {
342
254
  const key = Object.keys(element).find(
@@ -345,14 +257,26 @@ function getFiberFromElement(element) {
345
257
  return key ? element[key] : null;
346
258
  }
347
259
  function getComponentName(fiber) {
348
- var _a;
260
+ var _a, _b;
349
261
  if (!fiber) return "Unknown";
350
262
  const type = fiber.type;
263
+ if ((_a = fiber._debugSource) == null ? void 0 : _a.fileName) {
264
+ const fileName = fiber._debugSource.fileName;
265
+ const match = fileName.match(/\/([^/]+)\.(tsx?|jsx?)$/);
266
+ if (match) return match[1];
267
+ }
351
268
  if (typeof type === "string") return type;
352
269
  if (typeof type === "function")
353
270
  return type.displayName || type.name || "Anonymous";
354
271
  if (type == null ? void 0 : type.displayName) return type.displayName;
355
- if ((_a = type == null ? void 0 : type.render) == null ? void 0 : _a.name) return type.render.name;
272
+ if ((_b = type == null ? void 0 : type.render) == null ? void 0 : _b.name) return type.render.name;
273
+ if (fiber._debugStack) {
274
+ const stack = String(fiber._debugStack.stack || fiber._debugStack);
275
+ const match = stack.match(/at\s+([A-Z][a-zA-Z0-9]*)\s*\(/);
276
+ if (match && match[1] !== "Object") {
277
+ return match[1];
278
+ }
279
+ }
356
280
  return "Unknown";
357
281
  }
358
282
  function isUserComponent(fiber) {
@@ -361,24 +285,6 @@ function isUserComponent(fiber) {
361
285
  if (!type || typeof type === "string") return false;
362
286
  return true;
363
287
  }
364
- function captureElementContext(element, fiber) {
365
- const tagName = element.tagName.toLowerCase();
366
- let textContent;
367
- const directText = Array.from(element.childNodes).filter((n) => n.nodeType === Node.TEXT_NODE).map((n) => {
368
- var _a;
369
- return (_a = n.textContent) == null ? void 0 : _a.trim();
370
- }).filter(Boolean).join(" ");
371
- if (directText) {
372
- textContent = directText.slice(0, 50);
373
- } else if (element.textContent) {
374
- textContent = element.textContent.trim().slice(0, 50);
375
- }
376
- const className = typeof element.className === "string" ? element.className.slice(0, 100) : void 0;
377
- const nthOfType = countNthOfType(element, tagName);
378
- const key = (fiber == null ? void 0 : fiber.key) ? String(fiber.key) : void 0;
379
- const props = extractProps(fiber);
380
- return { tagName, textContent, className, key, nthOfType, props };
381
- }
382
288
  function countNthOfType(element, tagName) {
383
289
  let boundary = element.parentElement;
384
290
  while (boundary) {
@@ -398,9 +304,6 @@ function countNthOfType(element, tagName) {
398
304
  if (el === element) break;
399
305
  count++;
400
306
  }
401
- console.log(
402
- `[countNthOfType] tagName=${tagName}, found ${sameTagElements.length} elements (including boundary), this is #${count}`
403
- );
404
307
  return count;
405
308
  }
406
309
  function extractProps(fiber) {
@@ -429,51 +332,23 @@ function extractProps(fiber) {
429
332
  }
430
333
  return Object.keys(props).length > 0 ? props : void 0;
431
334
  }
432
- function findSourceFromFiber(fiber) {
433
- if (!fiber) return null;
434
- let current = fiber;
435
- const visited = /* @__PURE__ */ new Set();
436
- while (current && !visited.has(current)) {
437
- visited.add(current);
438
- const sourceData = current._debugSource || extractFromDebugStack(current) || extractFromDebugOwner(current);
439
- if (sourceData == null ? void 0 : sourceData.fileName) {
440
- const filePath = cleanPath(sourceData.fileName);
441
- if (!shouldSkip(filePath)) {
442
- let componentFiber = current;
443
- while (componentFiber) {
444
- if (isUserComponent(componentFiber)) {
445
- const componentName = getComponentName(componentFiber);
446
- const isNextJSInternal = [
447
- "Segment",
448
- "Boundary",
449
- "Router",
450
- "Handler",
451
- "Context",
452
- "Layout",
453
- "Template",
454
- "Scroll",
455
- "Focus",
456
- "Loading",
457
- "Error"
458
- ].some((pattern) => componentName.includes(pattern));
459
- if (!isNextJSInternal) {
460
- const rawDebugStack = current._debugStack ? String(current._debugStack.stack || current._debugStack) : void 0;
461
- return {
462
- filePath,
463
- lineNumber: sourceData.lineNumber || 1,
464
- columnNumber: sourceData.columnNumber || 0,
465
- componentName,
466
- debugStack: rawDebugStack
467
- };
468
- }
469
- }
470
- componentFiber = componentFiber.return || componentFiber._debugOwner || null;
471
- }
472
- }
473
- }
474
- current = current.return || current._debugOwner || null;
335
+ function captureElementContext(element, fiber) {
336
+ const tagName = element.tagName.toLowerCase();
337
+ let textContent;
338
+ const directText = Array.from(element.childNodes).filter((n) => n.nodeType === Node.TEXT_NODE).map((n) => {
339
+ var _a;
340
+ return (_a = n.textContent) == null ? void 0 : _a.trim();
341
+ }).filter(Boolean).join(" ");
342
+ if (directText) {
343
+ textContent = directText.slice(0, 50);
344
+ } else if (element.textContent) {
345
+ textContent = element.textContent.trim().slice(0, 50);
475
346
  }
476
- return null;
347
+ const className = typeof element.className === "string" ? element.className.slice(0, 100) : void 0;
348
+ const nthOfType = countNthOfType(element, tagName);
349
+ const key = (fiber == null ? void 0 : fiber.key) ? String(fiber.key) : void 0;
350
+ const props = extractProps(fiber);
351
+ return { tagName, textContent, className, key, nthOfType, props };
477
352
  }
478
353
  function extractFromDebugStack(fiber) {
479
354
  const stack = fiber._debugStack;
@@ -491,8 +366,8 @@ function extractFromDebugStack(fiber) {
491
366
  if (skipPatterns.some((p) => frame.includes(p))) continue;
492
367
  const match = frame.match(/at\s+(\w+)\s+\((.+?):(\d+):(\d+)\)?$/);
493
368
  if (match) {
494
- const fileName = cleanPath(match[2].replace(/\?[^:]*$/, ""));
495
- if (!shouldSkip(fileName)) {
369
+ const fileName = pathUtils.cleanPath(match[2].replace(/\?[^:]*$/, ""));
370
+ if (!pathUtils.shouldSkipPath(fileName, ["ai-editor-provider"])) {
496
371
  return {
497
372
  fileName,
498
373
  lineNumber: parseInt(match[3], 10),
@@ -516,43 +391,378 @@ function extractFromDebugOwner(fiber) {
516
391
  }
517
392
  return null;
518
393
  }
519
- function cleanPath(p) {
520
- let cleaned = p;
521
- if (cleaned.startsWith("file://")) {
522
- cleaned = cleaned.replace(/^file:\/\//, "");
394
+ function findSourceFromFiber(fiber) {
395
+ if (!fiber) return null;
396
+ let actualComponentName = null;
397
+ if (fiber._debugOwner && typeof fiber._debugOwner === "object") {
398
+ const debugOwner = fiber._debugOwner;
399
+ if (debugOwner.name && typeof debugOwner.name === "string") {
400
+ actualComponentName = debugOwner.name;
401
+ }
523
402
  }
524
- try {
525
- if (cleaned.includes("%")) {
526
- cleaned = decodeURIComponent(cleaned);
403
+ let current = fiber;
404
+ const visited = /* @__PURE__ */ new Set();
405
+ let iterations = 0;
406
+ while (current && !visited.has(current) && iterations < 10) {
407
+ iterations++;
408
+ visited.add(current);
409
+ const sourceData = current._debugSource || extractFromDebugStack(current) || extractFromDebugOwner(current);
410
+ if (sourceData == null ? void 0 : sourceData.fileName) {
411
+ const filePath = pathUtils.cleanPath(sourceData.fileName);
412
+ if (!pathUtils.shouldSkipPath(filePath, ["ai-editor-provider"])) {
413
+ if (actualComponentName) {
414
+ const rawDebugStack = current._debugStack ? String(current._debugStack.stack || current._debugStack) : void 0;
415
+ return {
416
+ filePath,
417
+ lineNumber: sourceData.lineNumber || 1,
418
+ columnNumber: sourceData.columnNumber || 0,
419
+ componentName: actualComponentName,
420
+ debugStack: rawDebugStack
421
+ };
422
+ } else {
423
+ let componentFiber = current;
424
+ while (componentFiber) {
425
+ if (isUserComponent(componentFiber)) {
426
+ const componentName = getComponentName(componentFiber);
427
+ const isNextJSInternal = [
428
+ "Segment",
429
+ "Boundary",
430
+ "Router",
431
+ "Handler",
432
+ "Context",
433
+ "Layout",
434
+ "Template",
435
+ "Scroll",
436
+ "Focus",
437
+ "Loading",
438
+ "Error"
439
+ ].some((pattern) => componentName.includes(pattern));
440
+ if (!isNextJSInternal) {
441
+ const rawDebugStack = current._debugStack ? String(current._debugStack.stack || current._debugStack) : void 0;
442
+ return {
443
+ filePath,
444
+ lineNumber: sourceData.lineNumber || 1,
445
+ columnNumber: sourceData.columnNumber || 0,
446
+ componentName,
447
+ debugStack: rawDebugStack
448
+ };
449
+ }
450
+ }
451
+ componentFiber = componentFiber.return || componentFiber._debugOwner || null;
452
+ }
453
+ }
454
+ }
527
455
  }
528
- } catch (e) {
456
+ current = current.return || current._debugOwner || null;
529
457
  }
530
- cleaned = cleaned.replace(/^webpack-internal:\/\/\/\([^)]+\)\/\.\//, "").replace(/^webpack-internal:\/\/\/\([^)]+\)\//, "").replace(/^webpack-internal:\/\//, "").replace(/^webpack:\/\/[^/]*\//, "").replace(/^about:\/\/React\/Server\//, "").replace(/^\([^)]+\)\//, "").replace(/^\.\//, "").replace(/\?.*$/, "");
531
- return cleaned;
458
+ return null;
532
459
  }
533
- function shouldSkip(p) {
534
- if (!p) return true;
535
- return ["node_modules", "next/dist", "react-dom", "ai-editor-provider"].some(
536
- (s) => p.includes(s)
537
- );
460
+ function findParentComponentFromFiber(childFiber, childComponentName) {
461
+ if (!childFiber) return null;
462
+ let childComponentFiber = null;
463
+ if (childFiber._debugOwner && typeof childFiber._debugOwner === "object") {
464
+ const debugOwner = childFiber._debugOwner;
465
+ if (debugOwner.type && typeof debugOwner.type === "function") {
466
+ const ownerComponentName = getComponentName(debugOwner);
467
+ if (ownerComponentName === childComponentName) {
468
+ childComponentFiber = debugOwner;
469
+ }
470
+ }
471
+ }
472
+ if (!childComponentFiber) {
473
+ let current2 = childFiber;
474
+ let iterations2 = 0;
475
+ while (current2 && iterations2 < 20) {
476
+ iterations2++;
477
+ const componentName = getComponentName(current2);
478
+ current2.index;
479
+ if (isUserComponent(current2)) {
480
+ if (componentName === childComponentName) {
481
+ childComponentFiber = current2;
482
+ break;
483
+ }
484
+ }
485
+ if (current2._debugOwner && typeof current2._debugOwner === "object") {
486
+ const debugOwner = current2._debugOwner;
487
+ if (debugOwner.name === childComponentName && !debugOwner.type) {
488
+ const parent = current2.return;
489
+ const parentDebugOwner = parent == null ? void 0 : parent._debugOwner;
490
+ const isRootElement = !parentDebugOwner || parentDebugOwner.name !== childComponentName;
491
+ if (isRootElement) {
492
+ childComponentFiber = current2;
493
+ break;
494
+ }
495
+ }
496
+ }
497
+ current2 = current2.return;
498
+ }
499
+ }
500
+ let childKey;
501
+ if (childComponentFiber == null ? void 0 : childComponentFiber.key) {
502
+ childKey = String(childComponentFiber.key);
503
+ } else if (childComponentFiber && typeof childComponentFiber.index === "number") {
504
+ childKey = String(childComponentFiber.index);
505
+ }
506
+ if (childFiber._debugOwner && typeof childFiber._debugOwner === "object") {
507
+ const debugOwner = childFiber._debugOwner;
508
+ if (debugOwner.owner && debugOwner.owner.name) {
509
+ const parentName = debugOwner.owner.name;
510
+ const parentDebugLocation = debugOwner.owner.debugLocation;
511
+ if (parentDebugLocation) {
512
+ const stack = String(parentDebugLocation.stack || parentDebugLocation);
513
+ return {
514
+ filePath: "",
515
+ // Will be resolved on server
516
+ lineNumber: 0,
517
+ columnNumber: 0,
518
+ componentName: parentName,
519
+ debugStack: stack,
520
+ childKey
521
+ };
522
+ }
523
+ }
524
+ if (debugOwner.type && typeof debugOwner.type === "function") {
525
+ const componentFiberOwner = debugOwner._debugOwner;
526
+ if (componentFiberOwner && typeof componentFiberOwner === "object") {
527
+ const parentName = componentFiberOwner.name;
528
+ const parentDebugLocation = componentFiberOwner.debugLocation;
529
+ if (parentName && typeof parentName === "string") {
530
+ if (parentDebugLocation) {
531
+ const stack = String(
532
+ parentDebugLocation.stack || parentDebugLocation
533
+ );
534
+ return {
535
+ filePath: "",
536
+ // Will be resolved on server
537
+ lineNumber: 0,
538
+ columnNumber: 0,
539
+ componentName: parentName,
540
+ debugStack: stack,
541
+ childKey
542
+ };
543
+ }
544
+ }
545
+ }
546
+ }
547
+ }
548
+ let current = childFiber.return;
549
+ const visited = /* @__PURE__ */ new Set();
550
+ let iterations = 0;
551
+ while (current && !visited.has(current) && iterations < 20) {
552
+ iterations++;
553
+ visited.add(current);
554
+ if (isUserComponent(current)) {
555
+ const componentName = getComponentName(current);
556
+ if (componentName === childComponentName) {
557
+ current = current._debugOwner || null;
558
+ continue;
559
+ }
560
+ const shouldSkipComponent = componentName.includes("AIEditorProvider") || // Skip the AI Editor wrapper itself
561
+ componentName.startsWith("__next") || // Skip all Next.js internal components like __next_root_layout_boundary__
562
+ componentName.startsWith("_") || // Skip internal components starting with underscore
563
+ [
564
+ "Segment",
565
+ "Boundary",
566
+ "Router",
567
+ "Handler",
568
+ "Context",
569
+ "Layout",
570
+ "Template",
571
+ "Scroll",
572
+ "Focus",
573
+ "Loading",
574
+ "Error",
575
+ "RootLayout",
576
+ // Skip root layout wrapper
577
+ "NotFound"
578
+ ].some((pattern) => componentName.includes(pattern));
579
+ if (shouldSkipComponent) {
580
+ current = current._debugOwner || null;
581
+ continue;
582
+ }
583
+ const sourceData = current._debugSource || extractFromDebugStack(current) || extractFromDebugOwner(current);
584
+ if (sourceData == null ? void 0 : sourceData.fileName) {
585
+ const filePath = pathUtils.cleanPath(sourceData.fileName);
586
+ if (!pathUtils.shouldSkipPath(filePath, ["ai-editor-provider"])) {
587
+ const rawDebugStack = current._debugStack ? String(current._debugStack.stack || current._debugStack) : void 0;
588
+ return {
589
+ filePath,
590
+ lineNumber: sourceData.lineNumber || 1,
591
+ columnNumber: sourceData.columnNumber || 0,
592
+ componentName,
593
+ debugStack: rawDebugStack,
594
+ childKey
595
+ };
596
+ }
597
+ }
598
+ }
599
+ const nextOwner = current._debugOwner;
600
+ current = nextOwner || null;
601
+ }
602
+ const childDebugStack = childFiber._debugStack ? String(childFiber._debugStack.stack || childFiber._debugStack) : null;
603
+ if (childDebugStack) {
604
+ return {
605
+ filePath: "",
606
+ // Will be resolved on server from debugStack
607
+ lineNumber: 0,
608
+ columnNumber: 0,
609
+ componentName: "",
610
+ // Will be determined on server
611
+ debugStack: childDebugStack,
612
+ // Use child's debugStack for server-side resolution
613
+ childKey
614
+ };
615
+ }
616
+ return null;
538
617
  }
539
618
  function getSourceFromElement(element) {
619
+ var _a;
540
620
  let current = element;
541
- let fiber = null;
621
+ let elementWithSource = null;
622
+ let fiberWithSource = null;
542
623
  while (current && current !== document.body) {
543
- fiber = getFiberFromElement(current);
544
- if (fiber) break;
624
+ const fiber = getFiberFromElement(current);
625
+ if (fiber) {
626
+ const hasSourceInfo = ((_a = fiber._debugSource) == null ? void 0 : _a.fileName) || fiber._debugStack || fiber._debugOwner && typeof fiber._debugOwner === "object";
627
+ if (hasSourceInfo && !fiberWithSource) {
628
+ elementWithSource = current;
629
+ fiberWithSource = fiber;
630
+ }
631
+ }
545
632
  current = current.parentElement;
546
633
  }
547
- if (!fiber) return null;
548
- const source = findSourceFromFiber(fiber);
634
+ if (!fiberWithSource || !elementWithSource) return null;
635
+ const source = findSourceFromFiber(fiberWithSource);
549
636
  if (!source) return null;
550
- const elementContext = captureElementContext(element, fiber);
551
- return { ...source, elementContext };
637
+ const elementContext = captureElementContext(elementWithSource, fiberWithSource);
638
+ const parentComponent = findParentComponentFromFiber(
639
+ fiberWithSource,
640
+ source.componentName
641
+ );
642
+ return {
643
+ ...source,
644
+ elementContext,
645
+ parentComponent: parentComponent || void 0
646
+ };
552
647
  }
553
648
  if (typeof window !== "undefined") {
554
649
  window.__getSource = getSourceFromElement;
555
650
  }
651
+ const ENABLE_SESSION_PERSISTENCE = false;
652
+ const sourceResolutionCache = /* @__PURE__ */ new Map();
653
+ const inflightSourceResolutions = /* @__PURE__ */ new Map();
654
+ function inferComponentNameFromPath(filePath) {
655
+ const fileName = filePath.split("/").pop() || "";
656
+ const nameWithoutExt = fileName.replace(/\.(tsx?|jsx?)$/, "");
657
+ return nameWithoutExt.charAt(0).toUpperCase() + nameWithoutExt.slice(1);
658
+ }
659
+ async function resolveSourceLocation(source) {
660
+ if (typeof window === "undefined" || process.env.NODE_ENV !== "development") {
661
+ return null;
662
+ }
663
+ if (!source.debugStack) {
664
+ console.warn("No debugStack available for resolution:", source);
665
+ return null;
666
+ }
667
+ const cacheKey = source.debugStack;
668
+ const cached = sourceResolutionCache.get(cacheKey);
669
+ if (cached) {
670
+ const resolved2 = { ...source, ...cached };
671
+ if (resolved2.componentName === "Unknown" && resolved2.filePath) {
672
+ resolved2.componentName = inferComponentNameFromPath(resolved2.filePath);
673
+ }
674
+ return resolved2;
675
+ }
676
+ let inflight = inflightSourceResolutions.get(cacheKey);
677
+ if (!inflight) {
678
+ inflight = fetch("/api/ai-editor/resolve", {
679
+ method: "POST",
680
+ headers: { "Content-Type": "application/json" },
681
+ body: JSON.stringify({ debugStack: cacheKey })
682
+ }).then(async (res) => {
683
+ if (!res.ok) {
684
+ const errorText = await res.text();
685
+ console.error(
686
+ `Resolve API error ${res.status}:`,
687
+ errorText,
688
+ "for stack:",
689
+ cacheKey.substring(0, 200)
690
+ );
691
+ return inferFilePathFromComponentName(source.componentName);
692
+ }
693
+ const data = await res.json();
694
+ if ((data == null ? void 0 : data.success) && data.filePath && data.lineNumber) {
695
+ const resolved2 = {
696
+ filePath: data.filePath,
697
+ lineNumber: data.lineNumber,
698
+ columnNumber: typeof data.columnNumber === "number" ? data.columnNumber : source.columnNumber
699
+ };
700
+ sourceResolutionCache.set(cacheKey, resolved2);
701
+ return resolved2;
702
+ }
703
+ console.warn("Resolve API returned unsuccessful response:", data);
704
+ return inferFilePathFromComponentName(source.componentName);
705
+ }).catch((err) => {
706
+ console.error("Error calling resolve API:", err);
707
+ return inferFilePathFromComponentName(source.componentName);
708
+ }).finally(() => {
709
+ inflightSourceResolutions.delete(cacheKey);
710
+ });
711
+ inflightSourceResolutions.set(cacheKey, inflight);
712
+ }
713
+ const resolvedInfo = await inflight;
714
+ if (!resolvedInfo) return null;
715
+ const resolved = {
716
+ ...source,
717
+ filePath: resolvedInfo.filePath,
718
+ lineNumber: resolvedInfo.lineNumber,
719
+ columnNumber: resolvedInfo.columnNumber
720
+ };
721
+ if (resolved.componentName === "Unknown" && resolved.filePath) {
722
+ resolved.componentName = inferComponentNameFromPath(resolved.filePath);
723
+ }
724
+ return resolved;
725
+ }
726
+ async function inferFilePathFromComponentName(componentName) {
727
+ if (!componentName || componentName === "Unknown") return null;
728
+ const possiblePaths = [
729
+ `components/${componentName}.tsx`,
730
+ `components/${componentName}.jsx`,
731
+ `app/${componentName}.tsx`,
732
+ `app/${componentName}.jsx`,
733
+ `src/components/${componentName}.tsx`,
734
+ `src/components/${componentName}.jsx`
735
+ ];
736
+ for (const tryPath of possiblePaths) {
737
+ try {
738
+ const response = await fetch(
739
+ `/api/ai-editor/absolute-path?path=${encodeURIComponent(tryPath)}`
740
+ );
741
+ if (response.ok) {
742
+ const data = await response.json();
743
+ if (data.success) {
744
+ console.log(
745
+ `Inferred file path for ${componentName}: ${tryPath}`
746
+ );
747
+ return {
748
+ filePath: tryPath,
749
+ lineNumber: 1,
750
+ columnNumber: 0
751
+ };
752
+ }
753
+ }
754
+ } catch (err) {
755
+ }
756
+ }
757
+ console.warn(`Could not infer file path for component: ${componentName}`);
758
+ return null;
759
+ }
760
+ const EditorContext = react.createContext(null);
761
+ function useAIEditor() {
762
+ const ctx = react.useContext(EditorContext);
763
+ if (!ctx) throw new Error("useAIEditor must be used within AIEditorProvider");
764
+ return ctx;
765
+ }
556
766
  function AIEditorProvider({
557
767
  children,
558
768
  theme = "dark"
@@ -569,6 +779,11 @@ function AIEditorProvider({
569
779
  const [showStaleWarning, setShowStaleWarning] = react.useState(false);
570
780
  const [staleSession, setStaleSession] = react.useState(null);
571
781
  const [hasActiveSession, setHasActiveSession] = react.useState(false);
782
+ const [parentInstance, setParentInstance] = react.useState(null);
783
+ const [suggestions, setSuggestions] = react.useState([]);
784
+ const [suggestionsLoading, setSuggestionsLoading] = react.useState(false);
785
+ const [lastAppliedSuggestion, setLastAppliedSuggestion] = react.useState(void 0);
786
+ const [suggestionsCache, setSuggestionsCache] = react.useState(/* @__PURE__ */ new Map());
572
787
  react.useEffect(() => {
573
788
  const handleKey = (e) => {
574
789
  if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === "e") {
@@ -608,6 +823,15 @@ function AIEditorProvider({
608
823
  async (suggestion) => {
609
824
  if (!selectedSource)
610
825
  return { success: false, error: "No element selected" };
826
+ const editId = Date.now().toString(36) + Math.random().toString(36).substring(2);
827
+ const pendingEdit = {
828
+ id: editId,
829
+ suggestion,
830
+ success: false,
831
+ pending: true,
832
+ timestamp: Date.now()
833
+ };
834
+ setEditHistory((prev) => [...prev, pendingEdit]);
611
835
  setIsLoading(true);
612
836
  try {
613
837
  const res = await fetch("/api/ai-editor/edit", {
@@ -626,56 +850,118 @@ function AIEditorProvider({
626
850
  editHistory: editHistory.map((item) => ({
627
851
  suggestion: item.suggestion,
628
852
  success: item.success
629
- }))
853
+ })),
854
+ // Pass parent instance if available
855
+ parentInstance
630
856
  })
631
857
  });
632
858
  const result = await res.json();
633
- const editId = Date.now().toString(36) + Math.random().toString(36).substring(2);
634
- const newHistoryItem = {
859
+ const completedEdit = {
635
860
  id: editId,
636
861
  suggestion,
637
862
  success: result.success,
863
+ pending: false,
638
864
  error: result.error,
639
865
  timestamp: Date.now(),
640
866
  fileSnapshot: result.fileSnapshot,
867
+ editedFilePath: result.editedFile,
868
+ // Track which file was actually edited
641
869
  generatedCode: result.generatedCode,
642
870
  modifiedLines: result.modifiedLines
643
871
  };
644
- setEditHistory((prev) => [...prev, newHistoryItem]);
872
+ setEditHistory(
873
+ (prev) => prev.map((item) => item.id === editId ? completedEdit : item)
874
+ );
645
875
  if (ENABLE_SESSION_PERSISTENCE && selectedSource && result.fileSnapshot) ;
646
876
  return result;
647
877
  } catch (err) {
648
878
  const error = String(err);
649
- const editId = Date.now().toString(36) + Math.random().toString(36).substring(2);
650
- setEditHistory((prev) => [
651
- ...prev,
652
- {
653
- id: editId,
654
- suggestion,
655
- success: false,
656
- error,
657
- timestamp: Date.now()
658
- }
659
- ]);
879
+ setEditHistory(
880
+ (prev) => prev.map(
881
+ (item) => item.id === editId ? { ...item, success: false, pending: false, error } : item
882
+ )
883
+ );
660
884
  return { success: false, error };
661
885
  } finally {
662
886
  setIsLoading(false);
663
887
  }
664
888
  },
665
- [selectedSource, editHistory]
889
+ [selectedSource, editHistory, parentInstance]
890
+ );
891
+ const fetchSuggestions = react.useCallback(
892
+ async (source, history, lastEdit, excludedSuggestions) => {
893
+ var _a, _b, _c, _d;
894
+ const CACHE_TTL = 3e4;
895
+ const cacheKey = `${source.componentName}:${((_a = source.elementContext) == null ? void 0 : _a.tagName) || "div"}:${lastEdit || "initial"}`;
896
+ const cached = !(excludedSuggestions == null ? void 0 : excludedSuggestions.length) ? suggestionsCache.get(cacheKey) : null;
897
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
898
+ setSuggestions(cached.suggestions);
899
+ return;
900
+ }
901
+ setSuggestionsLoading(true);
902
+ try {
903
+ const params = new URLSearchParams({
904
+ componentName: source.componentName
905
+ });
906
+ if ((_b = source.elementContext) == null ? void 0 : _b.tagName) {
907
+ params.append("elementTag", source.elementContext.tagName);
908
+ }
909
+ if ((_c = source.elementContext) == null ? void 0 : _c.className) {
910
+ params.append("className", source.elementContext.className);
911
+ }
912
+ if ((_d = source.elementContext) == null ? void 0 : _d.textContent) {
913
+ params.append("textContent", source.elementContext.textContent);
914
+ }
915
+ if (lastEdit) {
916
+ params.append("lastSuggestion", lastEdit);
917
+ }
918
+ if (history.length > 0) {
919
+ params.append(
920
+ "editHistory",
921
+ JSON.stringify(
922
+ history.slice(-3).map((h) => ({ suggestion: h.suggestion, success: h.success }))
923
+ )
924
+ );
925
+ }
926
+ if (excludedSuggestions && excludedSuggestions.length > 0) {
927
+ params.append("excludedSuggestions", JSON.stringify(excludedSuggestions));
928
+ }
929
+ const response = await fetch(`/api/ai-editor/suggestions?${params}`);
930
+ const data = await response.json();
931
+ if (data.success) {
932
+ setSuggestions(data.suggestions);
933
+ setSuggestionsCache((prev) => {
934
+ const newCache = new Map(prev);
935
+ newCache.set(cacheKey, {
936
+ suggestions: data.suggestions,
937
+ timestamp: Date.now()
938
+ });
939
+ return newCache;
940
+ });
941
+ } else {
942
+ setSuggestions([]);
943
+ }
944
+ } catch (error) {
945
+ setSuggestions([]);
946
+ } finally {
947
+ setSuggestionsLoading(false);
948
+ }
949
+ },
950
+ [suggestionsCache, setSuggestions, setSuggestionsLoading, setSuggestionsCache]
666
951
  );
667
952
  const handleSelect = react.useCallback(
668
953
  (element, source) => {
669
954
  setSelectedDOMElement(element);
670
955
  setSelectedSource(source);
671
956
  setEditHistory([]);
957
+ setParentInstance(null);
672
958
  resolveSourceLocation(source).then((resolved) => {
673
959
  if (resolved) {
674
960
  setSelectedSource((prev) => prev === source ? resolved : prev);
675
961
  }
676
962
  });
677
963
  },
678
- [setSelectedDOMElement, setSelectedSource]
964
+ [setSelectedDOMElement, setSelectedSource, setParentInstance]
679
965
  );
680
966
  if (process.env.NODE_ENV !== "development") {
681
967
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
@@ -693,7 +979,12 @@ function AIEditorProvider({
693
979
  setIsLoading,
694
980
  editHistory,
695
981
  setEditHistory,
696
- submitEdit
982
+ submitEdit,
983
+ suggestions,
984
+ suggestionsLoading,
985
+ lastAppliedSuggestion,
986
+ setLastAppliedSuggestion,
987
+ fetchSuggestions
697
988
  },
698
989
  children: [
699
990
  children,
@@ -709,7 +1000,9 @@ function AIEditorProvider({
709
1000
  setStaleSession,
710
1001
  setHasActiveSession,
711
1002
  restoreSession,
712
- handleClearSession
1003
+ handleClearSession,
1004
+ parentInstance,
1005
+ setParentInstance
713
1006
  }
714
1007
  )
715
1008
  ]
@@ -726,7 +1019,9 @@ function EditorOverlay({
726
1019
  setStaleSession,
727
1020
  setHasActiveSession,
728
1021
  restoreSession,
729
- handleClearSession
1022
+ handleClearSession,
1023
+ parentInstance,
1024
+ setParentInstance
730
1025
  }) {
731
1026
  var _a;
732
1027
  const {
@@ -738,7 +1033,12 @@ function EditorOverlay({
738
1033
  setEnabled,
739
1034
  selectedDOMElement,
740
1035
  editHistory,
741
- setEditHistory
1036
+ setEditHistory,
1037
+ suggestions,
1038
+ suggestionsLoading,
1039
+ lastAppliedSuggestion,
1040
+ setLastAppliedSuggestion,
1041
+ fetchSuggestions
742
1042
  } = useAIEditor();
743
1043
  const [hoveredSource, setHoveredSource] = react.useState(
744
1044
  null
@@ -757,43 +1057,34 @@ function EditorOverlay({
757
1057
  const [parsedComponentName, setParsedComponentName] = react.useState(
758
1058
  null
759
1059
  );
1060
+ const [isSourcePreviewExpanded, setIsSourcePreviewExpanded] = react.useState(false);
1061
+ const [isParentPreviewExpanded, setIsParentPreviewExpanded] = react.useState(false);
1062
+ const [isEditHistoryExpanded, setIsEditHistoryExpanded] = react.useState(true);
760
1063
  const isDark = theme === "dark";
1064
+ const refreshCodePreview = react.useCallback(async (source, options = {}) => {
1065
+ const params = buildReadQueryParams(source, options.includeParent ?? true);
1066
+ try {
1067
+ const url = `/api/ai-editor/read?` + new URLSearchParams(params);
1068
+ const response = await fetch(url);
1069
+ const data = await response.json();
1070
+ if (data.success) {
1071
+ setCode(data.content);
1072
+ setCodeLineStart(data.lineStart || 1);
1073
+ setTargetStartLine(data.targetStartLine || null);
1074
+ setTargetEndLine(data.targetEndLine || null);
1075
+ setParentInstance(data.parentInstance || null);
1076
+ if (data.componentName) {
1077
+ setParsedComponentName(data.componentName);
1078
+ }
1079
+ }
1080
+ } catch (error) {
1081
+ console.error("Error refreshing code preview:", error);
1082
+ }
1083
+ }, []);
761
1084
  react.useEffect(() => {
762
- var _a2, _b, _c, _d;
763
1085
  if (selectedSource) {
764
- const params = {
765
- path: selectedSource.filePath,
766
- line: String(selectedSource.lineNumber),
767
- tagName: ((_a2 = selectedSource.elementContext) == null ? void 0 : _a2.tagName) || "",
768
- nthOfType: String(((_b = selectedSource.elementContext) == null ? void 0 : _b.nthOfType) || 0),
769
- textContent: ((_c = selectedSource.elementContext) == null ? void 0 : _c.textContent) || "",
770
- className: ((_d = selectedSource.elementContext) == null ? void 0 : _d.className) || ""
771
- };
772
- if (selectedSource.debugStack) {
773
- params.debugStack = selectedSource.debugStack;
774
- }
775
- fetch(`/api/ai-editor/read?` + new URLSearchParams(params)).then((r) => r.json()).then((d) => {
776
- if (d.success) {
777
- console.log("[AI Editor] Read response:", {
778
- lineStart: d.lineStart,
779
- targetStartLine: d.targetStartLine,
780
- targetEndLine: d.targetEndLine,
781
- componentName: d.componentName
782
- });
783
- setCode(d.content);
784
- setCodeLineStart(d.lineStart || 1);
785
- setTargetStartLine(d.targetStartLine || null);
786
- setTargetEndLine(d.targetEndLine || null);
787
- if (d.componentName) {
788
- setParsedComponentName(d.componentName);
789
- }
790
- }
791
- }).catch(() => {
792
- setCode("// Could not load");
793
- setCodeLineStart(1);
794
- setTargetStartLine(null);
795
- setTargetEndLine(null);
796
- });
1086
+ refreshCodePreview(selectedSource);
1087
+ fetchSuggestions(selectedSource, []);
797
1088
  fetch(
798
1089
  `/api/ai-editor/absolute-path?` + new URLSearchParams({ path: selectedSource.filePath })
799
1090
  ).then((r) => r.json()).then((d) => d.success && setAbsolutePath(d.absolutePath)).catch(() => setAbsolutePath(null));
@@ -801,7 +1092,7 @@ function EditorOverlay({
801
1092
  setAbsolutePath(null);
802
1093
  setParsedComponentName(null);
803
1094
  }
804
- }, [selectedSource]);
1095
+ }, [selectedSource, refreshCodePreview]);
805
1096
  react.useEffect(() => {
806
1097
  let lastEl = null;
807
1098
  let raf;
@@ -872,54 +1163,38 @@ function EditorOverlay({
872
1163
  };
873
1164
  }, [hoveredSource, hoveredElement, selectedSource, onSelect]);
874
1165
  const handleSubmit = async () => {
875
- var _a2, _b, _c, _d;
876
1166
  if (!suggestion.trim()) return;
1167
+ const appliedSuggestion = suggestion;
877
1168
  const res = await submitEdit(suggestion);
878
1169
  setResult(res);
879
1170
  if (res.success) {
880
1171
  setSuggestion("");
1172
+ setLastAppliedSuggestion(appliedSuggestion);
881
1173
  if (selectedSource) {
882
- const params = {
883
- path: selectedSource.filePath,
884
- line: String(selectedSource.lineNumber),
885
- tagName: ((_a2 = selectedSource.elementContext) == null ? void 0 : _a2.tagName) || "",
886
- nthOfType: String(((_b = selectedSource.elementContext) == null ? void 0 : _b.nthOfType) || 0),
887
- textContent: ((_c = selectedSource.elementContext) == null ? void 0 : _c.textContent) || "",
888
- className: ((_d = selectedSource.elementContext) == null ? void 0 : _d.className) || ""
889
- };
890
- if (selectedSource.debugStack) {
891
- params.debugStack = selectedSource.debugStack;
892
- }
893
- fetch(`/api/ai-editor/read?` + new URLSearchParams(params)).then((r) => r.json()).then((d) => {
894
- if (d.success) {
895
- setCode(d.content);
896
- setCodeLineStart(d.lineStart || 1);
897
- setTargetStartLine(d.targetStartLine || null);
898
- setTargetEndLine(d.targetEndLine || null);
899
- if (d.componentName) {
900
- setParsedComponentName(d.componentName);
901
- }
902
- }
903
- }).catch(console.error);
1174
+ await new Promise((resolve) => setTimeout(resolve, 100));
1175
+ await refreshCodePreview(selectedSource);
1176
+ console.log("[AI Editor] Fetching suggestions after edit:", appliedSuggestion);
1177
+ fetchSuggestions(selectedSource, editHistory, appliedSuggestion);
904
1178
  }
905
1179
  setTimeout(() => setResult(null), 3e3);
906
1180
  }
907
1181
  };
908
1182
  const handleUndo = async () => {
909
- var _a2, _b, _c, _d;
910
1183
  if (editHistory.length === 0 || !selectedSource) return;
911
1184
  const lastSuccessfulEdit = [...editHistory].reverse().find((edit) => edit.success && edit.fileSnapshot);
912
1185
  if (!lastSuccessfulEdit) {
913
1186
  console.warn("No successful edit with snapshot found to undo");
914
1187
  return;
915
1188
  }
1189
+ const fileToRestore = lastSuccessfulEdit.editedFilePath || selectedSource.filePath;
1190
+ console.log(`[Undo] Restoring file: ${fileToRestore}`);
916
1191
  setIsLoading(true);
917
1192
  try {
918
1193
  const response = await fetch("/api/ai-editor/undo", {
919
1194
  method: "POST",
920
1195
  headers: { "Content-Type": "application/json" },
921
1196
  body: JSON.stringify({
922
- filePath: selectedSource.filePath,
1197
+ filePath: fileToRestore,
923
1198
  content: lastSuccessfulEdit.fileSnapshot
924
1199
  })
925
1200
  });
@@ -931,28 +1206,7 @@ function EditorOverlay({
931
1206
  const lastIndex = prev.lastIndexOf(lastSuccessfulEdit);
932
1207
  return prev.filter((_, idx) => idx !== lastIndex);
933
1208
  });
934
- const params = {
935
- path: selectedSource.filePath,
936
- line: String(selectedSource.lineNumber),
937
- tagName: ((_a2 = selectedSource.elementContext) == null ? void 0 : _a2.tagName) || "",
938
- nthOfType: String(((_b = selectedSource.elementContext) == null ? void 0 : _b.nthOfType) || 0),
939
- textContent: ((_c = selectedSource.elementContext) == null ? void 0 : _c.textContent) || "",
940
- className: ((_d = selectedSource.elementContext) == null ? void 0 : _d.className) || ""
941
- };
942
- if (selectedSource.debugStack) {
943
- params.debugStack = selectedSource.debugStack;
944
- }
945
- fetch(`/api/ai-editor/read?` + new URLSearchParams(params)).then((r) => r.json()).then((d) => {
946
- if (d.success) {
947
- setCode(d.content);
948
- setCodeLineStart(d.lineStart || 1);
949
- setTargetStartLine(d.targetStartLine || null);
950
- setTargetEndLine(d.targetEndLine || null);
951
- if (d.componentName) {
952
- setParsedComponentName(d.componentName);
953
- }
954
- }
955
- }).catch(console.error);
1209
+ await refreshCodePreview(selectedSource);
956
1210
  setResult({ success: true, error: void 0 });
957
1211
  setTimeout(() => setResult(null), 3e3);
958
1212
  } catch (error) {
@@ -963,50 +1217,30 @@ function EditorOverlay({
963
1217
  }
964
1218
  };
965
1219
  const handleRetry = async (editIndex) => {
966
- var _a2, _b, _c, _d;
967
1220
  const editToRetry = editHistory[editIndex];
968
1221
  if (!editToRetry) return;
969
1222
  const res = await submitEdit(editToRetry.suggestion);
970
1223
  if (res.success && selectedSource) {
971
- const params = {
972
- path: selectedSource.filePath,
973
- line: String(selectedSource.lineNumber),
974
- tagName: ((_a2 = selectedSource.elementContext) == null ? void 0 : _a2.tagName) || "",
975
- nthOfType: String(((_b = selectedSource.elementContext) == null ? void 0 : _b.nthOfType) || 0),
976
- textContent: ((_c = selectedSource.elementContext) == null ? void 0 : _c.textContent) || "",
977
- className: ((_d = selectedSource.elementContext) == null ? void 0 : _d.className) || ""
978
- };
979
- if (selectedSource.debugStack) {
980
- params.debugStack = selectedSource.debugStack;
981
- }
982
- fetch(`/api/ai-editor/read?` + new URLSearchParams(params)).then((r) => r.json()).then((d) => {
983
- if (d.success) {
984
- setCode(d.content);
985
- setCodeLineStart(d.lineStart || 1);
986
- setTargetStartLine(d.targetStartLine || null);
987
- setTargetEndLine(d.targetEndLine || null);
988
- if (d.componentName) {
989
- setParsedComponentName(d.componentName);
990
- }
991
- }
992
- }).catch(console.error);
1224
+ await new Promise((resolve) => setTimeout(resolve, 100));
1225
+ await refreshCodePreview(selectedSource, { includeParent: false });
993
1226
  }
994
1227
  };
995
1228
  const handleUndoAndRetry = async () => {
996
- var _a2, _b, _c, _d;
997
1229
  if (editHistory.length === 0 || !selectedSource) return;
998
1230
  const lastSuccessfulEdit = [...editHistory].reverse().find((edit) => edit.success && edit.fileSnapshot);
999
1231
  if (!lastSuccessfulEdit) {
1000
1232
  console.warn("No successful edit with snapshot found to undo and retry");
1001
1233
  return;
1002
1234
  }
1235
+ const fileToRestore = lastSuccessfulEdit.editedFilePath || selectedSource.filePath;
1236
+ console.log(`[Undo & Retry] Restoring file: ${fileToRestore}`);
1003
1237
  setIsLoading(true);
1004
1238
  try {
1005
1239
  const undoResponse = await fetch("/api/ai-editor/undo", {
1006
1240
  method: "POST",
1007
1241
  headers: { "Content-Type": "application/json" },
1008
1242
  body: JSON.stringify({
1009
- filePath: selectedSource.filePath,
1243
+ filePath: fileToRestore,
1010
1244
  content: lastSuccessfulEdit.fileSnapshot
1011
1245
  })
1012
1246
  });
@@ -1018,42 +1252,12 @@ function EditorOverlay({
1018
1252
  const lastIndex = prev.lastIndexOf(lastSuccessfulEdit);
1019
1253
  return prev.filter((_, idx) => idx !== lastIndex);
1020
1254
  });
1021
- const params = {
1022
- path: selectedSource.filePath,
1023
- line: String(selectedSource.lineNumber),
1024
- tagName: ((_a2 = selectedSource.elementContext) == null ? void 0 : _a2.tagName) || "",
1025
- nthOfType: String(((_b = selectedSource.elementContext) == null ? void 0 : _b.nthOfType) || 0),
1026
- textContent: ((_c = selectedSource.elementContext) == null ? void 0 : _c.textContent) || "",
1027
- className: ((_d = selectedSource.elementContext) == null ? void 0 : _d.className) || ""
1028
- };
1029
- if (selectedSource.debugStack) {
1030
- params.debugStack = selectedSource.debugStack;
1031
- }
1032
- await fetch(`/api/ai-editor/read?` + new URLSearchParams(params)).then((r) => r.json()).then((d) => {
1033
- if (d.success) {
1034
- setCode(d.content);
1035
- setCodeLineStart(d.lineStart || 1);
1036
- setTargetStartLine(d.targetStartLine || null);
1037
- setTargetEndLine(d.targetEndLine || null);
1038
- if (d.componentName) {
1039
- setParsedComponentName(d.componentName);
1040
- }
1041
- }
1042
- }).catch(console.error);
1255
+ await refreshCodePreview(selectedSource);
1043
1256
  setIsLoading(true);
1044
1257
  const retryResult = await submitEdit(lastSuccessfulEdit.suggestion);
1045
1258
  if (retryResult.success && selectedSource) {
1046
- await fetch(`/api/ai-editor/read?` + new URLSearchParams(params)).then((r) => r.json()).then((d) => {
1047
- if (d.success) {
1048
- setCode(d.content);
1049
- setCodeLineStart(d.lineStart || 1);
1050
- setTargetStartLine(d.targetStartLine || null);
1051
- setTargetEndLine(d.targetEndLine || null);
1052
- if (d.componentName) {
1053
- setParsedComponentName(d.componentName);
1054
- }
1055
- }
1056
- }).catch(console.error);
1259
+ await new Promise((resolve) => setTimeout(resolve, 100));
1260
+ await refreshCodePreview(selectedSource);
1057
1261
  }
1058
1262
  if (retryResult.success) {
1059
1263
  setResult({ success: true, error: void 0 });
@@ -1088,6 +1292,12 @@ function EditorOverlay({
1088
1292
  className: "ai-editor-ui",
1089
1293
  style: { fontFamily: "system-ui, sans-serif" },
1090
1294
  children: [
1295
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
1296
+ @keyframes ai-editor-spin {
1297
+ from { transform: rotate(0deg); }
1298
+ to { transform: rotate(360deg); }
1299
+ }
1300
+ ` }),
1091
1301
  hoveredRect && !selectedSource && /* @__PURE__ */ jsxRuntime.jsx(
1092
1302
  "div",
1093
1303
  {
@@ -1293,20 +1503,31 @@ function EditorOverlay({
1293
1503
  }
1294
1504
  ),
1295
1505
  code && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 20 }, children: [
1296
- /* @__PURE__ */ jsxRuntime.jsx(
1297
- "div",
1506
+ /* @__PURE__ */ jsxRuntime.jsxs(
1507
+ "button",
1298
1508
  {
1509
+ onClick: () => setIsSourcePreviewExpanded(!isSourcePreviewExpanded),
1299
1510
  style: {
1300
1511
  fontSize: 11,
1301
1512
  fontWeight: 600,
1302
1513
  marginBottom: 8,
1303
1514
  color: c.muted,
1304
- textTransform: "uppercase"
1515
+ textTransform: "uppercase",
1516
+ background: "transparent",
1517
+ border: "none",
1518
+ padding: 0,
1519
+ cursor: "pointer",
1520
+ display: "flex",
1521
+ alignItems: "center",
1522
+ gap: 6
1305
1523
  },
1306
- children: "Source Preview"
1524
+ children: [
1525
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10 }, children: isSourcePreviewExpanded ? "▼" : "▶" }),
1526
+ "Source Preview"
1527
+ ]
1307
1528
  }
1308
1529
  ),
1309
- /* @__PURE__ */ jsxRuntime.jsx(
1530
+ isSourcePreviewExpanded && /* @__PURE__ */ jsxRuntime.jsx(
1310
1531
  reactSyntaxHighlighter.Prism,
1311
1532
  {
1312
1533
  language: "tsx",
@@ -1342,25 +1563,99 @@ function EditorOverlay({
1342
1563
  }
1343
1564
  )
1344
1565
  ] }),
1566
+ parentInstance && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 20 }, children: [
1567
+ /* @__PURE__ */ jsxRuntime.jsxs(
1568
+ "button",
1569
+ {
1570
+ onClick: () => setIsParentPreviewExpanded(!isParentPreviewExpanded),
1571
+ style: {
1572
+ fontSize: 11,
1573
+ fontWeight: 600,
1574
+ marginBottom: 8,
1575
+ color: c.muted,
1576
+ textTransform: "uppercase",
1577
+ background: "transparent",
1578
+ border: "none",
1579
+ padding: 0,
1580
+ cursor: "pointer",
1581
+ display: "flex",
1582
+ alignItems: "center",
1583
+ gap: 6
1584
+ },
1585
+ children: [
1586
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10 }, children: isParentPreviewExpanded ? "▼" : "▶" }),
1587
+ "Used In: ",
1588
+ parentInstance.componentName,
1589
+ " (",
1590
+ parentInstance.filePath,
1591
+ ")"
1592
+ ]
1593
+ }
1594
+ ),
1595
+ isParentPreviewExpanded && /* @__PURE__ */ jsxRuntime.jsx(
1596
+ reactSyntaxHighlighter.Prism,
1597
+ {
1598
+ language: "tsx",
1599
+ style: isDark ? prism.vscDarkPlus : prism.vs,
1600
+ showLineNumbers: true,
1601
+ startingLineNumber: parentInstance.lineStart,
1602
+ customStyle: {
1603
+ background: c.bgAlt,
1604
+ borderRadius: 10,
1605
+ fontSize: 14,
1606
+ margin: 0,
1607
+ maxHeight: 180,
1608
+ overflowX: "auto",
1609
+ border: `1px solid ${c.border}`
1610
+ },
1611
+ lineNumberStyle: {
1612
+ minWidth: "2.5em",
1613
+ paddingRight: "1em",
1614
+ color: c.muted,
1615
+ textAlign: "right",
1616
+ userSelect: "none"
1617
+ },
1618
+ wrapLines: true,
1619
+ lineProps: (lineNumber) => {
1620
+ const isUsageLine = lineNumber >= parentInstance.usageLineStart && lineNumber <= parentInstance.usageLineEnd;
1621
+ return {
1622
+ style: {
1623
+ backgroundColor: isUsageLine ? isDark ? "rgba(255, 165, 0, 0.15)" : "rgba(255, 165, 0, 0.2)" : "transparent"
1624
+ }
1625
+ };
1626
+ },
1627
+ children: parentInstance.content
1628
+ }
1629
+ )
1630
+ ] }),
1345
1631
  editHistory.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 20 }, children: [
1346
1632
  /* @__PURE__ */ jsxRuntime.jsxs(
1347
- "div",
1633
+ "button",
1348
1634
  {
1635
+ onClick: () => setIsEditHistoryExpanded(!isEditHistoryExpanded),
1349
1636
  style: {
1350
1637
  fontSize: 11,
1351
1638
  fontWeight: 600,
1352
1639
  marginBottom: 8,
1353
1640
  color: c.muted,
1354
- textTransform: "uppercase"
1641
+ textTransform: "uppercase",
1642
+ background: "transparent",
1643
+ border: "none",
1644
+ padding: 0,
1645
+ cursor: "pointer",
1646
+ display: "flex",
1647
+ alignItems: "center",
1648
+ gap: 6
1355
1649
  },
1356
1650
  children: [
1651
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10 }, children: isEditHistoryExpanded ? "▼" : "▶" }),
1357
1652
  "Edit History (",
1358
1653
  editHistory.length,
1359
1654
  ")"
1360
1655
  ]
1361
1656
  }
1362
1657
  ),
1363
- /* @__PURE__ */ jsxRuntime.jsx(
1658
+ isEditHistoryExpanded && /* @__PURE__ */ jsxRuntime.jsx(
1364
1659
  "div",
1365
1660
  {
1366
1661
  style: {
@@ -1400,7 +1695,19 @@ function EditorOverlay({
1400
1695
  marginBottom: 4
1401
1696
  },
1402
1697
  children: [
1403
- /* @__PURE__ */ jsxRuntime.jsx(
1698
+ item.pending ? /* @__PURE__ */ jsxRuntime.jsx(
1699
+ "div",
1700
+ {
1701
+ style: {
1702
+ width: 16,
1703
+ height: 16,
1704
+ border: "2px solid transparent",
1705
+ borderTopColor: c.accent,
1706
+ borderRadius: "50%",
1707
+ animation: "ai-editor-spin 0.8s linear infinite"
1708
+ }
1709
+ }
1710
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1404
1711
  "span",
1405
1712
  {
1406
1713
  style: {
@@ -1451,7 +1758,7 @@ function EditorOverlay({
1451
1758
  )
1452
1759
  ] }),
1453
1760
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: [
1454
- isLastEdit && hasSnapshot && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1761
+ isLastEdit && hasSnapshot && !item.pending && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1455
1762
  /* @__PURE__ */ jsxRuntime.jsx(
1456
1763
  "button",
1457
1764
  {
@@ -1513,25 +1820,25 @@ function EditorOverlay({
1513
1820
  "button",
1514
1821
  {
1515
1822
  onClick: () => handleRetry(idx),
1516
- disabled: isLoading,
1823
+ disabled: isLoading || item.pending,
1517
1824
  style: {
1518
1825
  padding: "4px 10px",
1519
1826
  fontSize: 11,
1520
1827
  borderRadius: 6,
1521
1828
  border: "none",
1522
- background: isLoading ? c.muted : c.accent,
1829
+ background: isLoading || item.pending ? c.muted : c.accent,
1523
1830
  color: "#fff",
1524
1831
  fontWeight: 600,
1525
- cursor: isLoading ? "wait" : "pointer",
1832
+ cursor: isLoading || item.pending ? "wait" : "pointer",
1526
1833
  whiteSpace: "nowrap"
1527
1834
  },
1528
1835
  title: "Retry this edit",
1529
1836
  onMouseEnter: (e) => {
1530
- if (!isLoading)
1837
+ if (!isLoading && !item.pending)
1531
1838
  e.currentTarget.style.background = "#6366f1";
1532
1839
  },
1533
1840
  onMouseLeave: (e) => {
1534
- if (!isLoading)
1841
+ if (!isLoading && !item.pending)
1535
1842
  e.currentTarget.style.background = c.accent;
1536
1843
  },
1537
1844
  children: "↻ Retry"
@@ -1591,6 +1898,144 @@ function EditorOverlay({
1591
1898
  }
1592
1899
  )
1593
1900
  ] }),
1901
+ (suggestions.length > 0 || suggestionsLoading) && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 20 }, children: [
1902
+ /* @__PURE__ */ jsxRuntime.jsxs(
1903
+ "div",
1904
+ {
1905
+ style: {
1906
+ fontSize: 11,
1907
+ fontWeight: 600,
1908
+ marginBottom: 8,
1909
+ color: c.muted,
1910
+ textTransform: "uppercase",
1911
+ display: "flex",
1912
+ alignItems: "center",
1913
+ gap: 8
1914
+ },
1915
+ children: [
1916
+ "Quick Suggestions",
1917
+ suggestionsLoading ? /* @__PURE__ */ jsxRuntime.jsx(
1918
+ "div",
1919
+ {
1920
+ style: {
1921
+ width: 12,
1922
+ height: 12,
1923
+ border: "2px solid transparent",
1924
+ borderTopColor: c.accent,
1925
+ borderRadius: "50%",
1926
+ animation: "ai-editor-spin 0.8s linear infinite"
1927
+ }
1928
+ }
1929
+ ) : suggestions.length > 0 && selectedSource && /* @__PURE__ */ jsxRuntime.jsx(
1930
+ "button",
1931
+ {
1932
+ onClick: () => {
1933
+ console.log("[AI Editor] Refreshing suggestions, excluding:", suggestions);
1934
+ fetchSuggestions(selectedSource, editHistory, lastAppliedSuggestion, suggestions);
1935
+ },
1936
+ disabled: isLoading,
1937
+ style: {
1938
+ background: "transparent",
1939
+ border: "none",
1940
+ padding: 4,
1941
+ cursor: isLoading ? "wait" : "pointer",
1942
+ color: c.muted,
1943
+ fontSize: 14,
1944
+ display: "flex",
1945
+ alignItems: "center",
1946
+ transition: "all 0.15s ease"
1947
+ },
1948
+ onMouseEnter: (e) => {
1949
+ if (!isLoading) {
1950
+ e.currentTarget.style.color = c.accent;
1951
+ e.currentTarget.style.transform = "rotate(180deg)";
1952
+ }
1953
+ },
1954
+ onMouseLeave: (e) => {
1955
+ if (!isLoading) {
1956
+ e.currentTarget.style.color = c.muted;
1957
+ e.currentTarget.style.transform = "rotate(0deg)";
1958
+ }
1959
+ },
1960
+ title: "Get different suggestions",
1961
+ children: "↻"
1962
+ }
1963
+ )
1964
+ ]
1965
+ }
1966
+ ),
1967
+ /* @__PURE__ */ jsxRuntime.jsx(
1968
+ "div",
1969
+ {
1970
+ style: {
1971
+ display: "flex",
1972
+ flexWrap: "wrap",
1973
+ gap: 8
1974
+ },
1975
+ children: suggestionsLoading ? (
1976
+ // Loading skeletons
1977
+ Array.from({ length: 6 }).map((_, idx) => /* @__PURE__ */ jsxRuntime.jsx(
1978
+ "div",
1979
+ {
1980
+ style: {
1981
+ padding: "8px 14px",
1982
+ borderRadius: 8,
1983
+ background: c.bgAlt,
1984
+ border: `1px solid ${c.border}`,
1985
+ fontSize: 13,
1986
+ height: 34,
1987
+ width: Math.random() * 60 + 80,
1988
+ // Variable width
1989
+ opacity: 0.5
1990
+ }
1991
+ },
1992
+ idx
1993
+ ))
1994
+ ) : suggestions.map((suggestionText, idx) => /* @__PURE__ */ jsxRuntime.jsx(
1995
+ "button",
1996
+ {
1997
+ onClick: (e) => {
1998
+ if (e.metaKey || e.ctrlKey) {
1999
+ setSuggestion(suggestionText);
2000
+ setTimeout(() => handleSubmit(), 50);
2001
+ } else {
2002
+ setSuggestion(suggestionText);
2003
+ }
2004
+ },
2005
+ disabled: isLoading,
2006
+ style: {
2007
+ padding: "8px 14px",
2008
+ borderRadius: 8,
2009
+ border: `1px solid ${c.border}`,
2010
+ background: c.bgAlt,
2011
+ color: c.text,
2012
+ fontSize: 13,
2013
+ cursor: isLoading ? "wait" : "pointer",
2014
+ transition: "all 0.15s ease",
2015
+ whiteSpace: "nowrap"
2016
+ },
2017
+ onMouseEnter: (e) => {
2018
+ if (!isLoading) {
2019
+ e.currentTarget.style.background = c.accent + "20";
2020
+ e.currentTarget.style.borderColor = c.accent;
2021
+ e.currentTarget.style.transform = "translateY(-1px)";
2022
+ }
2023
+ },
2024
+ onMouseLeave: (e) => {
2025
+ if (!isLoading) {
2026
+ e.currentTarget.style.background = c.bgAlt;
2027
+ e.currentTarget.style.borderColor = c.border;
2028
+ e.currentTarget.style.transform = "translateY(0)";
2029
+ }
2030
+ },
2031
+ title: "Click to use • Cmd+Click to apply immediately",
2032
+ children: suggestionText
2033
+ },
2034
+ idx
2035
+ ))
2036
+ }
2037
+ )
2038
+ ] }),
1594
2039
  result && /* @__PURE__ */ jsxRuntime.jsx(
1595
2040
  "div",
1596
2041
  {
@@ -1719,4 +2164,4 @@ function EditorOverlay({
1719
2164
  );
1720
2165
  }
1721
2166
  exports.AIEditorProvider = AIEditorProvider;
1722
- //# sourceMappingURL=AIEditorProvider-Bs9zUVrL.cjs.map
2167
+ //# sourceMappingURL=AIEditorProvider-BGHm2xyU.cjs.map