@open-mercato/ui 0.6.3-develop.3901.1.ddad60693a → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/ai/AiAssistantLauncher.js +61 -8
  3. package/dist/ai/AiAssistantLauncher.js.map +2 -2
  4. package/dist/ai/AiChat.js +29 -6
  5. package/dist/ai/AiChat.js.map +2 -2
  6. package/dist/ai/AiChatSessions.js +31 -9
  7. package/dist/ai/AiChatSessions.js.map +2 -2
  8. package/dist/ai/AiDock.js +8 -1
  9. package/dist/ai/AiDock.js.map +2 -2
  10. package/dist/ai/ConversationShareButton.js +40 -0
  11. package/dist/ai/ConversationShareButton.js.map +7 -0
  12. package/dist/ai/ConversationShareDialog.js +234 -0
  13. package/dist/ai/ConversationShareDialog.js.map +7 -0
  14. package/dist/ai/conversation-store.js +4 -2
  15. package/dist/ai/conversation-store.js.map +2 -2
  16. package/dist/ai/index.js +4 -0
  17. package/dist/ai/index.js.map +2 -2
  18. package/dist/ai/useAiChat.js +20 -4
  19. package/dist/ai/useAiChat.js.map +2 -2
  20. package/dist/backend/charts/BarChart.js +19 -76
  21. package/dist/backend/charts/BarChart.js.map +2 -2
  22. package/dist/backend/charts/BarChartImpl.js +91 -0
  23. package/dist/backend/charts/BarChartImpl.js.map +7 -0
  24. package/dist/backend/charts/LineChart.js +18 -91
  25. package/dist/backend/charts/LineChart.js.map +2 -2
  26. package/dist/backend/charts/LineChartImpl.js +112 -0
  27. package/dist/backend/charts/LineChartImpl.js.map +7 -0
  28. package/dist/backend/charts/PieChart.js +17 -62
  29. package/dist/backend/charts/PieChart.js.map +2 -2
  30. package/dist/backend/charts/PieChartImpl.js +76 -0
  31. package/dist/backend/charts/PieChartImpl.js.map +7 -0
  32. package/dist/backend/dashboard/DashboardScreen.js +9 -1
  33. package/dist/backend/dashboard/DashboardScreen.js.map +2 -2
  34. package/dist/backend/dashboard/widgetRegistry.js +18 -2
  35. package/dist/backend/dashboard/widgetRegistry.js.map +2 -2
  36. package/dist/backend/icons/lucideRegistry.generated.js +2 -0
  37. package/dist/backend/icons/lucideRegistry.generated.js.map +2 -2
  38. package/dist/backend/injection/useInjectionDataWidgets.js +12 -2
  39. package/dist/backend/injection/useInjectionDataWidgets.js.map +2 -2
  40. package/package.json +4 -5
  41. package/src/ai/AiAssistantLauncher.tsx +81 -5
  42. package/src/ai/AiChat.tsx +42 -2
  43. package/src/ai/AiChatSessions.tsx +55 -9
  44. package/src/ai/AiDock.tsx +13 -0
  45. package/src/ai/ConversationShareButton.tsx +39 -0
  46. package/src/ai/ConversationShareDialog.tsx +299 -0
  47. package/src/ai/__tests__/AiAssistantLauncher.test.tsx +6 -0
  48. package/src/ai/__tests__/AiChat.conversation.test.tsx +97 -0
  49. package/src/ai/__tests__/AiChatSessions.test.tsx +228 -0
  50. package/src/ai/__tests__/AiDock.test.tsx +31 -1
  51. package/src/ai/__tests__/conversation-store.test.ts +74 -1
  52. package/src/ai/conversation-store.ts +22 -2
  53. package/src/ai/index.ts +2 -0
  54. package/src/ai/useAiChat.ts +47 -4
  55. package/src/backend/__tests__/lazy-heavy-libraries.test.ts +53 -0
  56. package/src/backend/charts/BarChart.tsx +21 -72
  57. package/src/backend/charts/BarChartImpl.tsx +99 -0
  58. package/src/backend/charts/LineChart.tsx +23 -85
  59. package/src/backend/charts/LineChartImpl.tsx +120 -0
  60. package/src/backend/charts/PieChart.tsx +19 -60
  61. package/src/backend/charts/PieChartImpl.tsx +82 -0
  62. package/src/backend/dashboard/DashboardScreen.tsx +9 -1
  63. package/src/backend/dashboard/__tests__/DashboardScreen.test.tsx +45 -0
  64. package/src/backend/dashboard/__tests__/widgetRegistry.test.ts +60 -0
  65. package/src/backend/dashboard/widgetRegistry.ts +23 -5
  66. package/src/backend/icons/lucideRegistry.generated.tsx +2 -0
  67. package/src/backend/injection/useInjectionDataWidgets.ts +18 -2
