next-ai-editor 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/AIEditorProvider-CKA2K_g2.js +1428 -0
  2. package/dist/AIEditorProvider-CKA2K_g2.js.map +1 -0
  3. package/dist/AIEditorProvider-C_zRSAuV.cjs +1427 -0
  4. package/dist/AIEditorProvider-C_zRSAuV.cjs.map +1 -0
  5. package/dist/client/AIEditorProvider.d.ts +1 -33
  6. package/dist/client/AIEditorProvider.d.ts.map +1 -1
  7. package/dist/client/components/ChatPanel.d.ts +18 -0
  8. package/dist/client/components/ChatPanel.d.ts.map +1 -0
  9. package/dist/client/components/ControlPill.d.ts +8 -0
  10. package/dist/client/components/ControlPill.d.ts.map +1 -0
  11. package/dist/client/components/MessageItem.d.ts +7 -0
  12. package/dist/client/components/MessageItem.d.ts.map +1 -0
  13. package/dist/client/components/MessageList.d.ts +7 -0
  14. package/dist/client/components/MessageList.d.ts.map +1 -0
  15. package/dist/client/components/TaskHistoryPanel.d.ts +10 -0
  16. package/dist/client/components/TaskHistoryPanel.d.ts.map +1 -0
  17. package/dist/client/components/index.d.ts +11 -0
  18. package/dist/client/components/index.d.ts.map +1 -0
  19. package/dist/client/hooks/index.d.ts +3 -0
  20. package/dist/client/hooks/index.d.ts.map +1 -0
  21. package/dist/client/hooks/useChatStream.d.ts +66 -0
  22. package/dist/client/hooks/useChatStream.d.ts.map +1 -0
  23. package/dist/client/hooks/useHotReload.d.ts +10 -0
  24. package/dist/client/hooks/useHotReload.d.ts.map +1 -0
  25. package/dist/client/index.d.ts +3 -0
  26. package/dist/client/index.d.ts.map +1 -1
  27. package/dist/client.cjs +7 -1
  28. package/dist/client.cjs.map +1 -1
  29. package/dist/client.js +8 -2
  30. package/dist/client.js.map +1 -1
  31. package/dist/{index-3OMXRwpD.js → comments-D3m0RsOO.js} +505 -26
  32. package/dist/comments-D3m0RsOO.js.map +1 -0
  33. package/dist/{index-9QODCOgD.cjs → comments-Daur80r4.cjs} +492 -13
  34. package/dist/comments-Daur80r4.cjs.map +1 -0
  35. package/dist/index.cjs +34 -26
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.js +22 -14
  38. package/dist/index.js.map +1 -1
  39. package/dist/next-ai-editor.css +880 -0
  40. package/dist/server/agent/sdk-client.d.ts +54 -0
  41. package/dist/server/agent/sdk-client.d.ts.map +1 -0
  42. package/dist/server/agent/session-store.d.ts +101 -0
  43. package/dist/server/agent/session-store.d.ts.map +1 -0
  44. package/dist/server/handlers/chat.d.ts +6 -0
  45. package/dist/server/handlers/chat.d.ts.map +1 -0
  46. package/dist/server/handlers/comments.d.ts +10 -0
  47. package/dist/server/handlers/comments.d.ts.map +1 -0
  48. package/dist/server/handlers/index.d.ts +2 -1
  49. package/dist/server/handlers/index.d.ts.map +1 -1
  50. package/dist/server/handlers/read.d.ts.map +1 -1
  51. package/dist/server/index.d.ts +1 -0
  52. package/dist/server/index.d.ts.map +1 -1
  53. package/dist/server/utils/ast.d.ts.map +1 -1
  54. package/dist/server/utils/source-map.d.ts +5 -0
  55. package/dist/server/utils/source-map.d.ts.map +1 -1
  56. package/dist/server.cjs +27 -25
  57. package/dist/server.cjs.map +1 -1
  58. package/dist/server.js +14 -12
  59. package/dist/shared/comment-types.d.ts +140 -0
  60. package/dist/shared/comment-types.d.ts.map +1 -0
  61. package/package.json +13 -4
  62. package/dist/AIEditorProvider-CFFnEtEB.js +0 -2170
  63. package/dist/AIEditorProvider-CFFnEtEB.js.map +0 -1
  64. package/dist/AIEditorProvider-CmiACRfw.cjs +0 -2169
  65. package/dist/AIEditorProvider-CmiACRfw.cjs.map +0 -1
  66. package/dist/index-3OMXRwpD.js.map +0 -1
  67. package/dist/index-9QODCOgD.cjs.map +0 -1
