@open-mercato/ui 0.5.1-develop.3036.f02c281f23 → 0.5.1-develop.3045.b4b3320cc2

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 (148) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +2 -1
  3. package/__integration__/TC-AI-UI-003-aichat-registry.spec.tsx +204 -0
  4. package/dist/ai/AiAssistantLauncher.js +596 -0
  5. package/dist/ai/AiAssistantLauncher.js.map +7 -0
  6. package/dist/ai/AiChat.js +1092 -0
  7. package/dist/ai/AiChat.js.map +7 -0
  8. package/dist/ai/AiChatSessions.js +297 -0
  9. package/dist/ai/AiChatSessions.js.map +7 -0
  10. package/dist/ai/AiDock.js +347 -0
  11. package/dist/ai/AiDock.js.map +7 -0
  12. package/dist/ai/AiMessageContent.js +369 -0
  13. package/dist/ai/AiMessageContent.js.map +7 -0
  14. package/dist/ai/ChatPaneTabs.js +251 -0
  15. package/dist/ai/ChatPaneTabs.js.map +7 -0
  16. package/dist/ai/index.js +115 -0
  17. package/dist/ai/index.js.map +7 -0
  18. package/dist/ai/parts/ConfirmationCard.js +211 -0
  19. package/dist/ai/parts/ConfirmationCard.js.map +7 -0
  20. package/dist/ai/parts/FieldDiffCard.js +119 -0
  21. package/dist/ai/parts/FieldDiffCard.js.map +7 -0
  22. package/dist/ai/parts/MutationPreviewCard.js +224 -0
  23. package/dist/ai/parts/MutationPreviewCard.js.map +7 -0
  24. package/dist/ai/parts/MutationResultCard.js +240 -0
  25. package/dist/ai/parts/MutationResultCard.js.map +7 -0
  26. package/dist/ai/parts/approval-cards-map.js +15 -0
  27. package/dist/ai/parts/approval-cards-map.js.map +7 -0
  28. package/dist/ai/parts/index.js +24 -0
  29. package/dist/ai/parts/index.js.map +7 -0
  30. package/dist/ai/parts/pending-action-api.js +60 -0
  31. package/dist/ai/parts/pending-action-api.js.map +7 -0
  32. package/dist/ai/parts/types.js +1 -0
  33. package/dist/ai/parts/types.js.map +7 -0
  34. package/dist/ai/parts/useAiPendingActionPolling.js +126 -0
  35. package/dist/ai/parts/useAiPendingActionPolling.js.map +7 -0
  36. package/dist/ai/records/ActivityCard.js +83 -0
  37. package/dist/ai/records/ActivityCard.js.map +7 -0
  38. package/dist/ai/records/CompanyCard.js +81 -0
  39. package/dist/ai/records/CompanyCard.js.map +7 -0
  40. package/dist/ai/records/DealCard.js +76 -0
  41. package/dist/ai/records/DealCard.js.map +7 -0
  42. package/dist/ai/records/PersonCard.js +68 -0
  43. package/dist/ai/records/PersonCard.js.map +7 -0
  44. package/dist/ai/records/ProductCard.js +68 -0
  45. package/dist/ai/records/ProductCard.js.map +7 -0
  46. package/dist/ai/records/RecordCard.js +29 -0
  47. package/dist/ai/records/RecordCard.js.map +7 -0
  48. package/dist/ai/records/RecordCardShell.js +103 -0
  49. package/dist/ai/records/RecordCardShell.js.map +7 -0
  50. package/dist/ai/records/index.js +31 -0
  51. package/dist/ai/records/index.js.map +7 -0
  52. package/dist/ai/records/registry.js +51 -0
  53. package/dist/ai/records/registry.js.map +7 -0
  54. package/dist/ai/records/types.js +1 -0
  55. package/dist/ai/records/types.js.map +7 -0
  56. package/dist/ai/ui-part-registry.js +112 -0
  57. package/dist/ai/ui-part-registry.js.map +7 -0
  58. package/dist/ai/ui-part-slots.js +14 -0
  59. package/dist/ai/ui-part-slots.js.map +7 -0
  60. package/dist/ai/ui-parts/pending-phase3-placeholder.js +35 -0
  61. package/dist/ai/ui-parts/pending-phase3-placeholder.js.map +7 -0
  62. package/dist/ai/upload-adapter.js +256 -0
  63. package/dist/ai/upload-adapter.js.map +7 -0
  64. package/dist/ai/useAiChat.js +549 -0
  65. package/dist/ai/useAiChat.js.map +7 -0
  66. package/dist/ai/useAiChatUpload.js +127 -0
  67. package/dist/ai/useAiChatUpload.js.map +7 -0
  68. package/dist/ai/useAiShortcuts.js +43 -0
  69. package/dist/ai/useAiShortcuts.js.map +7 -0
  70. package/dist/backend/AppShell.js +8 -4
  71. package/dist/backend/AppShell.js.map +2 -2
  72. package/dist/backend/BackendChromeProvider.js +2 -0
  73. package/dist/backend/BackendChromeProvider.js.map +2 -2
  74. package/dist/backend/DataTable.js +19 -2
  75. package/dist/backend/DataTable.js.map +2 -2
  76. package/dist/backend/FilterBar.js +19 -15
  77. package/dist/backend/FilterBar.js.map +2 -2
  78. package/dist/backend/dashboard/DashboardScreen.js +31 -3
  79. package/dist/backend/dashboard/DashboardScreen.js.map +2 -2
  80. package/dist/backend/injection/spotIds.js +6 -0
  81. package/dist/backend/injection/spotIds.js.map +2 -2
  82. package/dist/backend/notifications/useNotificationEffect.js +38 -2
  83. package/dist/backend/notifications/useNotificationEffect.js.map +2 -2
  84. package/dist/index.js +1 -0
  85. package/dist/index.js.map +2 -2
  86. package/jest.config.cjs +7 -1
  87. package/jest.markdown-mock.tsx +7 -0
  88. package/package.json +10 -4
  89. package/src/ai/AiAssistantLauncher.tsx +805 -0
  90. package/src/ai/AiChat.tsx +1483 -0
  91. package/src/ai/AiChatSessions.tsx +429 -0
  92. package/src/ai/AiDock.tsx +505 -0
  93. package/src/ai/AiMessageContent.tsx +515 -0
  94. package/src/ai/ChatPaneTabs.tsx +310 -0
  95. package/src/ai/__tests__/AiChat.conversation.test.tsx +160 -0
  96. package/src/ai/__tests__/AiChat.debug.test.tsx +152 -0
  97. package/src/ai/__tests__/AiChat.registry.test.tsx +213 -0
  98. package/src/ai/__tests__/AiChat.test.tsx +257 -0
  99. package/src/ai/__tests__/AiDock.test.tsx +124 -0
  100. package/src/ai/__tests__/AiMessageContent.test.ts +111 -0
  101. package/src/ai/__tests__/ui-part-registry.test.ts +199 -0
  102. package/src/ai/__tests__/ui-part-slots.test.ts +43 -0
  103. package/src/ai/__tests__/upload-adapter.test.ts +213 -0
  104. package/src/ai/__tests__/useAiChatUpload.test.tsx +163 -0
  105. package/src/ai/__tests__/useAiShortcuts.test.tsx +100 -0
  106. package/src/ai/index.ts +125 -0
  107. package/src/ai/parts/ConfirmationCard.tsx +310 -0
  108. package/src/ai/parts/FieldDiffCard.tsx +173 -0
  109. package/src/ai/parts/MutationPreviewCard.tsx +302 -0
  110. package/src/ai/parts/MutationResultCard.tsx +360 -0
  111. package/src/ai/parts/__tests__/ConfirmationCard.test.tsx +169 -0
  112. package/src/ai/parts/__tests__/FieldDiffCard.test.tsx +74 -0
  113. package/src/ai/parts/__tests__/MutationPreviewCard.test.tsx +177 -0
  114. package/src/ai/parts/__tests__/MutationResultCard.test.tsx +127 -0
  115. package/src/ai/parts/__tests__/useAiPendingActionPolling.test.tsx +151 -0
  116. package/src/ai/parts/approval-cards-map.ts +24 -0
  117. package/src/ai/parts/index.ts +27 -0
  118. package/src/ai/parts/pending-action-api.ts +123 -0
  119. package/src/ai/parts/types.ts +84 -0
  120. package/src/ai/parts/useAiPendingActionPolling.ts +210 -0
  121. package/src/ai/records/ActivityCard.tsx +102 -0
  122. package/src/ai/records/CompanyCard.tsx +89 -0
  123. package/src/ai/records/DealCard.tsx +85 -0
  124. package/src/ai/records/PersonCard.tsx +77 -0
  125. package/src/ai/records/ProductCard.tsx +83 -0
  126. package/src/ai/records/RecordCard.tsx +37 -0
  127. package/src/ai/records/RecordCardShell.tsx +169 -0
  128. package/src/ai/records/index.ts +30 -0
  129. package/src/ai/records/registry.tsx +80 -0
  130. package/src/ai/records/types.ts +90 -0
  131. package/src/ai/ui-part-registry.ts +233 -0
  132. package/src/ai/ui-part-slots.ts +32 -0
  133. package/src/ai/ui-parts/pending-phase3-placeholder.tsx +50 -0
  134. package/src/ai/upload-adapter.ts +421 -0
  135. package/src/ai/useAiChat.ts +865 -0
  136. package/src/ai/useAiChatUpload.ts +180 -0
  137. package/src/ai/useAiShortcuts.ts +79 -0
  138. package/src/backend/AppShell.tsx +12 -5
  139. package/src/backend/BackendChromeProvider.tsx +2 -0
  140. package/src/backend/DataTable.tsx +20 -1
  141. package/src/backend/FilterBar.tsx +26 -13
  142. package/src/backend/__tests__/BackendChromeProvider.test.tsx +45 -0
  143. package/src/backend/dashboard/DashboardScreen.tsx +38 -3
  144. package/src/backend/dashboard/__tests__/DashboardScreen.test.tsx +24 -1
  145. package/src/backend/injection/spotIds.ts +6 -0
  146. package/src/backend/notifications/__tests__/useNotificationEffect.test.tsx +77 -0
  147. package/src/backend/notifications/useNotificationEffect.ts +47 -2
  148. package/src/index.ts +1 -0
