@open-mercato/ui 0.6.3-develop.3894.1.352abf4240 → 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.
- package/.turbo/turbo-build.log +2 -2
- package/dist/ai/AiAssistantLauncher.js +61 -8
- package/dist/ai/AiAssistantLauncher.js.map +2 -2
- package/dist/ai/AiChat.js +29 -6
- package/dist/ai/AiChat.js.map +2 -2
- package/dist/ai/AiChatSessions.js +31 -9
- package/dist/ai/AiChatSessions.js.map +2 -2
- package/dist/ai/AiDock.js +8 -1
- package/dist/ai/AiDock.js.map +2 -2
- package/dist/ai/ConversationShareButton.js +40 -0
- package/dist/ai/ConversationShareButton.js.map +7 -0
- package/dist/ai/ConversationShareDialog.js +234 -0
- package/dist/ai/ConversationShareDialog.js.map +7 -0
- package/dist/ai/conversation-store.js +4 -2
- package/dist/ai/conversation-store.js.map +2 -2
- package/dist/ai/index.js +4 -0
- package/dist/ai/index.js.map +2 -2
- package/dist/ai/useAiChat.js +20 -4
- package/dist/ai/useAiChat.js.map +2 -2
- package/dist/backend/charts/BarChart.js +19 -76
- package/dist/backend/charts/BarChart.js.map +2 -2
- package/dist/backend/charts/BarChartImpl.js +91 -0
- package/dist/backend/charts/BarChartImpl.js.map +7 -0
- package/dist/backend/charts/LineChart.js +18 -91
- package/dist/backend/charts/LineChart.js.map +2 -2
- package/dist/backend/charts/LineChartImpl.js +112 -0
- package/dist/backend/charts/LineChartImpl.js.map +7 -0
- package/dist/backend/charts/PieChart.js +17 -62
- package/dist/backend/charts/PieChart.js.map +2 -2
- package/dist/backend/charts/PieChartImpl.js +76 -0
- package/dist/backend/charts/PieChartImpl.js.map +7 -0
- package/dist/backend/dashboard/DashboardScreen.js +9 -1
- package/dist/backend/dashboard/DashboardScreen.js.map +2 -2
- package/dist/backend/dashboard/widgetRegistry.js +18 -2
- package/dist/backend/dashboard/widgetRegistry.js.map +2 -2
- package/dist/backend/icons/lucideRegistry.generated.js +2 -0
- package/dist/backend/icons/lucideRegistry.generated.js.map +2 -2
- package/dist/backend/injection/useInjectionDataWidgets.js +12 -2
- package/dist/backend/injection/useInjectionDataWidgets.js.map +2 -2
- package/package.json +4 -5
- package/src/ai/AiAssistantLauncher.tsx +81 -5
- package/src/ai/AiChat.tsx +42 -2
- package/src/ai/AiChatSessions.tsx +55 -9
- package/src/ai/AiDock.tsx +13 -0
- package/src/ai/ConversationShareButton.tsx +39 -0
- package/src/ai/ConversationShareDialog.tsx +299 -0
- package/src/ai/__tests__/AiAssistantLauncher.test.tsx +6 -0
- package/src/ai/__tests__/AiChat.conversation.test.tsx +97 -0
- package/src/ai/__tests__/AiChatSessions.test.tsx +228 -0
- package/src/ai/__tests__/AiDock.test.tsx +31 -1
- package/src/ai/__tests__/conversation-store.test.ts +74 -1
- package/src/ai/conversation-store.ts +22 -2
- package/src/ai/index.ts +2 -0
- package/src/ai/useAiChat.ts +47 -4
- package/src/backend/__tests__/lazy-heavy-libraries.test.ts +53 -0
- package/src/backend/charts/BarChart.tsx +21 -72
- package/src/backend/charts/BarChartImpl.tsx +99 -0
- package/src/backend/charts/LineChart.tsx +23 -85
- package/src/backend/charts/LineChartImpl.tsx +120 -0
- package/src/backend/charts/PieChart.tsx +19 -60
- package/src/backend/charts/PieChartImpl.tsx +82 -0
- package/src/backend/dashboard/DashboardScreen.tsx +9 -1
- package/src/backend/dashboard/__tests__/DashboardScreen.test.tsx +45 -0
- package/src/backend/dashboard/__tests__/widgetRegistry.test.ts +60 -0
- package/src/backend/dashboard/widgetRegistry.ts +23 -5
- package/src/backend/icons/lucideRegistry.generated.tsx +2 -0
- 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
|
-
|
|
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<
|
|
5
|
-
"mappings": ";AAIA,MAAM,yBAAyB;
|
|
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,
|
package/dist/ai/index.js.map
CHANGED
|
@@ -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
|
}
|
package/dist/ai/useAiChat.js
CHANGED
|
@@ -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
|
|
541
|
+
const transcriptResult = await loadAiServerTranscript(effectiveConversationId, { limit: 100 });
|
|
542
542
|
if (cancelled || latestStatusRef.current !== "idle") return;
|
|
543
|
-
if (
|
|
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 (!
|
|
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
|