next-ai-editor 0.1.0 → 0.1.2

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-D-w9-GZb.js → AIEditorProvider-CFFnEtEB.js} +849 -402
  2. package/dist/AIEditorProvider-CFFnEtEB.js.map +1 -0
  3. package/dist/{AIEditorProvider-Bs9zUVrL.cjs → AIEditorProvider-CmiACRfw.cjs} +825 -378
  4. package/dist/AIEditorProvider-CmiACRfw.cjs.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-BFa7H-uO.js → index-3OMXRwpD.js} +601 -224
  16. package/dist/index-3OMXRwpD.js.map +1 -0
  17. package/dist/{index-DnoYi4f8.cjs → index-9QODCOgD.cjs} +595 -218
  18. package/dist/index-9QODCOgD.cjs.map +1 -0
  19. package/dist/index.cjs +6 -2
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.js +14 -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 +5 -0
  38. package/dist/server/utils/source-map.d.ts.map +1 -1
  39. package/dist/server.cjs +5 -1
  40. package/dist/server.cjs.map +1 -1
  41. package/dist/server.js +13 -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 +3 -3
  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,121 @@ 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
+ console.log("[AI Editor] fetchSuggestions called. cacheKey:", cacheKey, "lastEdit:", lastEdit, "excludedSuggestions:", excludedSuggestions);
897
+ const cached = !(excludedSuggestions == null ? void 0 : excludedSuggestions.length) ? suggestionsCache.get(cacheKey) : null;
898
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
899
+ console.log("[AI Editor] Using cached suggestions");
900
+ setSuggestions(cached.suggestions);
901
+ return;
902
+ }
903
+ console.log("[AI Editor] Fetching new suggestions from API");
904
+ setSuggestionsLoading(true);
905
+ try {
906
+ const params = new URLSearchParams({
907
+ componentName: source.componentName
908
+ });
909
+ if ((_b = source.elementContext) == null ? void 0 : _b.tagName) {
910
+ params.append("elementTag", source.elementContext.tagName);
911
+ }
912
+ if ((_c = source.elementContext) == null ? void 0 : _c.className) {
913
+ params.append("className", source.elementContext.className);
914
+ }
915
+ if ((_d = source.elementContext) == null ? void 0 : _d.textContent) {
916
+ params.append("textContent", source.elementContext.textContent);
917
+ }
918
+ if (lastEdit) {
919
+ params.append("lastSuggestion", lastEdit);
920
+ }
921
+ if (history.length > 0) {
922
+ params.append(
923
+ "editHistory",
924
+ JSON.stringify(
925
+ history.slice(-3).map((h) => ({ suggestion: h.suggestion, success: h.success }))
926
+ )
927
+ );
928
+ }
929
+ if (excludedSuggestions && excludedSuggestions.length > 0) {
930
+ params.append("excludedSuggestions", JSON.stringify(excludedSuggestions));
931
+ }
932
+ const response = await fetch(`/api/ai-editor/suggestions?${params}`);
933
+ const data = await response.json();
934
+ if (data.success) {
935
+ setSuggestions(data.suggestions);
936
+ setSuggestionsCache((prev) => {
937
+ const newCache = new Map(prev);
938
+ newCache.set(cacheKey, {
939
+ suggestions: data.suggestions,
940
+ timestamp: Date.now()
941
+ });
942
+ return newCache;
943
+ });
944
+ } else {
945
+ setSuggestions([]);
946
+ }
947
+ } catch (error) {
948
+ setSuggestions([]);
949
+ } finally {
950
+ setSuggestionsLoading(false);
951
+ }
952
+ },
953
+ [suggestionsCache, setSuggestions, setSuggestionsLoading, setSuggestionsCache]
666
954
  );
667
955
  const handleSelect = react.useCallback(
668
956
  (element, source) => {
669
957
  setSelectedDOMElement(element);
670
958
  setSelectedSource(source);
671
959
  setEditHistory([]);
960
+ setParentInstance(null);
672
961
  resolveSourceLocation(source).then((resolved) => {
673
962
  if (resolved) {
674
963
  setSelectedSource((prev) => prev === source ? resolved : prev);
675
964
  }
676
965
  });
677
966
  },
678
- [setSelectedDOMElement, setSelectedSource]
967
+ [setSelectedDOMElement, setSelectedSource, setParentInstance]
679
968
  );