@@ -0,0 +1,251 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Clock, Pencil, Plus, X } from "lucide-react";
5
+ import { IconButton } from "../primitives/icon-button.js";
6
+ import { cn } from "@open-mercato/shared/lib/utils";
7
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
8
+ import {
9
+ defaultSessionLabel,
10
+ useAiChatSessions
11
+ } from "./AiChatSessions.js";
12
+ function ChatPaneTabs({ agentId, className }) {
13
+ const t = useT();
14
+ const sessions = useAiChatSessions();
15
+ const open = sessions.getOpenSessions(agentId);
16
+ const closed = sessions.getClosedSessions(agentId);
17
+ const active = sessions.getActiveSession(agentId);
18
+ const [historyOpen, setHistoryOpen] = React.useState(false);
19
+ const [renamingId, setRenamingId] = React.useState(null);
20
+ const [draftName, setDraftName] = React.useState("");
21
+ const startRename = (session) => {
22
+ setRenamingId(session.id);
23
+ setDraftName(session.name ?? "");
24
+ };
25
+ const commitRename = () => {
26
+ if (!renamingId) return;
27
+ sessions.renameSession(renamingId, draftName);
28
+ setRenamingId(null);
29
+ };
30
+ const cancelRename = () => {
31
+ setRenamingId(null);
32
+ setDraftName("");
33
+ };
34
+ return (
35
+ // Outer wrapper does NOT scroll; only the inner tabs row does. The
36
+ // `+` button and the history dropdown live OUTSIDE that scroll area
37
+ // so the dropdown's absolute positioning isn't clipped by the
38
+ // strip's `overflow-x-auto` (which CSS resolves to `overflow-y:auto`
39
+ // too once one axis is non-`visible`, making any absolutely-
40
+ // positioned child get cut off at the strip's bottom edge).
41
+ /* @__PURE__ */ jsxs(
42
+ "div",
43
+ {
44
+ className: cn("flex items-center gap-1 px-2 pt-2 text-sm", className),
45
+ "data-ai-chat-tabs": "",
46
+ role: "tablist",
47
+ "aria-label": "Chat sessions",
48
+ children: [
49
+ /* @__PURE__ */ jsx(
50
+ "div",
51
+ {
52
+ className: "flex min-w-0 flex-1 items-center gap-1 overflow-x-auto",
53
+ "data-ai-chat-tabs-scroll": "",
54
+ children: open.length === 0 ? /* @__PURE__ */ jsx("span", { className: "px-2 py-1 text-xs text-muted-foreground", "data-ai-chat-tabs-empty": "", children: t("ai_assistant.chat.tabs.noSessions", "No sessions") }) : open.map((session) => {
55
+ const isActive = active?.id === session.id;
56
+ const isRenaming = renamingId === session.id;
57
+ const label = defaultSessionLabel(session);
58
+ return /* @__PURE__ */ jsxs(
59
+ "div",
60
+ {
61
+ role: "tab",
62
+ "aria-selected": isActive,
63
+ "data-ai-chat-tab-id": session.id,
64
+ "data-active": isActive ? "true" : "false",
65
+ className: cn(
66
+ "group flex max-w-[12rem] shrink-0 items-center gap-1 rounded-t-md border-b-2 px-2 py-1",
67
+ isActive ? "border-primary bg-background text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
68
+ ),
69
+ children: [
70
+ isRenaming ? /* @__PURE__ */ jsx(
71
+ "input",
72
+ {
73
+ autoFocus: true,
74
+ type: "text",
75
+ value: draftName,
76
+ onChange: (event) => setDraftName(event.target.value),
77
+ onBlur: commitRename,
78
+ onKeyDown: (event) => {
79
+ if (event.key === "Enter") {
80
+ event.preventDefault();
81
+ commitRename();
82
+ } else if (event.key === "Escape") {
83
+ event.preventDefault();
84
+ cancelRename();
85
+ }
86
+ },
87
+ className: "h-6 max-w-[10rem] rounded border border-input bg-background px-1 text-xs outline-none focus:ring-2 focus:ring-ring/40",
88
+ "data-ai-chat-tab-rename-input": ""
89
+ }
90
+ ) : /* @__PURE__ */ jsx(
91
+ "button",
92
+ {
93
+ type: "button",
94
+ onClick: () => sessions.setActiveSession(session.id),
95
+ onDoubleClick: () => startRename(session),
96
+ title: label,
97
+ className: "truncate text-xs font-medium",
98
+ children: label
99
+ }
100
+ ),
101
+ !isRenaming && isActive ? /* @__PURE__ */ jsx(
102
+ IconButton,
103
+ {
104
+ type: "button",
105
+ variant: "ghost",
106
+ size: "xs",
107
+ "aria-label": t("ai_assistant.chat.tabs.rename", "Rename"),
108
+ title: t("ai_assistant.chat.tabs.rename", "Rename"),
109
+ className: "opacity-60 hover:opacity-100",
110
+ onClick: () => startRename(session),
111
+ "data-ai-chat-tab-rename": "",
112
+ children: /* @__PURE__ */ jsx(Pencil, { className: "size-3" })
113
+ }
114
+ ) : null,
115
+ /* @__PURE__ */ jsx(
116
+ IconButton,
117
+ {
118
+ type: "button",
119
+ variant: "ghost",
120
+ size: "xs",
121
+ "aria-label": t("ai_assistant.chat.tabs.close", "Close"),
122
+ title: t("ai_assistant.chat.tabs.close", "Close"),
123
+ className: cn(
124
+ "transition-opacity",
125
+ isActive ? "opacity-60 hover:opacity-100" : "opacity-50 hover:opacity-100"
126
+ ),
127
+ "data-active": isActive ? "true" : "false",
128
+ onMouseDown: (event) => {
129
+ event.stopPropagation();
130
+ },
131
+ onClick: (event) => {
132
+ event.preventDefault();
133
+ event.stopPropagation();
134
+ sessions.closeSession(session.id);
135
+ },
136
+ "data-ai-chat-tab-close": "",
137
+ children: /* @__PURE__ */ jsx(X, { className: "size-3" })
138
+ }
139
+ )
140
+ ]
141
+ },
142
+ session.id
143
+ );
144
+ })
145
+ }
146
+ ),
147
+ /* @__PURE__ */ jsx(
148
+ IconButton,
149
+ {
150
+ type: "button",
151
+ variant: "ghost",
152
+ size: "sm",
153
+ "aria-label": t("ai_assistant.chat.tabs.newSession", "New session"),
154
+ title: t("ai_assistant.chat.tabs.newSession", "New session"),
155
+ onClick: () => sessions.createSession(agentId),
156
+ "data-ai-chat-new-session": "",
157
+ className: "shrink-0",
158
+ children: /* @__PURE__ */ jsx(Plus, { className: "size-4" })
159
+ }
160
+ ),
161
+ /* @__PURE__ */ jsx(
162
+ HistoryDropdown,
163
+ {
164
+ open: historyOpen,
165
+ onOpenChange: setHistoryOpen,
166
+ closed,
167
+ onPick: (sessionId) => {
168
+ sessions.reopenSession(sessionId);
169
+ setHistoryOpen(false);
170
+ }
171
+ }
172
+ )
173
+ ]
174
+ }
175
+ )
176
+ );
177
+ }
178
+ function HistoryDropdown({ open, onOpenChange, closed, onPick }) {
179
+ const t = useT();
180
+ const containerRef = React.useRef(null);
181
+ React.useEffect(() => {
182
+ if (!open) return;
183
+ const onDown = (event) => {
184
+ const root = containerRef.current;
185
+ if (!root) return;
186
+ if (event.target instanceof Node && root.contains(event.target)) return;
187
+ onOpenChange(false);
188
+ };
189
+ const onKey = (event) => {
190
+ if (event.key === "Escape") onOpenChange(false);
191
+ };
192
+ window.addEventListener("mousedown", onDown);
193
+ window.addEventListener("touchstart", onDown, { passive: true });
194
+ window.addEventListener("keydown", onKey);
195
+ return () => {
196
+ window.removeEventListener("mousedown", onDown);
197
+ window.removeEventListener("touchstart", onDown);
198
+ window.removeEventListener("keydown", onKey);
199
+ };
200
+ }, [open, onOpenChange]);
201
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "relative shrink-0", children: [
202
+ /* @__PURE__ */ jsx(
203
+ IconButton,
204
+ {
205
+ type: "button",
206
+ variant: "ghost",
207
+ size: "sm",
208
+ "aria-label": t("ai_assistant.chat.tabs.recentSessions", "Recent sessions"),
209
+ title: t("ai_assistant.chat.tabs.recentSessions", "Recent sessions"),
210
+ "data-ai-chat-history-trigger": "",
211
+ "aria-expanded": open,
212
+ onClick: () => onOpenChange(!open),
213
+ children: /* @__PURE__ */ jsx(Clock, { className: "size-4" })
214
+ }
215
+ ),
216
+ open ? /* @__PURE__ */ jsxs(
217
+ "div",
218
+ {
219
+ className: "absolute right-0 top-full mt-2 w-72 max-h-[60vh] overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
220
+ style: { zIndex: 2147483e3 },
221
+ "data-ai-chat-history-panel": "",
222
+ role: "menu",
223
+ children: [
224
+ /* @__PURE__ */ jsx("div", { className: "px-3 pt-2 pb-1 text-xs font-medium uppercase tracking-wide text-muted-foreground", children: t("ai_assistant.chat.tabs.recentSessions", "Recent sessions") }),
225
+ closed.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-3 py-3 text-xs text-muted-foreground", "data-ai-chat-history-empty": "", children: t("ai_assistant.chat.tabs.noPreviousSessions", "No previous sessions yet.") }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", children: closed.map((session) => /* @__PURE__ */ jsxs(
226
+ "button",
227
+ {
228
+ type: "button",
229
+ role: "menuitem",
230
+ onClick: () => onPick(session.id),
231
+ className: "flex w-full items-center justify-between gap-2 rounded-sm px-2 py-2 text-left text-sm hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground focus-visible:outline-none",
232
+ "data-ai-chat-history-item": session.id,
233
+ children: [
234
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: defaultSessionLabel(session) }),
235
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10px] text-muted-foreground", children: new Date(session.lastUsedAt).toLocaleDateString(void 0, {
236
+ month: "short",
237
+ day: "numeric"
238
+ }) })
239
+ ]
240
+ },
241
+ session.id
242
+ )) })
243
+ ]
244
+ }
245
+ ) : null
246
+ ] });
247
+ }
248
+ export {
249
+ ChatPaneTabs
250
+ };
251
+ //# sourceMappingURL=ChatPaneTabs.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/ai/ChatPaneTabs.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\n/**\n * Tab strip rendered above an `<AiChat>` surface (dock panel, dialog sheet).\n *\n * Each tab is a session for the same agent. The strip provides:\n * - tab switching (click)\n * - inline rename (double-click on the active tab title or pencil icon)\n * - close (X on hover)\n * - new session (`+`)\n * - history dropdown (clock icon \u2192 recent closed sessions; click reopens)\n *\n * The component is purely UI \u2014 state lives in `AiChatSessionsProvider`.\n */\n\nimport * as React from 'react'\nimport { Clock, Pencil, Plus, X } from 'lucide-react'\nimport { IconButton } from '../primitives/icon-button'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n defaultSessionLabel,\n useAiChatSessions,\n type AiChatSession,\n} from './AiChatSessions'\n\nexport interface ChatPaneTabsProps {\n agentId: string\n className?: string\n}\n\nexport function ChatPaneTabs({ agentId, className }: ChatPaneTabsProps) {\n const t = useT()\n const sessions = useAiChatSessions()\n const open = sessions.getOpenSessions(agentId)\n const closed = sessions.getClosedSessions(agentId)\n const active = sessions.getActiveSession(agentId)\n\n const [historyOpen, setHistoryOpen] = React.useState(false)\n const [renamingId, setRenamingId] = React.useState<string | null>(null)\n const [draftName, setDraftName] = React.useState('')\n\n const startRename = (session: AiChatSession) => {\n setRenamingId(session.id)\n // Pre-fill with the current name only \u2014 never the date fallback. If\n // we pre-filled with the formatted date, blurring without typing\n // would persist that date string as the session name, and creating\n // a new tab a minute later would \"leak\" the old tab's date label\n // onto every other unnamed tab.\n setDraftName(session.name ?? '')\n }\n\n const commitRename = () => {\n if (!renamingId) return\n sessions.renameSession(renamingId, draftName)\n setRenamingId(null)\n }\n\n const cancelRename = () => {\n setRenamingId(null)\n setDraftName('')\n }\n\n return (\n // Outer wrapper does NOT scroll; only the inner tabs row does. The\n // `+` button and the history dropdown live OUTSIDE that scroll area\n // so the dropdown's absolute positioning isn't clipped by the\n // strip's `overflow-x-auto` (which CSS resolves to `overflow-y:auto`\n // too once one axis is non-`visible`, making any absolutely-\n // positioned child get cut off at the strip's bottom edge).\n <div\n className={cn('flex items-center gap-1 px-2 pt-2 text-sm', className)}\n data-ai-chat-tabs=\"\"\n role=\"tablist\"\n aria-label=\"Chat sessions\"\n >\n <div\n className=\"flex min-w-0 flex-1 items-center gap-1 overflow-x-auto\"\n data-ai-chat-tabs-scroll=\"\"\n >\n {open.length === 0 ? (\n <span className=\"px-2 py-1 text-xs text-muted-foreground\" data-ai-chat-tabs-empty=\"\">\n {t('ai_assistant.chat.tabs.noSessions', 'No sessions')}\n </span>\n ) : (\n open.map((session) => {\n const isActive = active?.id === session.id\n const isRenaming = renamingId === session.id\n const label = defaultSessionLabel(session)\n return (\n <div\n key={session.id}\n role=\"tab\"\n aria-selected={isActive}\n data-ai-chat-tab-id={session.id}\n data-active={isActive ? 'true' : 'false'}\n className={cn(\n 'group flex max-w-[12rem] shrink-0 items-center gap-1 rounded-t-md border-b-2 px-2 py-1',\n isActive\n ? 'border-primary bg-background text-foreground'\n : 'border-transparent text-muted-foreground hover:text-foreground',\n )}\n >\n {isRenaming ? (\n <input\n autoFocus\n type=\"text\"\n value={draftName}\n onChange={(event) => setDraftName(event.target.value)}\n onBlur={commitRename}\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n event.preventDefault()\n commitRename()\n } else if (event.key === 'Escape') {\n event.preventDefault()\n cancelRename()\n }\n }}\n className=\"h-6 max-w-[10rem] rounded border border-input bg-background px-1 text-xs outline-none focus:ring-2 focus:ring-ring/40\"\n data-ai-chat-tab-rename-input=\"\"\n />\n ) : (\n <button\n type=\"button\"\n onClick={() => sessions.setActiveSession(session.id)}\n onDoubleClick={() => startRename(session)}\n title={label}\n className=\"truncate text-xs font-medium\"\n >\n {label}\n </button>\n )}\n {!isRenaming && isActive ? (\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-label={t('ai_assistant.chat.tabs.rename', 'Rename')}\n title={t('ai_assistant.chat.tabs.rename', 'Rename')}\n className=\"opacity-60 hover:opacity-100\"\n onClick={() => startRename(session)}\n data-ai-chat-tab-rename=\"\"\n >\n <Pencil className=\"size-3\" />\n </IconButton>\n ) : null}\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-label={t('ai_assistant.chat.tabs.close', 'Close')}\n title={t('ai_assistant.chat.tabs.close', 'Close')}\n // Always rendered visible (a previous opacity-0 default\n // hid the X on non-hover and made the active-tab close\n // button look unreachable). Closing the very last open\n // tab is fine \u2014 `ensureSession` in the chat body's\n // effect immediately mints a fresh empty tab so the user\n // never sees an empty pane.\n className={cn(\n 'transition-opacity',\n isActive ? 'opacity-60 hover:opacity-100' : 'opacity-50 hover:opacity-100',\n )}\n data-active={isActive ? 'true' : 'false'}\n onMouseDown={(event) => {\n // Prevent the parent tab button's blur logic / focus\n // shift from racing the close click on the active tab.\n event.stopPropagation()\n }}\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n sessions.closeSession(session.id)\n }}\n data-ai-chat-tab-close=\"\"\n >\n <X className=\"size-3\" />\n </IconButton>\n </div>\n )\n })\n )}\n </div>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('ai_assistant.chat.tabs.newSession', 'New session')}\n title={t('ai_assistant.chat.tabs.newSession', 'New session')}\n onClick={() => sessions.createSession(agentId)}\n data-ai-chat-new-session=\"\"\n className=\"shrink-0\"\n >\n <Plus className=\"size-4\" />\n </IconButton>\n <HistoryDropdown\n open={historyOpen}\n onOpenChange={setHistoryOpen}\n closed={closed}\n onPick={(sessionId) => {\n sessions.reopenSession(sessionId)\n setHistoryOpen(false)\n }}\n />\n </div>\n )\n}\n\ninterface HistoryDropdownProps {\n open: boolean\n onOpenChange: (next: boolean) => void\n closed: AiChatSession[]\n onPick: (sessionId: string) => void\n}\n\n/**\n * Plain absolutely-positioned dropdown for the recent-sessions list.\n * Bypasses the Radix Popover primitive on purpose \u2014 every chat surface\n * (dock panel, customers/catalog/launcher dialog) creates its own stacking\n * context, and the Radix Portal'd PopoverContent kept ending up either\n * behind the dialog or pushed off the visible area on tall sheets. A\n * direct `position: absolute` child of the trigger button anchors the\n * dropdown to the icon, inherits the surface's stacking context, and is\n * predictable across the dock + every dialog host without z-index hacks.\n */\nfunction HistoryDropdown({ open, onOpenChange, closed, onPick }: HistoryDropdownProps) {\n const t = useT()\n const containerRef = React.useRef<HTMLDivElement | null>(null)\n\n React.useEffect(() => {\n if (!open) return\n const onDown = (event: MouseEvent | TouchEvent) => {\n const root = containerRef.current\n if (!root) return\n if (event.target instanceof Node && root.contains(event.target)) return\n onOpenChange(false)\n }\n const onKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') onOpenChange(false)\n }\n window.addEventListener('mousedown', onDown)\n window.addEventListener('touchstart', onDown, { passive: true })\n window.addEventListener('keydown', onKey)\n return () => {\n window.removeEventListener('mousedown', onDown)\n window.removeEventListener('touchstart', onDown)\n window.removeEventListener('keydown', onKey)\n }\n }, [open, onOpenChange])\n\n return (\n <div ref={containerRef} className=\"relative shrink-0\">\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('ai_assistant.chat.tabs.recentSessions', 'Recent sessions')}\n title={t('ai_assistant.chat.tabs.recentSessions', 'Recent sessions')}\n data-ai-chat-history-trigger=\"\"\n aria-expanded={open}\n onClick={() => onOpenChange(!open)}\n >\n <Clock className=\"size-4\" />\n </IconButton>\n {open ? (\n <div\n className=\"absolute right-0 top-full mt-2 w-72 max-h-[60vh] overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md\"\n // Inline z-index so the dropdown sits above any host surface\n // (chat dialog at z-[70], dock panel, modal overlays). Inline\n // beats Tailwind JIT for arbitrary high values.\n style={{ zIndex: 2147483000 }}\n data-ai-chat-history-panel=\"\"\n role=\"menu\"\n >\n <div className=\"px-3 pt-2 pb-1 text-xs font-medium uppercase tracking-wide text-muted-foreground\">\n {t('ai_assistant.chat.tabs.recentSessions', 'Recent sessions')}\n </div>\n {closed.length === 0 ? (\n <div className=\"px-3 py-3 text-xs text-muted-foreground\" data-ai-chat-history-empty=\"\">\n {t('ai_assistant.chat.tabs.noPreviousSessions', 'No previous sessions yet.')}\n </div>\n ) : (\n <div className=\"flex flex-col gap-0.5\">\n {closed.map((session) => (\n <button\n key={session.id}\n type=\"button\"\n role=\"menuitem\"\n onClick={() => onPick(session.id)}\n className=\"flex w-full items-center justify-between gap-2 rounded-sm px-2 py-2 text-left text-sm hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground focus-visible:outline-none\"\n data-ai-chat-history-item={session.id}\n >\n <span className=\"min-w-0 flex-1 truncate\">\n {defaultSessionLabel(session)}\n </span>\n <span className=\"shrink-0 text-[10px] text-muted-foreground\">\n {new Date(session.lastUsedAt).toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n })}\n </span>\n </button>\n ))}\n </div>\n )}\n </div>\n ) : null}\n </div>\n )\n}\n"],
5
+ "mappings": ";AAiFQ,cASI,YATJ;AAlER,YAAY,WAAW;AACvB,SAAS,OAAO,QAAQ,MAAM,SAAS;AACvC,SAAS,kBAAkB;AAC3B,SAAS,UAAU;AACnB,SAAS,YAAY;AACrB;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AAOA,SAAS,aAAa,EAAE,SAAS,UAAU,GAAsB;AACtE,QAAM,IAAI,KAAK;AACf,QAAM,WAAW,kBAAkB;AACnC,QAAM,OAAO,SAAS,gBAAgB,OAAO;AAC7C,QAAM,SAAS,SAAS,kBAAkB,OAAO;AACjD,QAAM,SAAS,SAAS,iBAAiB,OAAO;AAEhD,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,KAAK;AAC1D,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAwB,IAAI;AACtE,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,EAAE;AAEnD,QAAM,cAAc,CAAC,YAA2B;AAC9C,kBAAc,QAAQ,EAAE;AAMxB,iBAAa,QAAQ,QAAQ,EAAE;AAAA,EACjC;AAEA,QAAM,eAAe,MAAM;AACzB,QAAI,CAAC,WAAY;AACjB,aAAS,cAAc,YAAY,SAAS;AAC5C,kBAAc,IAAI;AAAA,EACpB;AAEA,QAAM,eAAe,MAAM;AACzB,kBAAc,IAAI;AAClB,iBAAa,EAAE;AAAA,EACjB;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOE;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,GAAG,6CAA6C,SAAS;AAAA,QACpE,qBAAkB;AAAA,QAClB,MAAK;AAAA,QACL,cAAW;AAAA,QAEX;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,4BAAyB;AAAA,cAE1B,eAAK,WAAW,IACf,oBAAC,UAAK,WAAU,2CAA0C,2BAAwB,IAC/E,YAAE,qCAAqC,aAAa,GACvD,IAEA,KAAK,IAAI,CAAC,YAAY;AACpB,sBAAM,WAAW,QAAQ,OAAO,QAAQ;AACxC,sBAAM,aAAa,eAAe,QAAQ;AAC1C,sBAAM,QAAQ,oBAAoB,OAAO;AACzC,uBACE;AAAA,kBAAC;AAAA;AAAA,oBAEC,MAAK;AAAA,oBACL,iBAAe;AAAA,oBACf,uBAAqB,QAAQ;AAAA,oBAC7B,eAAa,WAAW,SAAS;AAAA,oBACjC,WAAW;AAAA,sBACT;AAAA,sBACA,WACI,iDACA;AAAA,oBACN;AAAA,oBAEC;AAAA,mCACC;AAAA,wBAAC;AAAA;AAAA,0BACC,WAAS;AAAA,0BACT,MAAK;AAAA,0BACL,OAAO;AAAA,0BACP,UAAU,CAAC,UAAU,aAAa,MAAM,OAAO,KAAK;AAAA,0BACpD,QAAQ;AAAA,0BACR,WAAW,CAAC,UAAU;AACpB,gCAAI,MAAM,QAAQ,SAAS;AACzB,oCAAM,eAAe;AACrB,2CAAa;AAAA,4BACf,WAAW,MAAM,QAAQ,UAAU;AACjC,oCAAM,eAAe;AACrB,2CAAa;AAAA,4BACf;AAAA,0BACF;AAAA,0BACA,WAAU;AAAA,0BACV,iCAA8B;AAAA;AAAA,sBAChC,IAEA;AAAA,wBAAC;AAAA;AAAA,0BACC,MAAK;AAAA,0BACL,SAAS,MAAM,SAAS,iBAAiB,QAAQ,EAAE;AAAA,0BACnD,eAAe,MAAM,YAAY,OAAO;AAAA,0BACxC,OAAO;AAAA,0BACP,WAAU;AAAA,0BAET;AAAA;AAAA,sBACH;AAAA,sBAED,CAAC,cAAc,WACd;AAAA,wBAAC;AAAA;AAAA,0BACC,MAAK;AAAA,0BACL,SAAQ;AAAA,0BACR,MAAK;AAAA,0BACL,cAAY,EAAE,iCAAiC,QAAQ;AAAA,0BACvD,OAAO,EAAE,iCAAiC,QAAQ;AAAA,0BAClD,WAAU;AAAA,0BACV,SAAS,MAAM,YAAY,OAAO;AAAA,0BAClC,2BAAwB;AAAA,0BAExB,8BAAC,UAAO,WAAU,UAAS;AAAA;AAAA,sBAC7B,IACE;AAAA,sBACJ;AAAA,wBAAC;AAAA;AAAA,0BACC,MAAK;AAAA,0BACL,SAAQ;AAAA,0BACR,MAAK;AAAA,0BACL,cAAY,EAAE,gCAAgC,OAAO;AAAA,0BACrD,OAAO,EAAE,gCAAgC,OAAO;AAAA,0BAOhD,WAAW;AAAA,4BACT;AAAA,4BACA,WAAW,iCAAiC;AAAA,0BAC9C;AAAA,0BACA,eAAa,WAAW,SAAS;AAAA,0BACjC,aAAa,CAAC,UAAU;AAGtB,kCAAM,gBAAgB;AAAA,0BACxB;AAAA,0BACA,SAAS,CAAC,UAAU;AAClB,kCAAM,eAAe;AACrB,kCAAM,gBAAgB;AACtB,qCAAS,aAAa,QAAQ,EAAE;AAAA,0BAClC;AAAA,0BACA,0BAAuB;AAAA,0BAEvB,8BAAC,KAAE,WAAU,UAAS;AAAA;AAAA,sBACxB;AAAA;AAAA;AAAA,kBAtFK,QAAQ;AAAA,gBAuFf;AAAA,cAEJ,CAAC;AAAA;AAAA,UAEH;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,cAAY,EAAE,qCAAqC,aAAa;AAAA,cAChE,OAAO,EAAE,qCAAqC,aAAa;AAAA,cAC3D,SAAS,MAAM,SAAS,cAAc,OAAO;AAAA,cAC7C,4BAAyB;AAAA,cACzB,WAAU;AAAA,cAEV,8BAAC,QAAK,WAAU,UAAS;AAAA;AAAA,UAC3B;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAM;AAAA,cACN,cAAc;AAAA,cACd;AAAA,cACA,QAAQ,CAAC,cAAc;AACrB,yBAAS,cAAc,SAAS;AAChC,+BAAe,KAAK;AAAA,cACtB;AAAA;AAAA,UACF;AAAA;AAAA;AAAA,IACF;AAAA;AAEJ;AAmBA,SAAS,gBAAgB,EAAE,MAAM,cAAc,QAAQ,OAAO,GAAyB;AACrF,QAAM,IAAI,KAAK;AACf,QAAM,eAAe,MAAM,OAA8B,IAAI;AAE7D,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,KAAM;AACX,UAAM,SAAS,CAAC,UAAmC;AACjD,YAAM,OAAO,aAAa;AAC1B,UAAI,CAAC,KAAM;AACX,UAAI,MAAM,kBAAkB,QAAQ,KAAK,SAAS,MAAM,MAAM,EAAG;AACjE,mBAAa,KAAK;AAAA,IACpB;AACA,UAAM,QAAQ,CAAC,UAAyB;AACtC,UAAI,MAAM,QAAQ,SAAU,cAAa,KAAK;AAAA,IAChD;AACA,WAAO,iBAAiB,aAAa,MAAM;AAC3C,WAAO,iBAAiB,cAAc,QAAQ,EAAE,SAAS,KAAK,CAAC;AAC/D,WAAO,iBAAiB,WAAW,KAAK;AACxC,WAAO,MAAM;AACX,aAAO,oBAAoB,aAAa,MAAM;AAC9C,aAAO,oBAAoB,cAAc,MAAM;AAC/C,aAAO,oBAAoB,WAAW,KAAK;AAAA,IAC7C;AAAA,EACF,GAAG,CAAC,MAAM,YAAY,CAAC;AAEvB,SACE,qBAAC,SAAI,KAAK,cAAc,WAAU,qBAChC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,cAAY,EAAE,yCAAyC,iBAAiB;AAAA,QACxE,OAAO,EAAE,yCAAyC,iBAAiB;AAAA,QACnE,gCAA6B;AAAA,QAC7B,iBAAe;AAAA,QACf,SAAS,MAAM,aAAa,CAAC,IAAI;AAAA,QAEjC,8BAAC,SAAM,WAAU,UAAS;AAAA;AAAA,IAC5B;AAAA,IACC,OACC;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QAIV,OAAO,EAAE,QAAQ,UAAW;AAAA,QAC5B,8BAA2B;AAAA,QAC3B,MAAK;AAAA,QAEL;AAAA,8BAAC,SAAI,WAAU,oFACZ,YAAE,yCAAyC,iBAAiB,GAC/D;AAAA,UACC,OAAO,WAAW,IACjB,oBAAC,SAAI,WAAU,2CAA0C,8BAA2B,IACjF,YAAE,6CAA6C,2BAA2B,GAC7E,IAEA,oBAAC,SAAI,WAAU,yBACZ,iBAAO,IAAI,CAAC,YACX;AAAA,YAAC;AAAA;AAAA,cAEC,MAAK;AAAA,cACL,MAAK;AAAA,cACL,SAAS,MAAM,OAAO,QAAQ,EAAE;AAAA,cAChC,WAAU;AAAA,cACV,6BAA2B,QAAQ;AAAA,cAEnC;AAAA,oCAAC,UAAK,WAAU,2BACb,8BAAoB,OAAO,GAC9B;AAAA,gBACA,oBAAC,UAAK,WAAU,8CACb,cAAI,KAAK,QAAQ,UAAU,EAAE,mBAAmB,QAAW;AAAA,kBAC1D,OAAO;AAAA,kBACP,KAAK;AAAA,gBACP,CAAC,GACH;AAAA;AAAA;AAAA,YAfK,QAAQ;AAAA,UAgBf,CACD,GACH;AAAA;AAAA;AAAA,IAEJ,IACE;AAAA,KACN;AAEJ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,115 @@
1
+ import { AiChat } from "./AiChat.js";
2
+ import {
3
+ AiAssistantLauncher
4
+ } from "./AiAssistantLauncher.js";
5
+ import {
6
+ AiDockProvider,
7
+ useAiDock
8
+ } from "./AiDock.js";
9
+ import {
10
+ AiChatSessionsProvider,
11
+ useAiChatSessions,
12
+ defaultSessionLabel
13
+ } from "./AiChatSessions.js";
14
+ import { ChatPaneTabs } from "./ChatPaneTabs.js";
15
+ import {
16
+ useAiChat
17
+ } from "./useAiChat.js";
18
+ import {
19
+ useAiShortcuts
20
+ } from "./useAiShortcuts.js";
21
+ import {
22
+ registerAiUiPart,
23
+ resolveAiUiPart,
24
+ unregisterAiUiPart,
25
+ resetAiUiPartRegistryForTests,
26
+ listAiUiParts,
27
+ createAiUiPartRegistry,
28
+ defaultAiUiPartRegistry,
29
+ RESERVED_AI_UI_PART_IDS,
30
+ isReservedAiUiPartId
31
+ } from "./ui-part-registry.js";
32
+ import { PendingPhase3Placeholder } from "./ui-parts/pending-phase3-placeholder.js";
33
+ import {
34
+ MutationPreviewCard,
35
+ FieldDiffCard,
36
+ ConfirmationCard,
37
+ MutationResultCard,
38
+ AI_MUTATION_APPROVAL_CARDS,
39
+ useAiPendingActionPolling,
40
+ confirmPendingAction,
41
+ cancelPendingAction
42
+ } from "./parts/index.js";
43
+ import {
44
+ uploadAttachmentsForChat
45
+ } from "./upload-adapter.js";
46
+ import {
47
+ RecordCard,
48
+ DealCard,
49
+ PersonCard,
50
+ CompanyCard,
51
+ ProductCard,
52
+ ActivityCard,
53
+ RecordCardShell,
54
+ KeyValueList,
55
+ TagRow,
56
+ statusToTagVariant,
57
+ registerRecordCardUiParts,
58
+ RECORD_CARD_COMPONENT_IDS
59
+ } from "./records/index.js";
60
+ import {
61
+ AiMessageContent,
62
+ parseAiContentSegments,
63
+ RECORD_CARD_FENCE_INFO_PREFIX
64
+ } from "./AiMessageContent.js";
65
+ import {
66
+ useAiChatUpload
67
+ } from "./useAiChatUpload.js";
68
+ export {
69
+ AI_MUTATION_APPROVAL_CARDS,
70
+ ActivityCard,
71
+ AiAssistantLauncher,
72
+ AiChat,
73
+ AiChatSessionsProvider,
74
+ AiDockProvider,
75
+ AiMessageContent,
76
+ ChatPaneTabs,
77
+ CompanyCard,
78
+ ConfirmationCard,
79
+ DealCard,
80
+ FieldDiffCard,
81
+ KeyValueList,
82
+ MutationPreviewCard,
83
+ MutationResultCard,
84
+ PendingPhase3Placeholder,
85
+ PersonCard,
86
+ ProductCard,
87
+ RECORD_CARD_COMPONENT_IDS,
88
+ RECORD_CARD_FENCE_INFO_PREFIX,
89
+ RESERVED_AI_UI_PART_IDS,
90
+ RecordCard,
91
+ RecordCardShell,
92
+ TagRow,
93
+ cancelPendingAction,
94
+ confirmPendingAction,
95
+ createAiUiPartRegistry,
96
+ defaultAiUiPartRegistry,
97
+ defaultSessionLabel,
98
+ isReservedAiUiPartId,
99
+ listAiUiParts,
100
+ parseAiContentSegments,
101
+ registerAiUiPart,
102
+ registerRecordCardUiParts,
103
+ resetAiUiPartRegistryForTests,
104
+ resolveAiUiPart,
105
+ statusToTagVariant,
106
+ unregisterAiUiPart,
107
+ uploadAttachmentsForChat,
108
+ useAiChat,
109
+ useAiChatSessions,
110
+ useAiChatUpload,
111
+ useAiDock,
112
+ useAiPendingActionPolling,
113
+ useAiShortcuts
114
+ };
115
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/ai/index.ts"],
4
+ "sourcesContent": ["export { AiChat, type AiChatProps, type AiChatDebugTool, type AiChatDebugPromptSection } from './AiChat'\nexport {\n AiAssistantLauncher,\n type AiAssistantLauncherAgent,\n type AiAssistantLauncherProps,\n} from './AiAssistantLauncher'\nexport {\n AiDockProvider,\n useAiDock,\n type AiDockedAssistant,\n} from './AiDock'\nexport {\n AiChatSessionsProvider,\n useAiChatSessions,\n defaultSessionLabel,\n type AiChatSession,\n} from './AiChatSessions'\nexport { ChatPaneTabs } from './ChatPaneTabs'\nexport {\n useAiChat,\n type AiChatMessage,\n type AiChatToolCallSnapshot,\n type AiChatMessageFile,\n type AiChatErrorEnvelope,\n type UseAiChatInput,\n type UseAiChatResult,\n} from './useAiChat'\nexport {\n useAiShortcuts,\n type UseAiShortcutsOptions,\n type UseAiShortcutsResult,\n} from './useAiShortcuts'\nexport {\n registerAiUiPart,\n resolveAiUiPart,\n unregisterAiUiPart,\n resetAiUiPartRegistryForTests,\n listAiUiParts,\n createAiUiPartRegistry,\n defaultAiUiPartRegistry,\n RESERVED_AI_UI_PART_IDS,\n isReservedAiUiPartId,\n type AiUiPartComponent,\n type AiUiPartComponentId,\n type AiUiPartProps,\n type AiUiPartRegistry,\n type AiUiPartRegistryEntry,\n type CreateAiUiPartRegistryOptions,\n type ReservedAiUiPartId,\n} from './ui-part-registry'\nexport { PendingPhase3Placeholder } from './ui-parts/pending-phase3-placeholder'\nexport {\n MutationPreviewCard,\n FieldDiffCard,\n ConfirmationCard,\n MutationResultCard,\n AI_MUTATION_APPROVAL_CARDS,\n useAiPendingActionPolling,\n confirmPendingAction,\n cancelPendingAction,\n type UseAiPendingActionPollingOptions,\n type UseAiPendingActionPollingResult,\n type PendingActionMutationOk,\n type PendingActionMutationError,\n type PendingActionMutationResult,\n type AiPendingActionCardAction,\n type AiPendingActionCardStatus,\n type AiPendingActionCardFieldDiff,\n type AiPendingActionCardRecordDiff,\n type AiPendingActionCardFailedRecord,\n type AiPendingActionCardExecutionResult,\n} from './parts'\nexport {\n uploadAttachmentsForChat,\n type UploadAttachmentsForChatOptions,\n type UploadAttachmentsForChatResult,\n type UploadedAttachment,\n type UploadFailure,\n type UploadFailureReason,\n} from './upload-adapter'\nexport {\n RecordCard,\n DealCard,\n PersonCard,\n CompanyCard,\n ProductCard,\n ActivityCard,\n RecordCardShell,\n KeyValueList,\n TagRow,\n statusToTagVariant,\n type RecordCardProps,\n type RecordCardShellProps,\n type KeyValueListItem,\n type DealCardProps,\n type PersonCardProps,\n type CompanyCardProps,\n type ProductCardProps,\n type ActivityCardProps,\n type RecordCardKind,\n type RecordCardPayload,\n type RecordCardBaseProps,\n type DealRecordPayload,\n type PersonRecordPayload,\n type CompanyRecordPayload,\n type ProductRecordPayload,\n type ActivityRecordPayload,\n registerRecordCardUiParts,\n RECORD_CARD_COMPONENT_IDS,\n type RecordCardComponentId,\n} from './records'\nexport {\n AiMessageContent,\n parseAiContentSegments,\n RECORD_CARD_FENCE_INFO_PREFIX,\n type AiMessageContentSegment,\n type AiMessageContentProps,\n} from './AiMessageContent'\nexport {\n useAiChatUpload,\n type UseAiChatUploadOptions,\n type UseAiChatUploadState,\n type AiChatUploadFileState,\n type AiChatUploadFileStatus,\n} from './useAiChatUpload'\n"],
5
+ "mappings": "AAAA,SAAS,cAAqF;AAC9F;AAAA,EACE;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,OAOK;AACP;AAAA,EACE;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAQK;AACP,SAAS,gCAAgC;AACzC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAYK;AACP;AAAA,EACE;AAAA,OAMK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAiBA;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,OAKK;",
6
+ "names": []
7
+ }
@@ -0,0 +1,211 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { AlertTriangle, Loader2 } from "lucide-react";
5
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
6
+ import { Alert, AlertDescription, AlertTitle } from "../../primitives/alert.js";
7
+ import { Button } from "../../primitives/button.js";
8
+ import { Spinner } from "../../primitives/spinner.js";
9
+ import { useAiShortcuts } from "../useAiShortcuts.js";
10
+ import { cancelPendingAction } from "./pending-action-api.js";
11
+ import { useAiPendingActionPolling } from "./useAiPendingActionPolling.js";
12
+ import { MutationResultCard } from "./MutationResultCard.js";
13
+ function ConfirmationCard(props) {
14
+ const t = useT();
15
+ const pendingActionId = props.pendingActionId ?? "";
16
+ const payload = props.payload ?? {};
17
+ const injected = props.initialAction ?? payload.pendingAction ?? null;
18
+ const { action, status, refresh } = useAiPendingActionPolling({
19
+ pendingActionId,
20
+ endpoint: props.endpoint,
21
+ disabled: !pendingActionId
22
+ });
23
+ const effectiveAction = action ?? injected ?? null;
24
+ const effectiveStatus = effectiveAction?.status ?? status;
25
+ const [isCancelling, setIsCancelling] = React.useState(false);
26
+ const [localError, setLocalError] = React.useState(
27
+ payload.confirmError ? { code: payload.confirmError.code, message: payload.confirmError.message, extra: payload.confirmError.extra } : null
28
+ );
29
+ const canCancel = !isCancelling && (effectiveStatus === "pending" || effectiveStatus === "confirmed" || effectiveStatus == null);
30
+ const handleCancel = React.useCallback(async () => {
31
+ if (!canCancel) return;
32
+ if (props.onCancel) {
33
+ await props.onCancel();
34
+ return;
35
+ }
36
+ if (!pendingActionId) return;
37
+ setIsCancelling(true);
38
+ try {
39
+ const result = await cancelPendingAction(pendingActionId, {
40
+ endpoint: props.endpoint
41
+ });
42
+ if (!result.ok) {
43
+ setLocalError({
44
+ code: result.error.code,
45
+ message: result.error.message,
46
+ extra: result.error.extra
47
+ });
48
+ }
49
+ await refresh();
50
+ } finally {
51
+ setIsCancelling(false);
52
+ }
53
+ }, [canCancel, pendingActionId, props, refresh]);
54
+ const { handleKeyDown } = useAiShortcuts({
55
+ onCancel: () => {
56
+ void handleCancel();
57
+ },
58
+ enabled: canCancel
59
+ });
60
+ const executionError = effectiveAction?.executionResult?.error;
61
+ if (effectiveStatus === "confirmed" || effectiveStatus === "failed" || effectiveStatus === "cancelled" || effectiveStatus === "expired" || executionError) {
62
+ return /* @__PURE__ */ jsx(
63
+ MutationResultCard,
64
+ {
65
+ componentId: "mutation-result-card",
66
+ pendingActionId,
67
+ initialAction: effectiveAction ?? void 0,
68
+ endpoint: props.endpoint
69
+ }
70
+ );
71
+ }
72
+ const summary = effectiveAction?.sideEffectsSummary ?? payload.sideEffectsSummary ?? t(
73
+ "ai_assistant.chat.mutation_cards.confirmation.defaultSummary",
74
+ "Applying the requested changes..."
75
+ );
76
+ return /* @__PURE__ */ jsxs(
77
+ "section",
78
+ {
79
+ className: "rounded-md border border-border bg-muted/30 p-4 text-sm outline-none",
80
+ tabIndex: 0,
81
+ onKeyDown: handleKeyDown,
82
+ "data-ai-confirmation-card": true,
83
+ "data-ai-confirmation-status": effectiveStatus ?? "pending",
84
+ "aria-busy": true,
85
+ children: [
86
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
87
+ /* @__PURE__ */ jsx(Spinner, { size: "sm", className: "mt-0.5" }),
88
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
89
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: t(
90
+ "ai_assistant.chat.mutation_cards.confirmation.title",
91
+ "Applying action..."
92
+ ) }),
93
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: summary })
94
+ ] })
95
+ ] }),
96
+ localError ? /* @__PURE__ */ jsx(ConfirmationErrorAlert, { error: localError }) : null,
97
+ /* @__PURE__ */ jsx("div", { className: "mt-3 flex items-center justify-end gap-2", children: /* @__PURE__ */ jsxs(
98
+ Button,
99
+ {
100
+ type: "button",
101
+ variant: "outline",
102
+ size: "sm",
103
+ onClick: () => {
104
+ void handleCancel();
105
+ },
106
+ disabled: !canCancel,
107
+ "data-ai-confirmation-cancel": true,
108
+ children: [
109
+ isCancelling ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": true }) : null,
110
+ /* @__PURE__ */ jsx("span", { children: t("ai_assistant.chat.mutation_cards.confirmation.cancel", "Cancel") })
111
+ ]
112
+ }
113
+ ) })
114
+ ]
115
+ }
116
+ );
117
+ }
118
+ function ConfirmationErrorAlert({
119
+ error
120
+ }) {
121
+ const t = useT();
122
+ const code = error.code ?? "unknown";
123
+ if (code === "stale_version") {
124
+ const failedRecords = Array.isArray(error.extra?.failedRecords) ? error.extra?.failedRecords : [];
125
+ return /* @__PURE__ */ jsxs(
126
+ Alert,
127
+ {
128
+ variant: "warning",
129
+ className: "mt-3",
130
+ "data-ai-confirmation-error": "stale_version",
131
+ children: [
132
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "size-4", "aria-hidden": true }),
133
+ /* @__PURE__ */ jsx(AlertTitle, { children: t(
134
+ "ai_assistant.chat.mutation_cards.confirmation.staleVersionTitle",
135
+ "Re-propose required"
136
+ ) }),
137
+ /* @__PURE__ */ jsxs(AlertDescription, { children: [
138
+ /* @__PURE__ */ jsx("p", { children: t(
139
+ "ai_assistant.chat.mutation_cards.confirmation.staleVersionBody",
140
+ "One or more records changed since this preview was generated. Ask the assistant to re-propose the change."
141
+ ) }),
142
+ failedRecords.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "mt-2 list-disc pl-5 text-xs", children: failedRecords.map((record, idx) => /* @__PURE__ */ jsx(
143
+ "li",
144
+ {
145
+ "data-ai-confirmation-stale-record": record.recordId ?? "",
146
+ children: /* @__PURE__ */ jsx("span", { className: "font-mono", children: record.recordId ?? "\u2014" })
147
+ },
148
+ `${record.recordId ?? idx}`
149
+ )) }) : null
150
+ ] })
151
+ ]
152
+ }
153
+ );
154
+ }
155
+ if (code === "schema_drift") {
156
+ return /* @__PURE__ */ jsxs(
157
+ Alert,
158
+ {
159
+ variant: "warning",
160
+ className: "mt-3",
161
+ "data-ai-confirmation-error": "schema_drift",
162
+ children: [
163
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "size-4", "aria-hidden": true }),
164
+ /* @__PURE__ */ jsx(AlertTitle, { children: t(
165
+ "ai_assistant.chat.mutation_cards.confirmation.schemaDriftTitle",
166
+ "Schema changed"
167
+ ) }),
168
+ /* @__PURE__ */ jsx(AlertDescription, { children: t(
169
+ "ai_assistant.chat.mutation_cards.confirmation.schemaDriftBody",
170
+ "The tool signature changed since this preview was generated. Ask the assistant to re-propose the change."
171
+ ) })
172
+ ]
173
+ }
174
+ );
175
+ }
176
+ if (code === "invalid_status") {
177
+ return /* @__PURE__ */ jsxs(
178
+ Alert,
179
+ {
180
+ variant: "warning",
181
+ className: "mt-3",
182
+ "data-ai-confirmation-error": "invalid_status",
183
+ children: [
184
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "size-4", "aria-hidden": true }),
185
+ /* @__PURE__ */ jsx(AlertTitle, { children: t(
186
+ "ai_assistant.chat.mutation_cards.confirmation.invalidStatusTitle",
187
+ "Action already resolved"
188
+ ) }),
189
+ /* @__PURE__ */ jsx(AlertDescription, { children: t(
190
+ "ai_assistant.chat.mutation_cards.confirmation.invalidStatusBody",
191
+ "This action has already been confirmed, cancelled, or executed."
192
+ ) })
193
+ ]
194
+ }
195
+ );
196
+ }
197
+ return /* @__PURE__ */ jsxs(Alert, { variant: "destructive", className: "mt-3", "data-ai-confirmation-error": code, children: [
198
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "size-4", "aria-hidden": true }),
199
+ /* @__PURE__ */ jsx(AlertTitle, { children: t("ai_assistant.chat.mutation_cards.confirmation.errorTitle", "Confirm failed") }),
200
+ /* @__PURE__ */ jsxs(AlertDescription, { children: [
201
+ /* @__PURE__ */ jsx("span", { className: "mr-2 font-mono text-xs", children: code }),
202
+ /* @__PURE__ */ jsx("span", { children: error.message })
203
+ ] })
204
+ ] });
205
+ }
206
+ var ConfirmationCard_default = ConfirmationCard;
207
+ export {
208
+ ConfirmationCard,
209
+ ConfirmationCard_default as default
210
+ };
211
+ //# sourceMappingURL=ConfirmationCard.js.map