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