@@ -0,0 +1,234 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { UserPlus, X, Loader2 } from "lucide-react";
5
+ import {
6
+ Dialog,
7
+ DialogContent,
8
+ DialogDescription,
9
+ DialogFooter,
10
+ DialogHeader,
11
+ DialogTitle
12
+ } from "../primitives/dialog.js";
13
+ import { Button } from "../primitives/button.js";
14
+ import { Input } from "../primitives/input.js";
15
+ import {
16
+ Select,
17
+ SelectContent,
18
+ SelectItem,
19
+ SelectTrigger,
20
+ SelectValue
21
+ } from "../primitives/select.js";
22
+ import { apiCall } from "../backend/utils/apiCall.js";
23
+ import { useCurrentUserId } from "../backend/utils/useCurrentUserId.js";
24
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
25
+ function ConversationShareDialog({ open, onOpenChange, conversationId }) {
26
+ const t = useT();
27
+ const currentUserId = useCurrentUserId();
28
+ const [participants, setParticipants] = React.useState([]);
29
+ const [ownerUserId, setOwnerUserId] = React.useState(null);
30
+ const [users, setUsers] = React.useState([]);
31
+ const [canListUsers, setCanListUsers] = React.useState(true);
32
+ const [loading, setLoading] = React.useState(false);
33
+ const [loadingUsers, setLoadingUsers] = React.useState(false);
34
+ const [selectedUserId, setSelectedUserId] = React.useState("");
35
+ const [textUserId, setTextUserId] = React.useState("");
36
+ const [adding, setAdding] = React.useState(false);
37
+ const [removingId, setRemovingId] = React.useState(null);
38
+ const [error, setError] = React.useState(null);
39
+ const baseUrl = `/api/ai_assistant/ai/conversations/${conversationId}/participants`;
40
+ const fetchParticipants = React.useCallback(async () => {
41
+ setLoading(true);
42
+ setError(null);
43
+ try {
44
+ const res = await apiCall(baseUrl);
45
+ if (!res.ok || !res.result) throw new Error("fetch failed");
46
+ setParticipants(res.result.participants.filter((p) => p.role !== "owner"));
47
+ setOwnerUserId(res.result.ownerUserId ?? null);
48
+ } catch {
49
+ setError(t("common.error", "Something went wrong."));
50
+ } finally {
51
+ setLoading(false);
52
+ }
53
+ }, [conversationId, t]);
54
+ const fetchUsers = React.useCallback(async () => {
55
+ setLoadingUsers(true);
56
+ try {
57
+ const res = await apiCall("/api/auth/users?limit=200");
58
+ if (res.ok && res.result) {
59
+ setUsers(res.result.items);
60
+ setCanListUsers(true);
61
+ } else {
62
+ setCanListUsers(false);
63
+ }
64
+ } catch {
65
+ setCanListUsers(false);
66
+ } finally {
67
+ setLoadingUsers(false);
68
+ }
69
+ }, []);
70
+ React.useEffect(() => {
71
+ if (open) {
72
+ setSelectedUserId("");
73
+ setTextUserId("");
74
+ setError(null);
75
+ void Promise.all([fetchParticipants(), fetchUsers()]);
76
+ }
77
+ }, [open, fetchParticipants, fetchUsers]);
78
+ const participantIds = React.useMemo(
79
+ () => new Set(participants.map((p) => p.userId)),
80
+ [participants]
81
+ );
82
+ const availableUsers = React.useMemo(
83
+ () => users.filter(
84
+ (u) => !participantIds.has(u.id) && u.id !== currentUserId && u.id !== ownerUserId
85
+ ),
86
+ [users, participantIds, currentUserId, ownerUserId]
87
+ );
88
+ const activeUserId = canListUsers ? selectedUserId : textUserId.trim();
89
+ const handleAdd = React.useCallback(async () => {
90
+ if (!activeUserId) return;
91
+ setAdding(true);
92
+ setError(null);
93
+ try {
94
+ const res = await apiCall(baseUrl, {
95
+ method: "POST",
96
+ headers: { "content-type": "application/json" },
97
+ body: JSON.stringify({ userId: activeUserId, role: "viewer" })
98
+ });
99
+ if (!res.ok || !res.result) throw new Error("fetch failed");
100
+ setParticipants((prev) => {
101
+ const next = res.result.participant;
102
+ const exists = prev.find((p) => p.userId === next.userId);
103
+ if (exists) return prev.map((p) => p.userId === next.userId ? next : p);
104
+ return [...prev, next];
105
+ });
106
+ setSelectedUserId("");
107
+ setTextUserId("");
108
+ } catch {
109
+ setError(t("common.error", "Something went wrong."));
110
+ } finally {
111
+ setAdding(false);
112
+ }
113
+ }, [activeUserId, baseUrl, t]);
114
+ React.useEffect(() => {
115
+ const handler = (e) => {
116
+ if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
117
+ e.preventDefault();
118
+ if (activeUserId && !adding) void handleAdd();
119
+ }
120
+ };
121
+ if (open) document.addEventListener("keydown", handler);
122
+ return () => document.removeEventListener("keydown", handler);
123
+ }, [open, activeUserId, adding, handleAdd]);
124
+ const handleRemove = async (userId) => {
125
+ setRemovingId(userId);
126
+ setError(null);
127
+ try {
128
+ await apiCall(`${baseUrl}/${userId}`, { method: "DELETE", headers: { "content-type": "application/json" } });
129
+ setParticipants((prev) => prev.filter((p) => p.userId !== userId));
130
+ } catch {
131
+ setError(t("common.error", "Something went wrong."));
132
+ } finally {
133
+ setRemovingId(null);
134
+ }
135
+ };
136
+ const formatUserLabel = (u) => u.name ? `${u.name} \u2014 ${u.email}` : u.email;
137
+ const getUserLabel = (userId) => {
138
+ const u = users.find((u2) => u2.id === userId);
139
+ return u ? formatUserLabel(u) : userId;
140
+ };
141
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-md", children: [
142
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
143
+ /* @__PURE__ */ jsx(DialogTitle, { children: t("ai_assistant.share.dialogTitle", "Share Conversation") }),
144
+ /* @__PURE__ */ jsx(DialogDescription, { children: t(
145
+ "ai_assistant.share.dialogDescription",
146
+ "Share this conversation with other users. They will get read-only access."
147
+ ) })
148
+ ] }),
149
+ /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
150
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
151
+ canListUsers === false ? /* @__PURE__ */ jsx(
152
+ Input,
153
+ {
154
+ placeholder: t("ai_assistant.share.participantPlaceholder", "User ID..."),
155
+ value: textUserId,
156
+ onChange: (e) => setTextUserId(e.target.value),
157
+ onKeyDown: (e) => {
158
+ if (e.key === "Enter") {
159
+ e.preventDefault();
160
+ void handleAdd();
161
+ }
162
+ },
163
+ className: "flex-1"
164
+ }
165
+ ) : /* @__PURE__ */ jsxs(
166
+ Select,
167
+ {
168
+ value: selectedUserId,
169
+ onValueChange: setSelectedUserId,
170
+ disabled: loadingUsers || availableUsers.length === 0,
171
+ children: [
172
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "flex-1", children: /* @__PURE__ */ jsx(
173
+ SelectValue,
174
+ {
175
+ placeholder: loadingUsers ? t("common.loading", "Loading...") : availableUsers.length === 0 ? t("ai_assistant.share.allUsersAdded", "All users already added") : t("ai_assistant.share.selectUser", "Select a user...")
176
+ }
177
+ ) }),
178
+ /* @__PURE__ */ jsx(SelectContent, { children: availableUsers.map((u) => /* @__PURE__ */ jsx(SelectItem, { value: u.id, children: formatUserLabel(u) }, u.id)) })
179
+ ]
180
+ }
181
+ ),
182
+ /* @__PURE__ */ jsxs(
183
+ Button,
184
+ {
185
+ type: "button",
186
+ onClick: () => void handleAdd(),
187
+ disabled: !activeUserId || adding,
188
+ size: "default",
189
+ children: [
190
+ adding ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx(UserPlus, { className: "size-4" }),
191
+ t("ai_assistant.share.addParticipant", "Add")
192
+ ]
193
+ }
194
+ )
195
+ ] }),
196
+ error ? /* @__PURE__ */ jsx("p", { className: "text-sm text-status-danger-text", children: error }) : null,
197
+ /* @__PURE__ */ jsx("div", { className: "space-y-2 min-h-[60px]", children: loading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-4", children: /* @__PURE__ */ jsx(Loader2, { className: "size-5 animate-spin text-muted-foreground" }) }) : participants.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground py-2", children: t(
198
+ "ai_assistant.share.noParticipants",
199
+ "No participants yet. Add someone to share this conversation."
200
+ ) }) : participants.map((p) => /* @__PURE__ */ jsxs(
201
+ "div",
202
+ {
203
+ className: "flex items-center justify-between rounded-lg border border-border px-3 py-2",
204
+ children: [
205
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
206
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm text-foreground", children: getUserLabel(p.userId) }),
207
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground capitalize", children: p.role })
208
+ ] }),
209
+ /* @__PURE__ */ jsx(
210
+ Button,
211
+ {
212
+ type: "button",
213
+ variant: "ghost",
214
+ size: "icon",
215
+ onClick: () => void handleRemove(p.userId),
216
+ disabled: removingId === p.userId,
217
+ "aria-label": t("ai_assistant.share.removeParticipant", "Remove"),
218
+ children: removingId === p.userId ? /* @__PURE__ */ jsx(Loader2, { className: "size-3 animate-spin" }) : /* @__PURE__ */ jsx(X, { className: "size-4" })
219
+ }
220
+ )
221
+ ]
222
+ },
223
+ p.userId
224
+ )) })
225
+ ] }),
226
+ /* @__PURE__ */ jsx(DialogFooter, { children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), children: t("common.close", "Close") }) })
227
+ ] }) });
228
+ }
229
+ var ConversationShareDialog_default = ConversationShareDialog;
230
+ export {
231
+ ConversationShareDialog,
232
+ ConversationShareDialog_default as default
233
+ };
234
+ //# sourceMappingURL=ConversationShareDialog.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/ai/ConversationShareDialog.tsx"],
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { UserPlus, X, Loader2 } from 'lucide-react'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../primitives/dialog'\nimport { Button } from '../primitives/button'\nimport { Input } from '../primitives/input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../primitives/select'\nimport { apiCall } from '../backend/utils/apiCall'\nimport { useCurrentUserId } from '../backend/utils/useCurrentUserId'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ninterface Participant {\n userId: string\n role: string\n lastReadAt: string | null\n addedAt: string\n}\n\ninterface UserOption {\n id: string\n email: string\n name: string | null\n}\n\ninterface Props {\n open: boolean\n onOpenChange: (next: boolean) => void\n conversationId: string\n}\n\nexport function ConversationShareDialog({ open, onOpenChange, conversationId }: Props) {\n const t = useT()\n const currentUserId = useCurrentUserId()\n const [participants, setParticipants] = React.useState<Participant[]>([])\n const [ownerUserId, setOwnerUserId] = React.useState<string | null>(null)\n const [users, setUsers] = React.useState<UserOption[]>([])\n const [canListUsers, setCanListUsers] = React.useState(true)\n const [loading, setLoading] = React.useState(false)\n const [loadingUsers, setLoadingUsers] = React.useState(false)\n const [selectedUserId, setSelectedUserId] = React.useState('')\n const [textUserId, setTextUserId] = React.useState('')\n const [adding, setAdding] = React.useState(false)\n const [removingId, setRemovingId] = React.useState<string | null>(null)\n const [error, setError] = React.useState<string | null>(null)\n\n const baseUrl = `/api/ai_assistant/ai/conversations/${conversationId}/participants`\n\n const fetchParticipants = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n try {\n const res = await apiCall<{ participants: Participant[]; ownerUserId?: string | null }>(baseUrl)\n if (!res.ok || !res.result) throw new Error('fetch failed')\n setParticipants(res.result.participants.filter((p) => p.role !== 'owner'))\n setOwnerUserId(res.result.ownerUserId ?? null)\n } catch {\n setError(t('common.error', 'Something went wrong.'))\n } finally {\n setLoading(false)\n }\n }, [conversationId, t])\n\n const fetchUsers = React.useCallback(async () => {\n setLoadingUsers(true)\n try {\n const res = await apiCall<{ items: UserOption[] }>('/api/auth/users?limit=200')\n if (res.ok && res.result) {\n setUsers(res.result.items)\n setCanListUsers(true)\n } else {\n setCanListUsers(false)\n }\n } catch {\n setCanListUsers(false)\n } finally {\n setLoadingUsers(false)\n }\n }, [])\n\n React.useEffect(() => {\n if (open) {\n setSelectedUserId('')\n setTextUserId('')\n setError(null)\n void Promise.all([fetchParticipants(), fetchUsers()])\n }\n }, [open, fetchParticipants, fetchUsers])\n\n const participantIds = React.useMemo(\n () => new Set(participants.map((p) => p.userId)),\n [participants],\n )\n\n const availableUsers = React.useMemo(\n () =>\n users.filter(\n (u) => !participantIds.has(u.id) && u.id !== currentUserId && u.id !== ownerUserId,\n ),\n [users, participantIds, currentUserId, ownerUserId],\n )\n\n const activeUserId = canListUsers ? selectedUserId : textUserId.trim()\n\n const handleAdd = React.useCallback(async () => {\n if (!activeUserId) return\n setAdding(true)\n setError(null)\n try {\n const res = await apiCall<{ participant: Participant }>(baseUrl, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ userId: activeUserId, role: 'viewer' }),\n })\n if (!res.ok || !res.result) throw new Error('fetch failed')\n setParticipants((prev) => {\n const next = res.result!.participant\n const exists = prev.find((p) => p.userId === next.userId)\n if (exists) return prev.map((p) => (p.userId === next.userId ? next : p))\n return [...prev, next]\n })\n setSelectedUserId('')\n setTextUserId('')\n } catch {\n setError(t('common.error', 'Something went wrong.'))\n } finally {\n setAdding(false)\n }\n }, [activeUserId, baseUrl, t])\n\n React.useEffect(() => {\n const handler = (e: KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n e.preventDefault()\n if (activeUserId && !adding) void handleAdd()\n }\n }\n if (open) document.addEventListener('keydown', handler)\n return () => document.removeEventListener('keydown', handler)\n }, [open, activeUserId, adding, handleAdd])\n\n const handleRemove = async (userId: string) => {\n setRemovingId(userId)\n setError(null)\n try {\n await apiCall(`${baseUrl}/${userId}`, { method: 'DELETE', headers: { 'content-type': 'application/json' } })\n setParticipants((prev) => prev.filter((p) => p.userId !== userId))\n } catch {\n setError(t('common.error', 'Something went wrong.'))\n } finally {\n setRemovingId(null)\n }\n }\n\n const formatUserLabel = (u: UserOption) => (u.name ? `${u.name} \u2014 ${u.email}` : u.email)\n\n const getUserLabel = (userId: string) => {\n const u = users.find((u) => u.id === userId)\n return u ? formatUserLabel(u) : userId\n }\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>\n {t('ai_assistant.share.dialogTitle', 'Share Conversation')}\n </DialogTitle>\n <DialogDescription>\n {t(\n 'ai_assistant.share.dialogDescription',\n 'Share this conversation with other users. They will get read-only access.',\n )}\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-4\">\n <div className=\"flex gap-2\">\n {canListUsers === false ? (\n <Input\n placeholder={t('ai_assistant.share.participantPlaceholder', 'User ID...')}\n value={textUserId}\n onChange={(e) => setTextUserId(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') { e.preventDefault(); void handleAdd() }\n }}\n className=\"flex-1\"\n />\n ) : (\n <Select\n value={selectedUserId}\n onValueChange={setSelectedUserId}\n disabled={loadingUsers || availableUsers.length === 0}\n >\n <SelectTrigger className=\"flex-1\">\n <SelectValue\n placeholder={\n loadingUsers\n ? t('common.loading', 'Loading...')\n : availableUsers.length === 0\n ? t('ai_assistant.share.allUsersAdded', 'All users already added')\n : t('ai_assistant.share.selectUser', 'Select a user...')\n }\n />\n </SelectTrigger>\n <SelectContent>\n {availableUsers.map((u) => (\n <SelectItem key={u.id} value={u.id}>\n {formatUserLabel(u)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n <Button\n type=\"button\"\n onClick={() => void handleAdd()}\n disabled={!activeUserId || adding}\n size=\"default\"\n >\n {adding ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <UserPlus className=\"size-4\" />\n )}\n {t('ai_assistant.share.addParticipant', 'Add')}\n </Button>\n </div>\n\n {error ? (\n <p className=\"text-sm text-status-danger-text\">{error}</p>\n ) : null}\n\n <div className=\"space-y-2 min-h-[60px]\">\n {loading ? (\n <div className=\"flex items-center justify-center py-4\">\n <Loader2 className=\"size-5 animate-spin text-muted-foreground\" />\n </div>\n ) : participants.length === 0 ? (\n <p className=\"text-sm text-muted-foreground py-2\">\n {t(\n 'ai_assistant.share.noParticipants',\n 'No participants yet. Add someone to share this conversation.',\n )}\n </p>\n ) : (\n participants.map((p) => (\n <div\n key={p.userId}\n className=\"flex items-center justify-between rounded-lg border border-border px-3 py-2\"\n >\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm text-foreground\">{getUserLabel(p.userId)}</p>\n <p className=\"text-xs text-muted-foreground capitalize\">{p.role}</p>\n </div>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => void handleRemove(p.userId)}\n disabled={removingId === p.userId}\n aria-label={t('ai_assistant.share.removeParticipant', 'Remove')}\n >\n {removingId === p.userId ? (\n <Loader2 className=\"size-3 animate-spin\" />\n ) : (\n <X className=\"size-4\" />\n )}\n </Button>\n </div>\n ))\n )}\n </div>\n </div>\n\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)}>\n {t('common.close', 'Close')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n\nexport default ConversationShareDialog\n"],
5
+ "mappings": "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",
6
+ "names": ["u"]
7
+ }
@@ -74,7 +74,8 @@ async function loadAiServerTranscript(conversationId, options = {}) {
74
74
  const result = await requestJson(
75
75
  `${CONVERSATIONS_ENDPOINT}/${encodeURIComponent(conversationId)}?${params.toString()}`
76
76
  );
77
- return result.ok ? result.data : null;
77
+ if (result.ok) return { ok: true, data: result.data };
78
+ return { ok: false, notFound: result.status === 404 };
78
79
  }