680
969
  if (process.env.NODE_ENV !== "development") {
681
970
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
@@ -693,7 +982,12 @@ function AIEditorProvider({
693
982
  setIsLoading,
694
983
  editHistory,
695
984
  setEditHistory,
696
- submitEdit
985
+ submitEdit,
986
+ suggestions,
987
+ suggestionsLoading,
988
+ lastAppliedSuggestion,
989
+ setLastAppliedSuggestion,
990
+ fetchSuggestions
697
991
  },
698
992
  children: [
699
993
  children,
@@ -709,7 +1003,9 @@ function AIEditorProvider({
709
1003
  setStaleSession,
710
1004
  setHasActiveSession,
711
1005
  restoreSession,
712
- handleClearSession
1006
+ handleClearSession,
1007
+ parentInstance,
1008
+ setParentInstance
713
1009
  }
714
1010
  )
715
1011
  ]
@@ -726,7 +1022,9 @@ function EditorOverlay({
726
1022
  setStaleSession,
727
1023
  setHasActiveSession,
728
1024
  restoreSession,
729
- handleClearSession
1025
+ handleClearSession,
1026
+ parentInstance,
1027
+ setParentInstance
730
1028
  }) {
731
1029
  var _a;
732
1030
  const {
@@ -738,7 +1036,12 @@ function EditorOverlay({
738
1036
  setEnabled,
739
1037
  selectedDOMElement,
740
1038
  editHistory,
741
- setEditHistory
1039
+ setEditHistory,
1040
+ suggestions,
1041
+ suggestionsLoading,
1042
+ lastAppliedSuggestion,
1043
+ setLastAppliedSuggestion,
1044
+ fetchSuggestions
742
1045
  } = useAIEditor();
743
1046
  const [hoveredSource, setHoveredSource] = react.useState(
744
1047
  null
@@ -757,43 +1060,33 @@ function EditorOverlay({
757
1060
  const [parsedComponentName, setParsedComponentName] = react.useState(
758
1061
  null
759
1062
  );
1063
+ const [isSourcePreviewExpanded, setIsSourcePreviewExpanded] = react.useState(false);
1064
+ const [isParentPreviewExpanded, setIsParentPreviewExpanded] = react.useState(false);
1065
+ const [isEditHistoryExpanded, setIsEditHistoryExpanded] = react.useState(true);
760
1066
  const isDark = theme === "dark";
1067
+ const refreshCodePreview = react.useCallback(async (source, options = {}) => {
1068
+ const params = buildReadQueryParams(source, options.includeParent ?? true);
1069
+ try {
1070
+ const response = await fetch(`/api/ai-editor/read?` + new URLSearchParams(params));
1071
+ const data = await response.json();
1072
+ if (data.success) {
1073
+ setCode(data.content);
1074
+ setCodeLineStart(data.lineStart || 1);
1075
+ setTargetStartLine(data.targetStartLine || null);
1076
+ setTargetEndLine(data.targetEndLine || null);
1077
+ setParentInstance(data.parentInstance || null);
1078
+ if (data.componentName) {
1079
+ setParsedComponentName(data.componentName);
1080
+ }
1081
+ }
1082
+ } catch (error) {
1083
+ console.error("Error refreshing code preview:", error);
1084
+ }
1085
+ }, []);
761
1086
  react.useEffect(() => {
762
- var _a2, _b, _c, _d;
763
1087
  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
- });
1088
+ refreshCodePreview(selectedSource);
1089
+ fetchSuggestions(selectedSource, []);
797
1090
  fetch(
798
1091
  `/api/ai-editor/absolute-path?` + new URLSearchParams({ path: selectedSource.filePath })
799
1092
  ).then((r) => r.json()).then((d) => d.success && setAbsolutePath(d.absolutePath)).catch(() => setAbsolutePath(null));
@@ -801,7 +1094,7 @@ function EditorOverlay({
801
1094
  setAbsolutePath(null);
802
1095
  setParsedComponentName(null);
803
1096
  }
804
- }, [selectedSource]);
1097
+ }, [selectedSource, refreshCodePreview]);
805
1098
  react.useEffect(() => {
806
1099
  let lastEl = null;
807
1100
  let raf;
@@ -872,54 +1165,38 @@ function EditorOverlay({
872
1165
  };
873
1166
  }, [hoveredSource, hoveredElement, selectedSource, onSelect]);
874
1167
  const handleSubmit = async () => {
875
- var _a2, _b, _c, _d;
876
1168
  if (!suggestion.trim()) return;
1169
+ const appliedSuggestion = suggestion;
877
1170
  const res = await submitEdit(suggestion);
878
1171
  setResult(res);
879
1172
  if (res.success) {
880
1173
  setSuggestion("");
1174
+ setLastAppliedSuggestion(appliedSuggestion);
881
1175
  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);
1176
+ await new Promise((resolve) => setTimeout(resolve, 100));
1177
+ await refreshCodePreview(selectedSource);
1178
+ console.log("[AI Editor] Fetching suggestions after edit:", appliedSuggestion);
1179
+ fetchSuggestions(selectedSource, editHistory, appliedSuggestion);
904
1180
  }
905
1181
  setTimeout(() => setResult(null), 3e3);
906
1182
  }
907
1183
  };
908
1184
  const handleUndo = async () => {
909
- var _a2, _b, _c, _d;
910
1185
  if (editHistory.length === 0 || !selectedSource) return;
911
1186
  const lastSuccessfulEdit = [...editHistory].reverse().find((edit) => edit.success && edit.fileSnapshot);
912
1187
  if (!lastSuccessfulEdit) {
913
1188
  console.warn("No successful edit with snapshot found to undo");
914
1189
  return;
915
1190
  }
1191
+ const fileToRestore = lastSuccessfulEdit.editedFilePath || selectedSource.filePath;
1192
+ console.log(`[Undo] Restoring file: ${fileToRestore}`);
916
1193
  setIsLoading(true);
917
1194
  try {
918
1195
  const response = await fetch("/api/ai-editor/undo", {
919
1196
  method: "POST",
920
1197
  headers: { "Content-Type": "application/json" },
921
1198
  body: JSON.stringify({
922
- filePath: selectedSource.filePath,
1199
+ filePath: fileToRestore,
923
1200
  content: lastSuccessfulEdit.fileSnapshot
924
1201
  })
925
1202
  });
@@ -931,28 +1208,7 @@ function EditorOverlay({
931
1208
  const lastIndex = prev.lastIndexOf(lastSuccessfulEdit);
932
1209
  return prev.filter((_, idx) => idx !== lastIndex);
933
1210
  });
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);
1211
+ await refreshCodePreview(selectedSource);
956
1212
  setResult({ success: true, error: void 0 });
957
1213
  setTimeout(() => setResult(null), 3e3);
958
1214
  } catch (error) {
@@ -963,50 +1219,30 @@ function EditorOverlay({
963
1219
  }
964
1220
  };
965
1221
  const handleRetry = async (editIndex) => {
966
- var _a2, _b, _c, _d;
967
1222
  const editToRetry = editHistory[editIndex];
968
1223
  if (!editToRetry) return;
969
1224
  const res = await submitEdit(editToRetry.suggestion);
970
1225
  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);
1226
+ await new Promise((resolve) => setTimeout(resolve, 100));
1227
+ await refreshCodePreview(selectedSource, { includeParent: false });
993
1228
  }
994
1229
  };
995
1230
  const handleUndoAndRetry = async () => {
996
- var _a2, _b, _c, _d;
997
1231
  if (editHistory.length === 0 || !selectedSource) return;
998
1232
  const lastSuccessfulEdit = [...editHistory].reverse().find((edit) => edit.success && edit.fileSnapshot);
999
1233
  if (!lastSuccessfulEdit) {
1000
1234
  console.warn("No successful edit with snapshot found to undo and retry");
1001
1235
  return;
1002
1236
  }
1237
+ const fileToRestore = lastSuccessfulEdit.editedFilePath || selectedSource.filePath;
1238
+ console.log(`[Undo & Retry] Restoring file: ${fileToRestore}`);
1003
1239
  setIsLoading(true);
1004
1240
  try {
1005
1241
  const undoResponse = await fetch("/api/ai-editor/undo", {
1006
1242
  method: "POST",
1007
1243
  headers: { "Content-Type": "application/json" },
1008
1244
  body: JSON.stringify({
1009
- filePath: selectedSource.filePath,
1245
+ filePath: fileToRestore,
1010
1246
  content: lastSuccessfulEdit.fileSnapshot
1011
1247
  })
1012
1248
  });
@@ -1018,42 +1254,12 @@ function EditorOverlay({
1018
1254
  const lastIndex = prev.lastIndexOf(lastSuccessfulEdit);
1019
1255
  return prev.filter((_, idx) => idx !== lastIndex);
1020
1256
  });
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);
1257
+ await refreshCodePreview(selectedSource);
1043
1258
  setIsLoading(true);
1044
1259
  const retryResult = await submitEdit(lastSuccessfulEdit.suggestion);
1045
1260
  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);
1261
+ await new Promise((resolve) => setTimeout(resolve, 100));
1262
+ await refreshCodePreview(selectedSource);
1057
1263
  }
1058
1264
  if (retryResult.success) {
1059
1265
  setResult({ success: true, error: void 0 });
@@ -1088,6 +1294,12 @@ function EditorOverlay({
1088
1294
  className: "ai-editor-ui",
1089
1295
  style: { fontFamily: "system-ui, sans-serif" },
1090
1296
  children: [
1297
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
1298
+ @keyframes ai-editor-spin {
1299
+ from { transform: rotate(0deg); }
1300
+ to { transform: rotate(360deg); }
1301
+ }
1302
+ ` }),
1091
1303
  hoveredRect && !selectedSource && /* @__PURE__ */ jsxRuntime.jsx(
1092
1304
  "div",
1093
1305
  {
@@ -1293,20 +1505,31 @@ function EditorOverlay({
1293
1505
  }
1294
1506
  ),
1295
1507
  code && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 20 }, children: [
1296
- /* @__PURE__ */ jsxRuntime.jsx(
1297
- "div",
1508
+ /* @__PURE__ */ jsxRuntime.jsxs(
1509
+ "button",
1298
1510
  {
1511
+ onClick: () => setIsSourcePreviewExpanded(!isSourcePreviewExpanded),
1299
1512
  style: {
1300
1513
  fontSize: 11,
1301
1514
  fontWeight: 600,
1302
1515
  marginBottom: 8,
1303
1516
  color: c.muted,
1304
- textTransform: "uppercase"
1517
+ textTransform: "uppercase",
1518
+ background: "transparent",
1519
+ border: "none",
1520
+ padding: 0,
1521
+ cursor: "pointer",
1522
+ display: "flex",
1523
+ alignItems: "center",
1524
+ gap: 6
1305
1525
  },
1306
- children: "Source Preview"
1526
+ children: [
1527
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10 }, children: isSourcePreviewExpanded ? "▼" : "▶" }),
1528
+ "Source Preview"
1529
+ ]
1307
1530
  }
1308
1531
  ),
1309
- /* @__PURE__ */ jsxRuntime.jsx(
1532
+ isSourcePreviewExpanded && /* @__PURE__ */ jsxRuntime.jsx(
1310
1533
  reactSyntaxHighlighter.Prism,
1311
1534
  {
1312
1535
  language: "tsx",
@@ -1342,25 +1565,99 @@ function EditorOverlay({
1342
1565
  }
1343
1566
  )
1344
1567
  ] }),
1568
+ parentInstance && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 20 }, children: [
1569
+ /* @__PURE__ */ jsxRuntime.jsxs(
1570
+ "button",
1571
+ {
1572
+ onClick: () => setIsParentPreviewExpanded(!isParentPreviewExpanded),
1573
+ style: {
1574
+ fontSize: 11,
1575
+ fontWeight: 600,
1576
+ marginBottom: 8,
1577
+ color: c.muted,
1578
+ textTransform: "uppercase",
1579
+ background: "transparent",
1580
+ border: "none",
1581
+ padding: 0,
1582
+ cursor: "pointer",
1583
+ display: "flex",
1584
+ alignItems: "center",
1585
+ gap: 6
1586
+ },
1587
+ children: [
1588
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10 }, children: isParentPreviewExpanded ? "▼" : "▶" }),
1589
+ "Used In: ",
1590
+ parentInstance.componentName,
1591
+ " (",
1592
+ parentInstance.filePath,
1593
+ ")"
1594
+ ]
1595
+ }
1596
+ ),
1597
+ isParentPreviewExpanded && /* @__PURE__ */ jsxRuntime.jsx(
1598
+ reactSyntaxHighlighter.Prism,
1599
+ {
1600
+ language: "tsx",
1601
+ style: isDark ? prism.vscDarkPlus : prism.vs,
1602
+ showLineNumbers: true,
1603
+ startingLineNumber: parentInstance.lineStart,
1604
+ customStyle: {
1605
+ background: c.bgAlt,
1606
+ borderRadius: 10,
1607
+ fontSize: 14,
1608
+ margin: 0,
1609
+ maxHeight: 180,
1610
+ overflowX: "auto",
1611
+ border: `1px solid ${c.border}`
1612
+ },
1613
+ lineNumberStyle: {
1614
+ minWidth: "2.5em",
1615
+ paddingRight: "1em",
1616
+ color: c.muted,
1617
+ textAlign: "right",
1618
+ userSelect: "none"
1619
+ },
1620
+ wrapLines: true,
1621
+ lineProps: (lineNumber) => {
1622
+ const isUsageLine = lineNumber >= parentInstance.usageLineStart && lineNumber <= parentInstance.usageLineEnd;
1623
+ return {
1624
+ style: {
1625
+ backgroundColor: isUsageLine ? isDark ? "rgba(255, 165, 0, 0.15)" : "rgba(255, 165, 0, 0.2)" : "transparent"
1626
+ }
1627
+ };
1628
+ },
1629
+ children: parentInstance.content
1630
+ }
1631
+ )
1632
+ ] }),
1345
1633
  editHistory.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 20 }, children: [
1346
1634
  /* @__PURE__ */ jsxRuntime.jsxs(
1347
- "div",
1635
+ "button",
1348
1636
  {
1637
+ onClick: () => setIsEditHistoryExpanded(!isEditHistoryExpanded),
1349
1638
  style: {
1350
1639
  fontSize: 11,
1351
1640
  fontWeight: 600,
1352
1641
  marginBottom: 8,
1353
1642
  color: c.muted,
1354
- textTransform: "uppercase"
1643
+ textTransform: "uppercase",
1644
+ background: "transparent",
1645
+ border: "none",
1646
+ padding: 0,
1647
+ cursor: "pointer",
1648
+ display: "flex",
1649
+ alignItems: "center",
1650
+ gap: 6
1355
1651
  },
1356
1652
  children: [
1653
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10 }, children: isEditHistoryExpanded ? "▼" : "▶" }),
1357
1654
  "Edit History (",
1358
1655
  editHistory.length,
1359
1656
  ")"
1360
1657
  ]
1361
1658
  }
1362
1659
  ),
1363
- /* @__PURE__ */ jsxRuntime.jsx(
1660
+ isEditHistoryExpanded && /* @__PURE__ */ jsxRuntime.jsx(
1364
1661
  "div",
1365
1662
  {
1366
1663
  style: {
@@ -1400,7 +1697,19 @@ function EditorOverlay({
1400
1697
  marginBottom: 4
1401
1698
  },
1402
1699
  children: [
1403
- /* @__PURE__ */ jsxRuntime.jsx(
1700
+ item.pending ? /* @__PURE__ */ jsxRuntime.jsx(
1701
+ "div",
1702
+ {
1703
+ style: {
1704
+ width: 16,
1705
+ height: 16,
1706
+ border: "2px solid transparent",
1707
+ borderTopColor: c.accent,
1708
+ borderRadius: "50%",
1709
+ animation: "ai-editor-spin 0.8s linear infinite"
1710
+ }
1711
+ }
1712
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1404
1713
  "span",
1405
1714
  {
1406
1715
  style: {
@@ -1451,7 +1760,7 @@ function EditorOverlay({
1451
1760
  )
1452
1761
  ] }),
1453
1762
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: [
1454
- isLastEdit && hasSnapshot && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1763
+ isLastEdit && hasSnapshot && !item.pending && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1455
1764
  /* @__PURE__ */ jsxRuntime.jsx(
1456
1765
  "button",
1457
1766
  {
@@ -1513,25 +1822,25 @@ function EditorOverlay({
1513
1822
  "button",
1514
1823
  {
1515
1824
  onClick: () => handleRetry(idx),
1516
- disabled: isLoading,
1825
+ disabled: isLoading || item.pending,
1517
1826
  style: {
1518
1827
  padding: "4px 10px",
1519
1828
  fontSize: 11,
1520
1829
  borderRadius: 6,
1521
1830
  border: "none",
1522
- background: isLoading ? c.muted : c.accent,
1831
+ background: isLoading || item.pending ? c.muted : c.accent,
1523
1832
  color: "#fff",
1524
1833
  fontWeight: 600,
1525
- cursor: isLoading ? "wait" : "pointer",
1834
+ cursor: isLoading || item.pending ? "wait" : "pointer",
1526
1835
  whiteSpace: "nowrap"
1527
1836
  },
1528
1837
  title: "Retry this edit",
1529
1838
  onMouseEnter: (e) => {
1530
- if (!isLoading)
1839
+ if (!isLoading && !item.pending)
1531
1840
  e.currentTarget.style.background = "#6366f1";
1532
1841
  },
1533
1842
  onMouseLeave: (e) => {
1534
- if (!isLoading)
1843
+ if (!isLoading && !item.pending)
1535
1844
  e.currentTarget.style.background = c.accent;
1536
1845
  },
1537
1846
  children: "↻ Retry"
@@ -1591,6 +1900,144 @@ function EditorOverlay({
1591
1900
  }
1592
1901
  )
1593
1902
  ] }),
1903
+ (suggestions.length > 0 || suggestionsLoading) && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 20 }, children: [
1904
+ /* @__PURE__ */ jsxRuntime.jsxs(
1905
+ "div",
1906
+ {
1907
+ style: {
1908
+ fontSize: 11,
1909
+ fontWeight: 600,
1910
+ marginBottom: 8,
1911
+ color: c.muted,
1912
+ textTransform: "uppercase",
1913
+ display: "flex",
1914
+ alignItems: "center",
1915
+ gap: 8
1916
+ },
1917
+ children: [
1918
+ "Quick Suggestions",
1919
+ suggestionsLoading ? /* @__PURE__ */ jsxRuntime.jsx(
1920
+ "div",
1921
+ {
1922
+ style: {
1923
+ width: 12,
1924
+ height: 12,
1925
+ border: "2px solid transparent",
1926
+ borderTopColor: c.accent,
1927
+ borderRadius: "50%",
1928
+ animation: "ai-editor-spin 0.8s linear infinite"
1929
+ }
1930
+ }
1931
+ ) : suggestions.length > 0 && selectedSource && /* @__PURE__ */ jsxRuntime.jsx(
1932
+ "button",
1933
+ {
1934
+ onClick: () => {
1935
+ console.log("[AI Editor] Refreshing suggestions, excluding:", suggestions);
1936
+ fetchSuggestions(selectedSource, editHistory, lastAppliedSuggestion, suggestions);
1937
+ },
1938
+ disabled: isLoading,
1939
+ style: {
1940
+ background: "transparent",
1941
+ border: "none",
1942
+ padding: 4,
1943
+ cursor: isLoading ? "wait" : "pointer",
1944
+ color: c.muted,
1945
+ fontSize: 14,
1946
+ display: "flex",
1947
+ alignItems: "center",
1948
+ transition: "all 0.15s ease"
1949
+ },
1950
+ onMouseEnter: (e) => {
1951
+ if (!isLoading) {
1952
+ e.currentTarget.style.color = c.accent;
1953
+ e.currentTarget.style.transform = "rotate(180deg)";
1954
+ }
1955
+ },
1956
+ onMouseLeave: (e) => {
1957
+ if (!isLoading) {
1958
+ e.currentTarget.style.color = c.muted;
1959
+ e.currentTarget.style.transform = "rotate(0deg)";
1960
+ }
1961
+ },
1962
+ title: "Get different suggestions",
1963
+ children: "↻"
1964
+ }
1965
+ )
1966
+ ]
1967
+ }
1968
+ ),
1969
+ /* @__PURE__ */ jsxRuntime.jsx(
1970
+ "div",
1971
+ {
1972
+ style: {
1973
+ display: "flex",
1974
+ flexWrap: "wrap",
1975
+ gap: 8
1976
+ },
1977
+ children: suggestionsLoading ? (
1978
+ // Loading skeletons
1979
+ Array.from({ length: 6 }).map((_, idx) => /* @__PURE__ */ jsxRuntime.jsx(
1980
+ "div",
1981
+ {
1982
+ style: {
1983
+ padding: "8px 14px",
1984
+ borderRadius: 8,
1985
+ background: c.bgAlt,
1986
+ border: `1px solid ${c.border}`,
1987
+ fontSize: 13,
1988
+ height: 34,
1989
+ width: Math.random() * 60 + 80,
1990
+ // Variable width
1991
+ opacity: 0.5
1992
+ }
1993
+ },
1994
+ idx
1995
+ ))
1996
+ ) : suggestions.map((suggestionText, idx) => /* @__PURE__ */ jsxRuntime.jsx(
1997
+ "button",
1998
+ {
1999
+ onClick: (e) => {
2000
+ if (e.metaKey || e.ctrlKey) {
2001
+ setSuggestion(suggestionText);
2002
+ setTimeout(() => handleSubmit(), 50);
2003
+ } else {
2004
+ setSuggestion(suggestionText);
2005
+ }
2006
+ },
2007
+ disabled: isLoading,
2008
+ style: {
2009
+ padding: "8px 14px",
2010
+ borderRadius: 8,
2011
+ border: `1px solid ${c.border}`,
2012
+ background: c.bgAlt,
2013
+ color: c.text,
2014
+ fontSize: 13,
2015
+ cursor: isLoading ? "wait" : "pointer",
2016
+ transition: "all 0.15s ease",
2017
+ whiteSpace: "nowrap"
2018
+ },
2019
+ onMouseEnter: (e) => {
2020
+ if (!isLoading) {
2021
+ e.currentTarget.style.background = c.accent + "20";
2022
+ e.currentTarget.style.borderColor = c.accent;
2023
+ e.currentTarget.style.transform = "translateY(-1px)";
2024
+ }
2025
+ },
2026
+ onMouseLeave: (e) => {
2027
+ if (!isLoading) {
2028
+ e.currentTarget.style.background = c.bgAlt;
2029
+ e.currentTarget.style.borderColor = c.border;
2030
+ e.currentTarget.style.transform = "translateY(0)";
2031
+ }
2032
+ },
2033
+ title: "Click to use • Cmd+Click to apply immediately",
2034
+ children: suggestionText
2035
+ },
2036
+ idx
2037
+ ))
2038
+ }
2039
+ )
2040
+ ] }),
1594
2041
  result && /* @__PURE__ */ jsxRuntime.jsx(
1595
2042
  "div",
1596
2043
  {
@@ -1719,4 +2166,4 @@ function EditorOverlay({
1719
2166
  );
1720
2167
  }
1721
2168
  exports.AIEditorProvider = AIEditorProvider;
1722
- //# sourceMappingURL=AIEditorProvider-Bs9zUVrL.cjs.map
2169
+ //# sourceMappingURL=AIEditorProvider-CmiACRfw.cjs.map