@@ -1,14 +1,16 @@
1
1
  import { NextResponse } from "next/server";
2
- import fs from "fs/promises";
2
+ import fs, { readFile, writeFile, mkdir } from "fs/promises";
3
3
  import { ChatAnthropic } from "@langchain/anthropic";
4
4
  import { SystemMessage, HumanMessage } from "@langchain/core/messages";
5
- import path from "path";
5
+ import path, { join } from "path";
6
6
  import * as parser from "@babel/parser";
7
7
  import traverse from "@babel/traverse";
8
8
  import * as t from "@babel/types";
9
9
  import { SourceMapConsumer } from "@jridgewell/source-map";
10
10
  import { c as cleanPath, s as shouldSkipPath, n as normalizeSourcePath } from "./path-utils-Bai2xKx9.js";
11
11
  import crypto from "crypto";
12
+ import { unstable_v2_resumeSession, unstable_v2_createSession } from "@anthropic-ai/claude-agent-sdk";
13
+ import { existsSync } from "fs";
12
14
  function validateDevMode() {
13
15
  if (process.env.NODE_ENV !== "development") {
14
16
  return NextResponse.json(
@@ -88,6 +90,11 @@ function extractComponentName(ast) {
88
90
  if (t.isIdentifier(declarator.id)) {
89
91
  componentName = declarator.id.name;
90
92
  }
93
+ } else if (path2.node.specifiers && path2.node.specifiers.length > 0) {
94
+ const specifier = path2.node.specifiers[0];
95
+ if (t.isExportSpecifier(specifier) && t.isIdentifier(specifier.exported)) {
96
+ componentName = specifier.exported.name;
97
+ }
91
98
  }
92
99
  }
93
100
  });
@@ -239,16 +246,34 @@ function findTargetElement(ast, fileContent, options) {
239
246
  componentEnd
240
247
  };
241
248
  }