79
80
  async function importAiLocalConversation(input) {
80
81
  const result = await requestJson(`${CONVERSATIONS_ENDPOINT}/import`, {
@@ -135,7 +136,8 @@ function serverMessageToChatMessage(message) {
135
136
  ...previewUrl ? { previewUrl } : {}
136
137
  };
137
138
  }).filter((file) => file !== null),
138
- uiParts
139
+ uiParts,
140
+ senderUserId: message.senderUserId ?? null
139
141
  };
140
142
  }
141
143
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/ai/conversation-store.ts"],
4
- "sourcesContent": ["\"use client\"\n\nimport type { AiChatMessage, AiChatMessageUiPart } from './useAiChat'\n\nconst CONVERSATIONS_ENDPOINT = '/api/ai_assistant/ai/conversations'\n\nexport interface AiServerConversation {\n conversationId: string\n agentId: string\n title: string | null\n status: 'open' | 'closed'\n visibility: 'private' | 'shared' | 'organization'\n pageContext: Record<string, unknown> | null\n createdAt: string\n updatedAt: string\n lastMessageAt: string | null\n importedFromLocalAt: string | null\n}\n\nexport interface AiServerMessage {\n id: string\n clientMessageId: string | null\n role: 'user' | 'assistant' | 'system'\n content: string\n uiParts: unknown[]\n attachmentIds: string[]\n files: Array<Record<string, unknown>>\n model: string | null\n metadata: Record<string, unknown> | null\n createdAt: string\n}\n\nexport interface AiServerConversationListResponse {\n items: AiServerConversation[]\n nextCursor: string | null\n}\n\nexport interface AiServerTranscriptResponse {\n conversation: AiServerConversation\n messages: AiServerMessage[]\n nextCursor: string | null\n}\n\nexport interface AiServerImportResponse {\n conversation: AiServerConversation\n importedMessageCount: number\n skippedMessageCount: number\n}\n\nfunction readString(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null\n}\n\nfunction buildAttachmentImagePreviewUrl(attachmentId: string): string {\n const params = new URLSearchParams({\n width: '320',\n height: '320',\n cropType: 'contain',\n })\n return `/api/attachments/image/${encodeURIComponent(attachmentId)}?${params.toString()}`\n}\n\nfunction getFetch(): typeof fetch | null {\n const fetchImpl = (globalThis as typeof globalThis & { fetch?: typeof fetch }).fetch\n return typeof fetchImpl === 'function' ? fetchImpl.bind(globalThis) : null\n}\n\nasync function requestJson<T>(\n input: string,\n init?: RequestInit,\n): Promise<{ ok: true; status: number; data: T } | { ok: false; status: number; data: null }> {\n const fetchImpl = getFetch()\n if (!fetchImpl) return { ok: false, status: 0, data: null }\n try {\n const response = await fetchImpl(input, {\n credentials: 'include',\n ...init,\n headers: {\n Accept: 'application/json',\n 'x-om-forbidden-redirect': '0',\n 'x-om-unauthorized-redirect': '0',\n ...(init?.body ? { 'Content-Type': 'application/json' } : {}),\n ...(init?.headers ?? {}),\n },\n })\n if (!response.ok) return { ok: false, status: response.status, data: null }\n return { ok: true, status: response.status, data: (await response.json()) as T }\n } catch {\n return { ok: false, status: 0, data: null }\n }\n}\n\nexport async function listAiServerConversations(options: {\n agentId?: string | null\n status?: 'open' | 'closed' | null\n limit?: number\n} = {}): Promise<AiServerConversation[] | null> {\n const params = new URLSearchParams()\n if (options.agentId) params.set('agent', options.agentId)\n if (options.status) params.set('status', options.status)\n params.set('limit', String(options.limit ?? 100))\n const result = await requestJson<AiServerConversationListResponse>(\n `${CONVERSATIONS_ENDPOINT}?${params.toString()}`,\n )\n return result.ok ? result.data.items : null\n}\n\nexport async function createAiServerConversation(input: {\n agentId: string\n conversationId: string\n title?: string | null\n pageContext?: Record<string, unknown> | null\n}): Promise<AiServerConversation | null> {\n const result = await requestJson<AiServerConversation>(CONVERSATIONS_ENDPOINT, {\n method: 'POST',\n body: JSON.stringify({\n agentId: input.agentId,\n conversationId: input.conversationId,\n title: input.title ?? undefined,\n pageContext: input.pageContext ?? undefined,\n }),\n })\n return result.ok ? result.data : null\n}\n\nexport async function updateAiServerConversation(\n conversationId: string,\n patch: {\n title?: string | null\n status?: 'open' | 'closed'\n pageContext?: Record<string, unknown> | null\n },\n): Promise<AiServerConversation | null> {\n const result = await requestJson<AiServerConversation>(\n `${CONVERSATIONS_ENDPOINT}/${encodeURIComponent(conversationId)}`,\n {\n method: 'PATCH',\n body: JSON.stringify(patch),\n },\n )\n return result.ok ? result.data : null\n}\n\nexport async function loadAiServerTranscript(\n conversationId: string,\n options: { limit?: number } = {},\n): Promise<AiServerTranscriptResponse | null> {\n const params = new URLSearchParams()\n params.set('limit', String(options.limit ?? 100))\n const result = await requestJson<AiServerTranscriptResponse>(\n `${CONVERSATIONS_ENDPOINT}/${encodeURIComponent(conversationId)}?${params.toString()}`,\n )\n return result.ok ? result.data : null\n}\n\nexport async function importAiLocalConversation(input: {\n agentId: string\n conversationId: string\n title?: string | null\n status?: 'open' | 'closed'\n pageContext?: Record<string, unknown> | null\n messages: AiChatMessage[]\n}): Promise<AiServerImportResponse | null> {\n const result = await requestJson<AiServerImportResponse>(`${CONVERSATIONS_ENDPOINT}/import`, {\n method: 'POST',\n body: JSON.stringify({\n conversation: {\n conversationId: input.conversationId,\n agentId: input.agentId,\n title: input.title ?? undefined,\n status: input.status ?? 'open',\n pageContext: input.pageContext ?? undefined,\n },\n messages: input.messages.slice(-100).map((message) => ({\n clientMessageId: message.id,\n role: message.role,\n content: message.content,\n uiParts: message.uiParts ?? [],\n files: (message.files ?? []).map((file) => ({\n id: file.id,\n name: file.name,\n mimeType: file.type,\n })),\n })),\n }),\n })\n return result.ok ? result.data : null\n}\n\nexport function serverMessageToChatMessage(message: AiServerMessage): AiChatMessage | null {\n if (message.role !== 'user' && message.role !== 'assistant') return null\n const uiParts: AiChatMessageUiPart[] = []\n message.uiParts.forEach((part, index) => {\n if (!part || typeof part !== 'object') return\n const value = part as Record<string, unknown>\n const componentId =\n typeof value.componentId === 'string'\n ? value.componentId\n : typeof value.type === 'string'\n ? value.type\n : null\n if (!componentId) return\n uiParts.push({\n componentId,\n payload: value.payload ?? value.props,\n pendingActionId:\n typeof value.pendingActionId === 'string' ? value.pendingActionId : undefined,\n key:\n typeof value.key === 'string'\n ? value.key\n : `${message.id}:${index}:${componentId}`,\n })\n })\n return {\n id: message.clientMessageId ?? message.id,\n role: message.role,\n content: message.content,\n files: message.files\n .map((file, index) => {\n const name = readString(file.name)\n if (!name) return null\n const type =\n typeof file.mimeType === 'string'\n ? file.mimeType\n : typeof file.type === 'string'\n ? file.type\n : 'application/octet-stream'\n const id = readString(file.id) ?? readString(message.attachmentIds[index])\n const rawPreviewUrl =\n readString(file.previewUrl) ??\n readString(file.thumbnailUrl) ??\n readString(file.url)\n const previewUrl = type.startsWith('image/')\n ? rawPreviewUrl ?? (id ? buildAttachmentImagePreviewUrl(id) : undefined)\n : undefined\n return {\n ...(id ? { id } : {}),\n name,\n type,\n ...(previewUrl ? { previewUrl } : {}),\n }\n })\n .filter((file): file is NonNullable<AiChatMessage['files']>[number] => file !== null),\n uiParts,\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport type { AiChatMessage, AiChatMessageUiPart } from './useAiChat'\n\nconst CONVERSATIONS_ENDPOINT = '/api/ai_assistant/ai/conversations'\n\nexport interface AiServerConversation {\n conversationId: string\n agentId: string\n title: string | null\n status: 'open' | 'closed'\n visibility: 'private' | 'shared' | 'organization'\n pageContext: Record<string, unknown> | null\n createdAt: string\n updatedAt: string\n lastMessageAt: string | null\n importedFromLocalAt: string | null\n /** `true` when the authenticated caller created the conversation; `false` for shared participants; `null` when unknown. */\n isOwner: boolean | null\n}\n\nexport interface AiServerMessage {\n id: string\n clientMessageId: string | null\n role: 'user' | 'assistant' | 'system'\n content: string\n uiParts: unknown[]\n attachmentIds: string[]\n files: Array<Record<string, unknown>>\n model: string | null\n metadata: Record<string, unknown> | null\n createdAt: string\n senderUserId: string | null\n}\n\nexport interface AiServerConversationListResponse {\n items: AiServerConversation[]\n nextCursor: string | null\n}\n\nexport interface AiServerTranscriptResponse {\n conversation: AiServerConversation\n messages: AiServerMessage[]\n nextCursor: string | null\n}\n\nexport interface AiServerImportResponse {\n conversation: AiServerConversation\n importedMessageCount: number\n skippedMessageCount: number\n}\n\n/**\n * Discriminated result of {@link loadAiServerTranscript}. Callers must\n * distinguish 404 (the conversation does not exist for the current scope \u2014\n * e.g. it belonged to a previous tenant/org) from other failures (network\n * down, server error) because the recovery strategies are opposite:\n * - `notFound: true` \u2192 drop the local cache, optionally surface a\n * not-found callback. NEVER import local messages\n * onto the new scope.\n * - `notFound: false` \u2192 preserve local state and retry later.\n */\nexport type LoadTranscriptResult =\n | { ok: true; data: AiServerTranscriptResponse }\n | { ok: false; notFound: true }\n | { ok: false; notFound: false }\n\nfunction readString(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null\n}\n\nfunction buildAttachmentImagePreviewUrl(attachmentId: string): string {\n const params = new URLSearchParams({\n width: '320',\n height: '320',\n cropType: 'contain',\n })\n return `/api/attachments/image/${encodeURIComponent(attachmentId)}?${params.toString()}`\n}\n\nfunction getFetch(): typeof fetch | null {\n const fetchImpl = (globalThis as typeof globalThis & { fetch?: typeof fetch }).fetch\n return typeof fetchImpl === 'function' ? fetchImpl.bind(globalThis) : null\n}\n\nasync function requestJson<T>(\n input: string,\n init?: RequestInit,\n): Promise<{ ok: true; status: number; data: T } | { ok: false; status: number; data: null }> {\n const fetchImpl = getFetch()\n if (!fetchImpl) return { ok: false, status: 0, data: null }\n try {\n const response = await fetchImpl(input, {\n credentials: 'include',\n ...init,\n headers: {\n Accept: 'application/json',\n 'x-om-forbidden-redirect': '0',\n 'x-om-unauthorized-redirect': '0',\n ...(init?.body ? { 'Content-Type': 'application/json' } : {}),\n ...(init?.headers ?? {}),\n },\n })\n if (!response.ok) return { ok: false, status: response.status, data: null }\n return { ok: true, status: response.status, data: (await response.json()) as T }\n } catch {\n return { ok: false, status: 0, data: null }\n }\n}\n\nexport async function listAiServerConversations(options: {\n agentId?: string | null\n status?: 'open' | 'closed' | null\n limit?: number\n} = {}): Promise<AiServerConversation[] | null> {\n const params = new URLSearchParams()\n if (options.agentId) params.set('agent', options.agentId)\n if (options.status) params.set('status', options.status)\n params.set('limit', String(options.limit ?? 100))\n const result = await requestJson<AiServerConversationListResponse>(\n `${CONVERSATIONS_ENDPOINT}?${params.toString()}`,\n )\n return result.ok ? result.data.items : null\n}\n\nexport async function createAiServerConversation(input: {\n agentId: string\n conversationId: string\n title?: string | null\n pageContext?: Record<string, unknown> | null\n}): Promise<AiServerConversation | null> {\n const result = await requestJson<AiServerConversation>(CONVERSATIONS_ENDPOINT, {\n method: 'POST',\n body: JSON.stringify({\n agentId: input.agentId,\n conversationId: input.conversationId,\n title: input.title ?? undefined,\n pageContext: input.pageContext ?? undefined,\n }),\n })\n return result.ok ? result.data : null\n}\n\nexport async function updateAiServerConversation(\n conversationId: string,\n patch: {\n title?: string | null\n status?: 'open' | 'closed'\n pageContext?: Record<string, unknown> | null\n },\n): Promise<AiServerConversation | null> {\n const result = await requestJson<AiServerConversation>(\n `${CONVERSATIONS_ENDPOINT}/${encodeURIComponent(conversationId)}`,\n {\n method: 'PATCH',\n body: JSON.stringify(patch),\n },\n )\n return result.ok ? result.data : null\n}\n\nexport async function loadAiServerTranscript(\n conversationId: string,\n options: { limit?: number } = {},\n): Promise<LoadTranscriptResult> {\n const params = new URLSearchParams()\n params.set('limit', String(options.limit ?? 100))\n const result = await requestJson<AiServerTranscriptResponse>(\n `${CONVERSATIONS_ENDPOINT}/${encodeURIComponent(conversationId)}?${params.toString()}`,\n )\n if (result.ok) return { ok: true, data: result.data }\n return { ok: false, notFound: result.status === 404 }\n}\n\nexport async function importAiLocalConversation(input: {\n agentId: string\n conversationId: string\n title?: string | null\n status?: 'open' | 'closed'\n pageContext?: Record<string, unknown> | null\n messages: AiChatMessage[]\n}): Promise<AiServerImportResponse | null> {\n const result = await requestJson<AiServerImportResponse>(`${CONVERSATIONS_ENDPOINT}/import`, {\n method: 'POST',\n body: JSON.stringify({\n conversation: {\n conversationId: input.conversationId,\n agentId: input.agentId,\n title: input.title ?? undefined,\n status: input.status ?? 'open',\n pageContext: input.pageContext ?? undefined,\n },\n messages: input.messages.slice(-100).map((message) => ({\n clientMessageId: message.id,\n role: message.role,\n content: message.content,\n uiParts: message.uiParts ?? [],\n files: (message.files ?? []).map((file) => ({\n id: file.id,\n name: file.name,\n mimeType: file.type,\n })),\n })),\n }),\n })\n return result.ok ? result.data : null\n}\n\nexport function serverMessageToChatMessage(message: AiServerMessage): AiChatMessage | null {\n if (message.role !== 'user' && message.role !== 'assistant') return null\n const uiParts: AiChatMessageUiPart[] = []\n message.uiParts.forEach((part, index) => {\n if (!part || typeof part !== 'object') return\n const value = part as Record<string, unknown>\n const componentId =\n typeof value.componentId === 'string'\n ? value.componentId\n : typeof value.type === 'string'\n ? value.type\n : null\n if (!componentId) return\n uiParts.push({\n componentId,\n payload: value.payload ?? value.props,\n pendingActionId:\n typeof value.pendingActionId === 'string' ? value.pendingActionId : undefined,\n key:\n typeof value.key === 'string'\n ? value.key\n : `${message.id}:${index}:${componentId}`,\n })\n })\n return {\n id: message.clientMessageId ?? message.id,\n role: message.role,\n content: message.content,\n files: message.files\n .map((file, index) => {\n const name = readString(file.name)\n if (!name) return null\n const type =\n typeof file.mimeType === 'string'\n ? file.mimeType\n : typeof file.type === 'string'\n ? file.type\n : 'application/octet-stream'\n const id = readString(file.id) ?? readString(message.attachmentIds[index])\n const rawPreviewUrl =\n readString(file.previewUrl) ??\n readString(file.thumbnailUrl) ??\n readString(file.url)\n const previewUrl = type.startsWith('image/')\n ? rawPreviewUrl ?? (id ? buildAttachmentImagePreviewUrl(id) : undefined)\n : undefined\n return {\n ...(id ? { id } : {}),\n name,\n type,\n ...(previewUrl ? { previewUrl } : {}),\n }\n })\n .filter((file): file is NonNullable<AiChatMessage['files']>[number] => file !== null),\n uiParts,\n senderUserId: message.senderUserId ?? null,\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/ai/index.js CHANGED
@@ -68,6 +68,8 @@ import {
68
68
  useAiChatUpload
69
69
  } from "./useAiChatUpload.js";
70
70
  import { LoopDisabledBanner } from "./LoopDisabledBanner.js";
71
+ import { ConversationShareButton } from "./ConversationShareButton.js";
72
+ import { ConversationShareDialog } from "./ConversationShareDialog.js";
71
73
  import {
72
74
  LoopTracePanel
73
75
  } from "./LoopTracePanel.js";
@@ -84,6 +86,8 @@ export {
84
86
  ChatPaneTabs,
85
87
  CompanyCard,
86
88
  ConfirmationCard,
89
+ ConversationShareButton,
90
+ ConversationShareDialog,
87
91
  DealCard,
88
92
  FieldDiffCard,
89
93
  KeyValueList,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/ai/index.ts"],
4
- "sourcesContent": ["export { AiChat, type AiChatProps, type AiChatDebugTool, type AiChatDebugPromptSection } from './AiChat'\nexport { AiIcon } from './AiIcon'\nexport {\n AiAssistantLauncher,\n AI_ASSISTANT_LAUNCHER_OPEN_EVENT,\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'\nexport { LoopDisabledBanner } from './LoopDisabledBanner'\nexport {\n LoopTracePanel,\n type LoopTracePanelProps,\n type LoopTracePanelTrace,\n type LoopTracePanelStepRecord,\n} from './LoopTracePanel'\n"],
5
- "mappings": "AAAA,SAAS,cAAqF;AAC9F,SAAS,cAAc;AACvB;AAAA,EACE;AAAA,EACA;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;AACP,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,OAIK;",
4
+ "sourcesContent": ["export { AiChat, type AiChatProps, type AiChatDebugTool, type AiChatDebugPromptSection } from './AiChat'\nexport { AiIcon } from './AiIcon'\nexport {\n AiAssistantLauncher,\n AI_ASSISTANT_LAUNCHER_OPEN_EVENT,\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'\nexport { LoopDisabledBanner } from './LoopDisabledBanner'\nexport { ConversationShareButton } from './ConversationShareButton'\nexport { ConversationShareDialog } from './ConversationShareDialog'\nexport {\n LoopTracePanel,\n type LoopTracePanelProps,\n type LoopTracePanelTrace,\n type LoopTracePanelStepRecord,\n} from './LoopTracePanel'\n"],
5
+ "mappings": "AAAA,SAAS,cAAqF;AAC9F,SAAS,cAAc;AACvB;AAAA,EACE;AAAA,EACA;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;AACP,SAAS,0BAA0B;AACnC,SAAS,+BAA+B;AACxC,SAAS,+BAA+B;AACxC;AAAA,EACE;AAAA,OAIK;",
6
6
  "names": []
7
7
  }
@@ -437,7 +437,7 @@ async function readErrorEnvelope(response) {
437
437
  return { message: text || `Agent dispatch failed (${response.status}).` };
438
438
  }
439
439
  function useAiChat(input) {
440
- const { agent, apiPath, pageContext, attachmentIds, debug, initialMessages, onError, conversationId: conversationIdInput, providerOverride, modelOverride } = input;
440
+ const { agent, apiPath, pageContext, attachmentIds, debug, initialMessages, onError, conversationId: conversationIdInput, providerOverride, modelOverride, onConversationNotFound } = input;
441
441
  const persistedRef = React.useRef("unread");
442
442
  if (persistedRef.current === "unread") {
443
443
  persistedRef.current = typeof conversationIdInput === "string" && conversationIdInput.length > 0 ? readPersistedSession(agent, conversationIdInput) : readPersistedSession(agent);
@@ -538,9 +538,13 @@ function useAiChat(input) {
538
538
  let cancelled = false;
539
539
  const localCandidate = readPersistedSession(agent, persistKey);
540
540
  async function hydrateFromServer() {
541
- const transcript = await loadAiServerTranscript(effectiveConversationId, { limit: 100 });
541
+ const transcriptResult = await loadAiServerTranscript(effectiveConversationId, { limit: 100 });
542
542
  if (cancelled || latestStatusRef.current !== "idle") return;
543
- if (transcript) {
543
+ if (transcriptResult.ok) {
544
+ const transcript = transcriptResult.data;
545
+ if (typeof transcript.conversation.isOwner === "boolean") {
546
+ setIsOwner(transcript.conversation.isOwner);
547
+ }
544
548
  const serverMessages = transcript.messages.map(serverMessageToChatMessage).filter((message) => message !== null);
545
549
  if (serverMessages.length > 0 || !localCandidate || localCandidate.messages.length === 0) {
546
550
  updateMessages(serverMessages);
@@ -548,8 +552,14 @@ function useAiChat(input) {
548
552
  return;
549
553
  }
550
554
  }
555
+ if (!transcriptResult.ok && transcriptResult.notFound) {
556
+ updateMessages([]);
557
+ clearPersistedSession(agent, persistKey);
558
+ onConversationNotFoundRef.current?.();
559
+ return;
560
+ }
551
561
  if (!localCandidate || localCandidate.messages.length === 0) {
552
- if (!transcript) {
562
+ if (!transcriptResult.ok) {
553
563
  void createAiServerConversation({
554
564
  agentId: agent,
555
565
  conversationId: effectiveConversationId,
@@ -582,11 +592,16 @@ function useAiChat(input) {
582
592
  const [lastRequestDebug, setLastRequestDebug] = React.useState(null);
583
593
  const [lastResponseDebug, setLastResponseDebug] = React.useState(null);
584
594
  const [lastLoopTrace, setLastLoopTrace] = React.useState(null);
595
+ const [isOwner, setIsOwner] = React.useState(null);
585
596
  const abortRef = React.useRef(null);
586
597
  const onErrorRef = React.useRef(onError);
587
598
  React.useEffect(() => {
588
599
  onErrorRef.current = onError;
589
600
  }, [onError]);
601
+ const onConversationNotFoundRef = React.useRef(onConversationNotFound);
602
+ React.useEffect(() => {
603
+ onConversationNotFoundRef.current = onConversationNotFound;
604
+ }, [onConversationNotFound]);
590
605
  const emitError = React.useCallback((envelope) => {
591
606
  if (mountedRef.current) {
592
607
  setError(envelope);
@@ -869,6 +884,7 @@ function useAiChat(input) {
869
884
  lastResponseDebug,
870
885
  conversationId: effectiveConversationId,
871
886
  lastLoopTrace,
887
+ isOwner,
872
888
  sendMessage,
873
889
  cancel,
874
890
  reset