242
- if ((elementContext == null ? void 0 : elementContext.nthOfType) && elementContext.tagName) {
249
+ if (elementContext == null ? void 0 : elementContext.tagName) {
243
250
  const allOfTag = allElementsByTag.get(elementContext.tagName);
244
- if (allOfTag && allOfTag.length >= elementContext.nthOfType) {
245
- const target = allOfTag[elementContext.nthOfType - 1];
246
- return {
247
- startLine: target.startLine,
248
- endLine: target.endLine,
249
- componentStart,
250
- componentEnd
251
- };
251
+ if (allOfTag && allOfTag.length > 0) {
252
+ if (elementContext.textContent || elementContext.className) {
253
+ for (const elem of allOfTag) {
254
+ if (t.isJSXElement(elem.node)) {
255
+ elem.score = scoreElementMatch(elem.node, elementContext, fileContent);
256
+ }
257
+ }
258
+ allOfTag.sort((a, b) => b.score - a.score);
259
+ if (allOfTag[0].score > 50) {
260
+ return {
261
+ startLine: allOfTag[0].startLine,
262
+ endLine: allOfTag[0].endLine,
263
+ componentStart,
264
+ componentEnd
265
+ };
266
+ }
267
+ }
268
+ if (elementContext.nthOfType && allOfTag.length >= elementContext.nthOfType) {
269
+ const target = allOfTag[elementContext.nthOfType - 1];
270
+ return {
271
+ startLine: target.startLine,
272
+ endLine: target.endLine,
273
+ componentStart,
274
+ componentEnd
275
+ };
276
+ }
252
277
  }
253
278
  }
254
279
  const nearbyElements = [];
@@ -695,6 +720,30 @@ function parseDebugStack(stack) {
695
720
  );
696
721
  return null;
697
722
  }
723
+ function extractComponentNameFromStack(stack) {
724
+ if (!stack) return null;
725
+ const stackStr = typeof stack === "string" ? stack : stack.stack || String(stack);
726
+ const frames = stackStr.split("\n");
727
+ const skipPatterns = [
728
+ "node_modules",
729
+ "SegmentViewNode",
730
+ "LayoutRouter",
731
+ "ErrorBoundary",
732
+ "fakeJSXCallSite",
733
+ "react_stack_bottom_frame"
734
+ ];
735
+ for (const frame of frames) {
736
+ if (skipPatterns.some((p) => frame.includes(p))) continue;
737
+ const match = frame.match(/at\s+(\w+)\s+\(/);
738
+ if (match && match[1]) {
739
+ const componentName = match[1];
740
+ if (componentName !== "Object" && componentName !== "anonymous") {
741
+ return componentName;
742
+ }
743
+ }
744
+ }
745
+ return null;
746
+ }
698
747
  function parseDebugStackFrames(stack) {
699
748
  if (!stack) return [];
700
749
  const stackStr = typeof stack === "string" ? stack : stack.stack || String(stack);
@@ -995,7 +1044,10 @@ async function handleRead(req) {
995
1044
  let resolvedParentPath = parentFilePath;
996
1045
  let resolvedParentLine = parentLine;
997
1046
  let resolvedParentComponentName = parentComponentName;
998
- if (resolvedParentComponentName && parentDebugStack) {
1047
+ if (!resolvedParentComponentName && parentDebugStack) {
1048
+ resolvedParentComponentName = extractComponentNameFromStack(parentDebugStack) || "";
1049
+ }
1050
+ if (parentDebugStack) {
999
1051
  const compiledPos = parseDebugStack(parentDebugStack);
1000
1052
  if (compiledPos) {
1001
1053
  const originalPos = await resolveOriginalPosition(
@@ -1027,12 +1079,14 @@ async function handleRead(req) {
1027
1079
  const parentTarget = findTargetElement(parentAst, parentContent, {
1028
1080
  componentName: resolvedParentComponentName,
1029
1081
  lineNumber: 0,
1030
- // Don't use line number - rely on nthOfType to find correct instance
1082
+ // Don't use line number - rely on element context to find correct instance
1031
1083
  elementContext: {
1032
1084
  tagName: componentName,
1033
1085
  // Search for child component usage
1034
- nthOfType: nthOfType2
1086
+ nthOfType: nthOfType2,
1035
1087
  // Find specific instance if key is numeric
1088
+ textContent: textContent || void 0
1089
+ // Use text content to match the specific instance
1036
1090
  }
1037
1091
  });
1038
1092
  if (parentTarget) {
@@ -1054,6 +1108,7 @@ async function handleRead(req) {
1054
1108
  }
1055
1109
  }
1056
1110
  } catch (error) {
1111
+ console.error("Error resolving parent instance:", error);
1057
1112
  }
1058
1113
  }
1059
1114
  return NextResponse.json({
@@ -1461,6 +1516,361 @@ Generate 6-8 initial suggestions:`;
1461
1516
  return null;
1462
1517
  }
1463
1518
  }
1519
+ class AIEditorAgent {
1520
+ constructor(session, projectRoot) {
1521
+ this.session = session;
1522
+ this.projectRoot = projectRoot;
1523
+ }
1524
+ /**
1525
+ * Create a new agent instance
1526
+ */
1527
+ static async create(config) {
1528
+ const sessionOptions = {
1529
+ model: "claude-sonnet-4-5-20250929",
1530
+ // Enable auto-apply for edits without permission prompts
1531
+ permissionMode: "acceptEdits",
1532
+ env: {
1533
+ ...process.env,
1534
+ ANTHROPIC_API_KEY: config.apiKey,
1535
+ // Set working directory
1536
+ PWD: config.projectRoot
1537
+ },
1538
+ // Explicitly allow core development tools
1539
+ allowedTools: config.allowedTools || ["Read", "Edit", "Glob", "Grep"],
1540
+ disallowedTools: config.disallowedTools || []
1541
+ };
1542
+ let session;
1543
+ if (config.sessionId) {
1544
+ session = unstable_v2_resumeSession(config.sessionId, sessionOptions);
1545
+ } else {
1546
+ session = unstable_v2_createSession(sessionOptions);
1547
+ }
1548
+ return new AIEditorAgent(session, config.projectRoot);
1549
+ }
1550
+ /**
1551
+ * Send a message to the agent and stream responses
1552
+ */
1553
+ async *sendMessage(message, componentContext) {
1554
+ let fullMessage = `<response_style>
1555
+ KEEP RESPONSES EXTREMELY BRIEF AND STATUS-LIKE.
1556
+ - Use terse status messages: "Reading file.tsx", "Changing color", "Done"
1557
+ - NO conversational language (avoid: "I'll", "Let me", "Sure", "Great", "I can see")
1558
+ - NO explanations unless explicitly asked
1559
+ - State ONLY what you're doing, nothing more
1560
+ - Format: Action + target (e.g., "Updating StatsCard.tsx:24")
1561
+ </response_style>
1562
+
1563
+ `;
1564
+ if (componentContext) {
1565
+ fullMessage += `[Component: ${componentContext.componentName} at ${componentContext.filePath}:${componentContext.lineNumber}]
1566
+
1567
+ `;
1568
+ }
1569
+ fullMessage += message;
1570
+ await this.session.send(fullMessage);
1571
+ for await (const event of this.session.stream()) {
1572
+ yield event;
1573
+ }
1574
+ }
1575
+ /**
1576
+ * Get the session ID
1577
+ */
1578
+ getSessionId() {
1579
+ return this.session.sessionId;
1580
+ }
1581
+ /**
1582
+ * Close the session
1583
+ */
1584
+ close() {
1585
+ this.session.close();
1586
+ }
1587
+ /**
1588
+ * Async disposal support
1589
+ */
1590
+ async [Symbol.asyncDispose]() {
1591
+ await this.session[Symbol.asyncDispose]();
1592
+ }
1593
+ }
1594
+ class AgentSessionStore {
1595
+ // 5 minutes
1596
+ constructor() {
1597
+ this.sessions = /* @__PURE__ */ new Map();
1598
+ this.SESSION_TIMEOUT = 1e3 * 60 * 30;
1599
+ this.CLEANUP_INTERVAL = 1e3 * 60 * 5;
1600
+ this.startCleanupTimer();
1601
+ }
1602
+ /**
1603
+ * Create or resume a session
1604
+ */
1605
+ async createSession(config) {
1606
+ const sessionId = config.sessionId || this.generateSessionId();
1607
+ const existing = this.sessions.get(sessionId);
1608
+ if (existing) {
1609
+ existing.lastActive = Date.now();
1610
+ existing.threadId = config.threadId;
1611
+ return existing;
1612
+ }
1613
+ const agent = await AIEditorAgent.create({
1614
+ apiKey: config.apiKey,
1615
+ projectRoot: config.projectRoot,
1616
+ sessionId: config.sessionId,
1617
+ allowedTools: config.allowedTools,
1618
+ disallowedTools: config.disallowedTools
1619
+ });
1620
+ const session = {
1621
+ sessionId,
1622
+ threadId: config.threadId,
1623
+ agent,
1624
+ createdAt: Date.now(),
1625
+ lastActive: Date.now(),
1626
+ isLocked: false
1627
+ };
1628
+ this.sessions.set(sessionId, session);
1629
+ return session;
1630
+ }
1631
+ /**
1632
+ * Get a session by ID
1633
+ */
1634
+ getSession(sessionId) {
1635
+ const session = this.sessions.get(sessionId);
1636
+ if (session) {
1637
+ session.lastActive = Date.now();
1638
+ }
1639
+ return session;
1640
+ }
1641
+ /**
1642
+ * Acquire a lock on a session to prevent concurrent edits
1643
+ * Returns true if lock was acquired, false if already locked
1644
+ */
1645
+ acquireLock(sessionId) {
1646
+ const session = this.sessions.get(sessionId);
1647
+ if (!session) {
1648
+ throw new Error(`Session ${sessionId} not found`);
1649
+ }
1650
+ if (session.isLocked) {
1651
+ return false;
1652
+ }
1653
+ session.isLocked = true;
1654
+ session.lastActive = Date.now();
1655
+ return true;
1656
+ }
1657
+ /**
1658
+ * Release a lock on a session
1659
+ */
1660
+ releaseLock(sessionId) {
1661
+ const session = this.sessions.get(sessionId);
1662
+ if (session) {
1663
+ session.isLocked = false;
1664
+ session.lastActive = Date.now();
1665
+ }
1666
+ }
1667
+ /**
1668
+ * Check if any session is currently locked (to prevent concurrent edits)
1669
+ */
1670
+ hasActiveLock() {
1671
+ for (const session of this.sessions.values()) {
1672
+ if (session.isLocked) {
1673
+ return true;
1674
+ }
1675
+ }
1676
+ return false;
1677
+ }
1678
+ /**
1679
+ * Get all active sessions for a thread
1680
+ */
1681
+ getSessionsByThread(threadId) {
1682
+ const sessions = [];
1683
+ for (const session of this.sessions.values()) {
1684
+ if (session.threadId === threadId) {
1685
+ sessions.push(session);
1686
+ }
1687
+ }
1688
+ return sessions;
1689
+ }
1690
+ /**
1691
+ * Delete a session
1692
+ */
1693
+ deleteSession(sessionId) {
1694
+ const session = this.sessions.get(sessionId);
1695
+ if (session) {
1696
+ session.agent.close();
1697
+ this.sessions.delete(sessionId);
1698
+ }
1699
+ }
1700
+ /**
1701
+ * Delete all sessions for a thread
1702
+ */
1703
+ deleteSessionsByThread(threadId) {
1704
+ for (const [sessionId, session] of this.sessions.entries()) {
1705
+ if (session.threadId === threadId) {
1706
+ session.agent.close();
1707
+ this.sessions.delete(sessionId);
1708
+ }
1709
+ }
1710
+ }
1711
+ /**
1712
+ * Get all active sessions
1713
+ */
1714
+ getAllSessions() {
1715
+ return Array.from(this.sessions.values());
1716
+ }
1717
+ /**
1718
+ * Clean up expired sessions
1719
+ */
1720
+ cleanupExpiredSessions() {
1721
+ const now = Date.now();
1722
+ const expiredSessions = [];
1723
+ for (const [sessionId, session] of this.sessions.entries()) {
1724
+ const inactiveTime = now - session.lastActive;
1725
+ if (inactiveTime > this.SESSION_TIMEOUT && !session.isLocked) {
1726
+ expiredSessions.push(sessionId);
1727
+ }
1728
+ }
1729
+ for (const sessionId of expiredSessions) {
1730
+ console.log(
1731
+ `[AgentSessionStore] Cleaning up expired session: ${sessionId}`
1732
+ );
1733
+ this.deleteSession(sessionId);
1734
+ }
1735
+ if (expiredSessions.length > 0) {
1736
+ console.log(
1737
+ `[AgentSessionStore] Cleaned up ${expiredSessions.length} expired sessions`
1738
+ );
1739
+ }
1740
+ }
1741
+ /**
1742
+ * Start periodic cleanup timer
1743
+ */
1744
+ startCleanupTimer() {
1745
+ setInterval(() => {
1746
+ this.cleanupExpiredSessions();
1747
+ }, this.CLEANUP_INTERVAL);
1748
+ }
1749
+ /**
1750
+ * Generate a unique session ID
1751
+ */
1752
+ generateSessionId() {
1753
+ return `session-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
1754
+ }
1755
+ /**
1756
+ * Get statistics about the session store
1757
+ */
1758
+ getStats() {
1759
+ return {
1760
+ totalSessions: this.sessions.size,
1761
+ lockedSessions: Array.from(this.sessions.values()).filter(
1762
+ (s) => s.isLocked
1763
+ ).length,
1764
+ sessions: Array.from(this.sessions.values()).map((s) => ({
1765
+ sessionId: s.sessionId,
1766
+ threadId: s.threadId,
1767
+ isLocked: s.isLocked,
1768
+ ageMinutes: Math.floor((Date.now() - s.createdAt) / 1e3 / 60),
1769
+ inactiveMinutes: Math.floor((Date.now() - s.lastActive) / 1e3 / 60)
1770
+ }))
1771
+ };
1772
+ }
1773
+ }
1774
+ let sessionStore = null;
1775
+ function getSessionStore() {
1776
+ if (!sessionStore) {
1777
+ sessionStore = new AgentSessionStore();
1778
+ }
1779
+ return sessionStore;
1780
+ }
1781
+ async function handleChat(req) {
1782
+ const devModeError = validateDevMode();
1783
+ if (devModeError) return devModeError;
1784
+ try {
1785
+ const body = await req.json();
1786
+ const { sessionId, threadId, message, componentContext } = body;
1787
+ if (!threadId || !message) {
1788
+ return NextResponse.json(
1789
+ { error: "threadId and message are required" },
1790
+ { status: 400 }
1791
+ );
1792
+ }
1793
+ const apiKey = process.env.ANTHROPIC_API_KEY;
1794
+ if (!apiKey) {
1795
+ return NextResponse.json(
1796
+ { error: "ANTHROPIC_API_KEY not configured" },
1797
+ { status: 500 }
1798
+ );
1799
+ }
1800
+ const sessionStore2 = getSessionStore();
1801
+ const projectRoot = process.cwd();
1802
+ const session = await sessionStore2.createSession({
1803
+ sessionId,
1804
+ threadId,
1805
+ apiKey,
1806
+ projectRoot
1807
+ });
1808
+ if (!sessionStore2.acquireLock(session.sessionId)) {
1809
+ return NextResponse.json(
1810
+ {
1811
+ error: "Another operation is in progress. Please wait for it to complete."
1812
+ },
1813
+ { status: 409 }
1814
+ );
1815
+ }
1816
+ const encoder = new TextEncoder();
1817
+ const stream = new ReadableStream({
1818
+ async start(controller) {
1819
+ try {
1820
+ controller.enqueue(
1821
+ encoder.encode(
1822
+ `data: ${JSON.stringify({ type: "connected", sessionId: session.sessionId })}
1823
+
1824
+ `
1825
+ )
1826
+ );
1827
+ for await (const event of session.agent.sendMessage(
1828
+ message,
1829
+ componentContext
1830
+ )) {
1831
+ console.log("[AGENT EVENT]", event.type, JSON.stringify(event, null, 2));
1832
+ controller.enqueue(
1833
+ encoder.encode(`data: ${JSON.stringify(event)}
1834
+
1835
+ `)
1836
+ );
1837
+ }
1838
+ controller.enqueue(
1839
+ encoder.encode(`data: ${JSON.stringify({ type: "done" })}
1840
+
1841
+ `)
1842
+ );
1843
+ controller.close();
1844
+ } catch (error) {
1845
+ console.error("[handleChat] Error:", error);
1846
+ controller.enqueue(
1847
+ encoder.encode(
1848
+ `data: ${JSON.stringify({ type: "error", error: error.message || String(error) })}
1849
+
1850
+ `
1851
+ )
1852
+ );
1853
+ controller.close();
1854
+ } finally {
1855
+ sessionStore2.releaseLock(session.sessionId);
1856
+ }
1857
+ }
1858
+ });
1859
+ return new Response(stream, {
1860
+ headers: {
1861
+ "Content-Type": "text/event-stream",
1862
+ "Cache-Control": "no-cache",
1863
+ Connection: "keep-alive"
1864
+ }
1865
+ });
1866
+ } catch (error) {
1867
+ console.error("[handleChat] Request error:", error);
1868
+ return NextResponse.json(
1869
+ { error: String(error) },
1870
+ { status: 500 }
1871
+ );
1872
+ }
1873
+ }
1464
1874
  async function handleAIEditorRequest(req, context) {
1465
1875
  const { path: path2 } = await context.params;
1466
1876
  const endpoint = path2[0];
@@ -1487,12 +1897,79 @@ async function handleAIEditorRequest(req, context) {
1487
1897
  case "suggestions":
1488
1898
  if (method === "GET") return handleSuggestions(req);
1489
1899
  break;
1900
+ case "chat":
1901
+ if (method === "POST") return handleChat(req);
1902
+ break;
1490
1903
  }
1491
1904
  return NextResponse.json(
1492
1905
  { error: `Unknown endpoint: ${endpoint}` },
1493
1906
  { status: 404 }
1494
1907
  );
1495
1908
  }
1909
+ const STORAGE_DIR = ".ai-editor";
1910
+ const STORAGE_FILE = "comments.json";
1911
+ function getProjectRoot() {
1912
+ return process.cwd();
1913
+ }
1914
+ function getStoragePath() {
1915
+ return join(getProjectRoot(), STORAGE_DIR, STORAGE_FILE);
1916
+ }
1917
+ async function ensureStorageDir() {
1918
+ const dir = join(getProjectRoot(), STORAGE_DIR);
1919
+ if (!existsSync(dir)) {
1920
+ await mkdir(dir, { recursive: true });
1921
+ }
1922
+ }
1923
+ async function handleCommentsRequest(request) {
1924
+ if (process.env.NODE_ENV !== "development") {
1925
+ return NextResponse.json(
1926
+ { error: "Comments API only available in development" },
1927
+ { status: 403 }
1928
+ );
1929
+ }
1930
+ try {
1931
+ if (request.method === "GET") {
1932
+ const storagePath = getStoragePath();
1933
+ if (!existsSync(storagePath)) {
1934
+ return NextResponse.json({ comments: [] });
1935
+ }
1936
+ const data = await readFile(storagePath, "utf-8");
1937
+ const comments = JSON.parse(data);
1938
+ return NextResponse.json({ comments });
1939
+ }
1940
+ if (request.method === "POST") {
1941
+ const body = await request.json();
1942
+ const { action } = body;
1943
+ if (action === "save") {
1944
+ const { comments } = body;
1945
+ await ensureStorageDir();
1946
+ const storagePath = getStoragePath();
1947
+ await writeFile(storagePath, JSON.stringify(comments, null, 2), "utf-8");
1948
+ return NextResponse.json({ success: true });
1949
+ }
1950
+ if (action === "clear") {
1951
+ await ensureStorageDir();
1952
+ const storagePath = getStoragePath();
1953
+ await writeFile(storagePath, JSON.stringify([]), "utf-8");
1954
+ return NextResponse.json({ success: true });
1955
+ }
1956
+ return NextResponse.json(
1957
+ { error: "Invalid action" },
1958
+ { status: 400 }
1959
+ );
1960
+ }
1961
+ return NextResponse.json(
1962
+ { error: "Method not allowed" },
1963
+ { status: 405 }
1964
+ );
1965
+ } catch (error) {
1966
+ console.error("Comment storage error:", error);
1967
+ return NextResponse.json(
1968
+ { error: "Internal server error" },
1969
+ { status: 500 }
1970
+ );
1971
+ }
1972
+ }
1496
1973
  export {
1497
1974
  handleEdit as a,
1498
1975
  handleRead as b,
@@ -1502,21 +1979,23 @@ export {
1502
1979
  handleValidateSession as f,
1503
1980
  handleSuggestions as g,
1504
1981
  handleAIEditorRequest as h,
1505
- isPathSecure as i,
1506
- fileExists$1 as j,
1507
- extractComponentName as k,
1508
- validateGeneratedCode as l,
1509
- findTargetElement as m,
1982
+ handleCommentsRequest as i,
1983
+ isPathSecure as j,
1984
+ fileExists$1 as k,
1985
+ extractComponentName as l,
1986
+ validateGeneratedCode as m,
1510
1987
  normalizePath as n,
1511
- getJSXMemberName as o,
1988
+ findTargetElement as o,
1512
1989
  parseFile as p,
1513
- getAttributeValue as q,
1990
+ getJSXMemberName as q,
1514
1991
  resolveFilePath as r,
1515
1992
  scoreElementMatch as s,
1516
- parseDebugStack as t,
1517
- parseDebugStackFrames as u,
1993
+ getAttributeValue as t,
1994
+ parseDebugStack as u,
1518
1995
  validateDevMode as v,
1519
- resolveOriginalPosition as w,
1520
- getOriginalPositionFromDebugStack as x
1996
+ extractComponentNameFromStack as w,
1997
+ parseDebugStackFrames as x,
1998
+ resolveOriginalPosition as y,
1999
+ getOriginalPositionFromDebugStack as z
1521
2000
  };
1522
- //# sourceMappingURL=index-3OMXRwpD.js.map
2001
+ //# sourceMappingURL=comments-D3m0RsOO.js.map