@arbidocs/react 0.4.4 → 0.4.6

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 (76) hide show
  1. package/README.md +23 -0
  2. package/dist/artifacts/index.cjs +458 -0
  3. package/dist/artifacts/index.cjs.map +1 -0
  4. package/dist/artifacts/index.d.cts +276 -0
  5. package/dist/artifacts/index.d.ts +276 -0
  6. package/dist/artifacts/index.js +434 -0
  7. package/dist/artifacts/index.js.map +1 -0
  8. package/dist/artifacts/redline-core.cjs +20 -0
  9. package/dist/artifacts/redline-core.cjs.map +1 -0
  10. package/dist/artifacts/redline-core.d.cts +36 -0
  11. package/dist/artifacts/redline-core.d.ts +36 -0
  12. package/dist/artifacts/redline-core.js +3 -0
  13. package/dist/artifacts/redline-core.js.map +1 -0
  14. package/dist/{chunk-MIJRORQL.js → chunk-2ACHNDO6.js} +10 -304
  15. package/dist/chunk-2ACHNDO6.js.map +1 -0
  16. package/dist/chunk-CBJ3WOGD.cjs +317 -0
  17. package/dist/chunk-CBJ3WOGD.cjs.map +1 -0
  18. package/dist/{chunk-EJ2BMHYK.js → chunk-FSMIN5VX.js} +4 -3
  19. package/dist/chunk-FSMIN5VX.js.map +1 -0
  20. package/dist/{chunk-JKO3NR5L.js → chunk-HDCRPPBW.js} +5 -23
  21. package/dist/chunk-HDCRPPBW.js.map +1 -0
  22. package/dist/chunk-J4MX2W4H.js +26 -0
  23. package/dist/chunk-J4MX2W4H.js.map +1 -0
  24. package/dist/chunk-KWCHIAQ5.js +290 -0
  25. package/dist/chunk-KWCHIAQ5.js.map +1 -0
  26. package/dist/{chunk-6ALMBER7.js → chunk-N5SVBDLC.js} +4 -9
  27. package/dist/chunk-N5SVBDLC.js.map +1 -0
  28. package/dist/{chunk-RZQACYNQ.cjs → chunk-OBNTJEEO.cjs} +14 -20
  29. package/dist/chunk-OBNTJEEO.cjs.map +1 -0
  30. package/dist/chunk-OI55JRKT.cjs +334 -0
  31. package/dist/chunk-OI55JRKT.cjs.map +1 -0
  32. package/dist/{chunk-BXP6S34V.cjs → chunk-PGLV23SB.cjs} +13 -12
  33. package/dist/chunk-PGLV23SB.cjs.map +1 -0
  34. package/dist/chunk-RIHMIEXM.js +320 -0
  35. package/dist/chunk-RIHMIEXM.js.map +1 -0
  36. package/dist/chunk-RKWNX32A.js +299 -0
  37. package/dist/chunk-RKWNX32A.js.map +1 -0
  38. package/dist/chunk-S6A6FSG7.cjs +13 -0
  39. package/dist/chunk-S6A6FSG7.cjs.map +1 -0
  40. package/dist/chunk-SN5YV6FF.js +11 -0
  41. package/dist/chunk-SN5YV6FF.js.map +1 -0
  42. package/dist/{chunk-7IOCMCC5.cjs → chunk-TMPDW7J5.cjs} +3 -23
  43. package/dist/chunk-TMPDW7J5.cjs.map +1 -0
  44. package/dist/chunk-VJWAP2HJ.cjs +303 -0
  45. package/dist/chunk-VJWAP2HJ.cjs.map +1 -0
  46. package/dist/{chunk-ORKGCXG3.cjs → chunk-Z5YCRJY3.cjs} +125 -429
  47. package/dist/chunk-Z5YCRJY3.cjs.map +1 -0
  48. package/dist/chunk-ZMTLGO3K.cjs +29 -0
  49. package/dist/chunk-ZMTLGO3K.cjs.map +1 -0
  50. package/dist/index-CC6CvXEs.d.cts +1366 -0
  51. package/dist/index-CC6CvXEs.d.ts +1366 -0
  52. package/dist/index.cjs +1159 -722
  53. package/dist/index.cjs.map +1 -1
  54. package/dist/index.d.cts +588 -28
  55. package/dist/index.d.ts +588 -28
  56. package/dist/index.js +645 -310
  57. package/dist/index.js.map +1 -1
  58. package/dist/shell/index.cjs +8 -7
  59. package/dist/shell/index.js +3 -2
  60. package/dist/ui/index.cjs +158 -152
  61. package/dist/ui/index.d.cts +24 -1324
  62. package/dist/ui/index.d.ts +24 -1324
  63. package/dist/ui/index.js +4 -2
  64. package/dist/viewers/index.cjs +9 -8
  65. package/dist/viewers/index.cjs.map +1 -1
  66. package/dist/viewers/index.js +3 -2
  67. package/dist/viewers/index.js.map +1 -1
  68. package/package.json +19 -3
  69. package/dist/chunk-6ALMBER7.js.map +0 -1
  70. package/dist/chunk-7IOCMCC5.cjs.map +0 -1
  71. package/dist/chunk-BXP6S34V.cjs.map +0 -1
  72. package/dist/chunk-EJ2BMHYK.js.map +0 -1
  73. package/dist/chunk-JKO3NR5L.js.map +0 -1
  74. package/dist/chunk-MIJRORQL.js.map +0 -1
  75. package/dist/chunk-ORKGCXG3.cjs.map +0 -1
  76. package/dist/chunk-RZQACYNQ.cjs.map +0 -1
package/dist/index.js CHANGED
@@ -1,20 +1,27 @@
1
- import { formatRelativeTime } from './chunk-EJ2BMHYK.js';
2
- export { DEFAULT_NOTIFY_TONE, NotificationBell, Toaster, formatRelativeTime, notify, seedNotifications, selectUnread, toast, useNotify } from './chunk-EJ2BMHYK.js';
3
- import { normalizeCitationMarkup, MarkMenu, AiMarkdown, Card, Button, Badge, TagEntity, MarkdownRenderer, Input, FILE_TYPE_COLORS, getEntityIcon, UserEntity, getEntityColorStyles, AgentBadge, Textarea, Label, Slider, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DocumentEntity, DocumentCard, useAudioRecorder, MicWaveformIndicator, IconToggle, classifyMediaError, formatElapsed, PassageCard } from './chunk-MIJRORQL.js';
4
- export { AgentBadge, AiMarkdown, ArbiBotHeadIcon, AudIcon, Avatar, AvatarFallback, AvatarImage, BASE_COLORS, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DictateProvider, DocumentCard, DocumentEntity, ENTITY_MAPPINGS, EmlIcon, EntityIcon, EntityPill, FILE_TYPE_COLORS, HoverCard, HoverCardContent, HoverCardTrigger, IconToggle, ImgIcon, InlineMicButton, Input, Label, MarkMenu, MarkdownRenderer, MarkdownStreamingContext, MicRecordingOverlay, MicWaveformIndicator, NEUTRAL_GRAY, PLACEHOLDER_COLOR, PassageCard, PdfIcon, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, PresenceLabel, Progress, ProjectEntity, RadioGroup, RadioGroupItem, RagDot, RelevanceDot, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Slider, StatusPill, Switch, TAG_TYPE_ICONS, TAG_TYPE_LABELS, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, TagEntity, Textarea, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UserEntity, UserMenu, VISIBILITY_ICONS, VidIcon, VisibilityIcon, badgeVariants, buttonVariants, cardVariants, configureEntityRegistry, configureRagLabels, configureStatusRegistry, formatElapsed, getDocIcon, getEntityColor, getEntityColorStyles, getEntityIcon, getEntityLabel, getStatusVariant, getTagTypeIcon, getTagTypeLabel, getWorkspaceColor, markdownBaseComponents, normalizeCitationMarkup, splitMarkdownBlocks, toggleVariants, useAudioRecorder } from './chunk-MIJRORQL.js';
5
- import { cn, ScrollArea, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from './chunk-6ALMBER7.js';
6
- export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, ScrollArea, ScrollBar, cn } from './chunk-6ALMBER7.js';
7
- import { useArbi, AudioViewer, ImageViewer, VideoViewer } from './chunk-JKO3NR5L.js';
8
- export { ArbiProvider, useArbi } from './chunk-JKO3NR5L.js';
1
+ import { useDocuments, useConversations, useAssistantQuery, useConversationThreads, arbiQueryKeys, useWorkspaces, useParsedDocument, useTags, useActivity, useThumbnails } from './chunk-KWCHIAQ5.js';
2
+ export { DEFAULT_USD_PER_CREDIT, arbiQueryKeys, creditsToUsd, formatCredits, isUsageMetered, useActivity, useAgents, useAssistantQuery, useConfigs, useConversationThreads, useConversations, useDeleteAgents, useDocuments, useFacts, useHealth, useParsedDocument, useProjectInvoices, useProjectUsage, useProjects, useProjectsUsage, useSessions, useTags, useTasks, useThumbnails, useUserUsageToday, useWorkspaces } from './chunk-KWCHIAQ5.js';
3
+ import { MarkMenu, AiMarkdown, Card, Button, Badge, TagEntity, Input, FILE_TYPE_COLORS, getEntityIcon, UserEntity, getEntityColorStyles, AgentBadge, Textarea, Label, Slider, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DocumentEntity, DocumentCard, useAudioRecorder, MicWaveformIndicator, IconToggle, classifyMediaError, formatElapsed, PassageCard } from './chunk-2ACHNDO6.js';
4
+ export { AgentBadge, AiMarkdown, ArbiBotHeadIcon, AudIcon, Avatar, AvatarFallback, AvatarImage, BASE_COLORS, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DictateProvider, DocumentCard, DocumentEntity, ENTITY_MAPPINGS, EmlIcon, EntityIcon, EntityPill, FILE_TYPE_COLORS, HoverCard, HoverCardContent, HoverCardTrigger, IconToggle, ImgIcon, InlineMicButton, Input, Label, MarkMenu, MicRecordingOverlay, MicWaveformIndicator, NEUTRAL_GRAY, PLACEHOLDER_COLOR, PassageCard, PdfIcon, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, PresenceLabel, Progress, ProjectEntity, RadioGroup, RadioGroupItem, RagDot, RelevanceDot, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Slider, StatusPill, Switch, TAG_TYPE_ICONS, TAG_TYPE_LABELS, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, TagEntity, Textarea, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UserEntity, UserMenu, VISIBILITY_ICONS, VidIcon, VisibilityIcon, badgeVariants, buttonVariants, cardVariants, configureEntityRegistry, configureRagLabels, configureStatusRegistry, formatElapsed, getDocIcon, getEntityColor, getEntityColorStyles, getEntityIcon, getEntityLabel, getStatusVariant, getTagTypeIcon, getTagTypeLabel, getWorkspaceColor, toggleVariants, useAudioRecorder } from './chunk-2ACHNDO6.js';
5
+ import { normalizeCitationMarkup, MarkdownRenderer } from './chunk-RIHMIEXM.js';
6
+ export { MarkdownRenderer, MarkdownStreamingContext, markdownBaseComponents, normalizeCitationMarkup, splitMarkdownBlocks, trimOpenCitation } from './chunk-RIHMIEXM.js';
7
+ import { formatRelativeTime } from './chunk-FSMIN5VX.js';
8
+ export { DEFAULT_NOTIFY_TONE, NotificationBell, Toaster, formatRelativeTime, notify, seedNotifications, selectUnread, toast, useNotify } from './chunk-FSMIN5VX.js';
9
+ import { ScrollArea, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from './chunk-N5SVBDLC.js';
10
+ export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, ScrollArea, ScrollBar } from './chunk-N5SVBDLC.js';
11
+ import { cn } from './chunk-SN5YV6FF.js';
12
+ export { cn } from './chunk-SN5YV6FF.js';
13
+ import { AudioViewer, ImageViewer, VideoViewer } from './chunk-HDCRPPBW.js';
14
+ import { useArbi } from './chunk-J4MX2W4H.js';
15
+ export { ArbiProvider, useArbi } from './chunk-J4MX2W4H.js';
9
16
  import React, { memo, useState, createContext, useMemo, useCallback, useEffect, useRef, useLayoutEffect, useContext, useId, Fragment as Fragment$1 } from 'react';
10
- import { useQuery, useMutation, useQueries, useQueryClient } from '@tanstack/react-query';
17
+ import { resolveCitations, summarizeCitations, connectWithReconnect } from '@arbidocs/sdk/browser';
18
+ export { Arbi, ArbiError } from '@arbidocs/sdk/browser';
11
19
  import { Link, Trash2, CheckCheck, Check, Loader2, AlertTriangle, ShieldCheck, Copy, Sparkles, FileText, GitCompareArrows, Download, Image as Image$1, Search, CornerDownLeft, X, ArrowLeft, Send, Tags, Crown, Info, Activity, Gauge, Gift, PartyPopper, UserCheck, MessageCircleMore, FileX2, FileCheck2, Pencil, FolderPlus, SquareArrowOutUpRight, MessagesSquare, ArrowRight, MoreHorizontal, SquarePen, Mic, Pause, Play, Square, RotateCcw, Camera, Circle } from 'lucide-react';
12
20
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
13
- import { connectWithReconnect } from '@arbidocs/sdk/browser';
14
- export { Arbi, ArbiError } from '@arbidocs/sdk/browser';
15
- import { isMessageType } from '@arbidocs/client';
21
+ import { isMessageType, passwordChangeErrorMessage } from '@arbidocs/client';
16
22
  export { isMessageType } from '@arbidocs/client';
17
23
  import { create } from 'zustand';
24
+ import { useQueryClient, useMutation, useQuery } from '@tanstack/react-query';
18
25
  import { diffWords } from 'diff';
19
26
  import { AnimatePresence, motion } from 'framer-motion';
20
27
  import { createPortal } from 'react-dom';
@@ -23,274 +30,28 @@ import Cropper from 'react-easy-crop';
23
30
  import { AgGridReact } from 'ag-grid-react';
24
31
  import { ModuleRegistry, AllCommunityModule } from 'ag-grid-community';
25
32
 
26
- // src/credits.ts
27
- var DEFAULT_USD_PER_CREDIT = 0.01;
28
- function formatCredits(credits, options = {}) {
29
- const { unit = true, locale = "en-US" } = options;
30
- const whole = Math.round(Number.isFinite(credits) ? credits : 0);
31
- const digits = whole.toLocaleString(locale);
32
- return unit ? `${digits} ${whole === 1 ? "credit" : "credits"}` : digits;
33
- }
34
- function creditsToUsd(credits, usdPerCredit = DEFAULT_USD_PER_CREDIT) {
35
- return credits * usdPerCredit;
36
- }
37
- function isUsageMetered(usage, subscription) {
38
- const empty = usage.total_spend === 0 && usage.total_tokens === 0 && usage.total_requests === 0;
39
- return !(empty && subscription === "dev");
40
- }
41
-
42
- // src/hooks.ts
43
- var arbiQueryKeys = {
44
- workspaces: () => ["arbi", "workspaces"],
45
- documents: (workspaceId) => ["arbi", "documents", workspaceId],
46
- conversations: (workspaceId) => ["arbi", "conversations", workspaceId],
47
- tags: (workspaceId) => ["arbi", "tags", workspaceId],
48
- thumbnails: (docIds) => ["arbi", "thumbnails", [...docIds].sort().join(",")],
49
- parsedDoc: (docId, stage) => ["arbi", "parsedDoc", docId, stage],
50
- agents: () => ["arbi", "agents"],
51
- sessions: () => ["arbi", "sessions"],
52
- health: () => ["arbi", "health"],
53
- tasks: () => ["arbi", "tasks"],
54
- facts: (workspaceId) => ["arbi", "facts", workspaceId],
55
- configs: () => ["arbi", "configs"],
56
- activity: () => ["arbi", "activity"],
57
- projects: () => ["arbi", "projects"],
58
- projectUsage: (projectExtId, monthsBack) => ["arbi", "projectUsage", projectExtId, monthsBack],
59
- projectInvoices: (projectExtId) => ["arbi", "projectInvoices", projectExtId],
60
- userUsageToday: () => ["arbi", "userUsageToday"],
61
- contacts: () => ["arbi", "contacts"],
62
- skills: () => ["arbi", "skills"],
63
- taskRuns: (taskId) => ["arbi", "taskRuns", taskId],
64
- /** An agent's pinned config; `undefined` = the library default config. */
65
- agentConfig: (agentExtId) => ["arbi", "agentConfig", agentExtId ?? "default"],
66
- agentWorkspaces: (agentExtId) => ["arbi", "agentWorkspaces", agentExtId],
67
- models: () => ["arbi", "models"]
68
- };
69
- function useWorkspaces(options) {
70
- const arbi = useArbi();
71
- return useQuery({
72
- queryKey: arbiQueryKeys.workspaces(),
73
- queryFn: () => arbi.workspaces.list(),
74
- enabled: options?.enabled ?? arbi.isLoggedIn
75
- });
76
- }
77
- function useDocuments(workspaceId, options) {
78
- const arbi = useArbi();
79
- return useQuery({
80
- queryKey: arbiQueryKeys.documents(workspaceId ?? ""),
81
- queryFn: () => arbi.documents.list(),
82
- enabled: (options?.enabled ?? true) && !!workspaceId && arbi.hasWorkspace
83
- });
84
- }
85
- function useConversations(workspaceId, options) {
86
- const arbi = useArbi();
87
- return useQuery({
88
- queryKey: arbiQueryKeys.conversations(workspaceId ?? ""),
89
- queryFn: () => arbi.conversations.list(),
90
- enabled: (options?.enabled ?? true) && !!workspaceId && arbi.hasWorkspace
91
- });
92
- }
93
- function useTags(workspaceId, options) {
94
- const arbi = useArbi();
95
- return useQuery({
96
- queryKey: arbiQueryKeys.tags(workspaceId ?? ""),
97
- queryFn: () => arbi.tags.list(),
98
- enabled: (options?.enabled ?? true) && !!workspaceId && arbi.hasWorkspace
99
- });
100
- }
101
- function useThumbnails(docIds, options) {
102
- const arbi = useArbi();
103
- return useQuery({
104
- queryKey: arbiQueryKeys.thumbnails(docIds),
105
- queryFn: () => arbi.documents.getThumbnails(docIds),
106
- enabled: (options?.enabled ?? true) && docIds.length > 0 && arbi.hasWorkspace,
107
- staleTime: 5 * 6e4
108
- });
109
- }
110
- function useParsedDocument(docId, stage = "final", options) {
111
- const arbi = useArbi();
112
- return useQuery({
113
- queryKey: arbiQueryKeys.parsedDoc(docId ?? "", stage),
114
- queryFn: () => arbi.documents.getParsedContent(docId, stage),
115
- enabled: (options?.enabled ?? true) && !!docId && arbi.hasWorkspace
116
- });
117
- }
118
- function useAssistantQuery() {
119
- const arbi = useArbi();
120
- return useMutation({
121
- mutationFn: ({ question, ...options }) => arbi.assistant.query(question, options)
122
- });
123
- }
124
- function useAgents(options) {
125
- const arbi = useArbi();
126
- return useQuery({
127
- queryKey: arbiQueryKeys.agents(),
128
- queryFn: () => arbi.agents.list(),
129
- enabled: options?.enabled ?? arbi.isLoggedIn
130
- });
131
- }
132
- function useDeleteAgents() {
133
- const arbi = useArbi();
134
- return useMutation({
135
- mutationFn: ({ agentExtIds }) => arbi.agents.delete(agentExtIds)
136
- });
137
- }
138
- function useSessions(options) {
139
- const arbi = useArbi();
140
- return useQuery({
141
- queryKey: arbiQueryKeys.sessions(),
142
- queryFn: () => arbi.sessions.list(),
143
- enabled: options?.enabled ?? arbi.isLoggedIn
144
- });
145
- }
146
- function useHealth(options) {
147
- const arbi = useArbi();
148
- return useQuery({
149
- queryKey: arbiQueryKeys.health(),
150
- queryFn: () => arbi.health.check(),
151
- enabled: options?.enabled
152
- });
153
- }
154
- function useTasks(options) {
155
- const arbi = useArbi();
156
- return useQuery({
157
- queryKey: arbiQueryKeys.tasks(),
158
- queryFn: () => arbi.tasks.list(),
159
- enabled: options?.enabled ?? arbi.isLoggedIn
160
- });
161
- }
162
- function useFacts(workspaceId, options) {
163
- const arbi = useArbi();
164
- return useQuery({
165
- queryKey: arbiQueryKeys.facts(workspaceId ?? ""),
166
- queryFn: () => arbi.facts.list({ pinnedOnly: options?.pinnedOnly }),
167
- enabled: (options?.enabled ?? true) && !!workspaceId && arbi.hasWorkspace
168
- });
169
- }
170
- function useConfigs(options) {
171
- const arbi = useArbi();
172
- return useQuery({
173
- queryKey: arbiQueryKeys.configs(),
174
- queryFn: () => arbi.agentConfig.list(),
175
- enabled: options?.enabled ?? arbi.isLoggedIn
176
- });
33
+ function previewText(content) {
34
+ return content.replace(/\s+/g, " ").trim().replace(/^(?:(?:[-*•>]|#{1,6})\s+)+/, "");
177
35
  }
178
- function useActivity(options) {
179
- const arbi = useArbi();
180
- return useQuery({
181
- queryKey: arbiQueryKeys.activity(),
182
- queryFn: () => arbi.dm.listActivity({ limit: options?.limit, offset: options?.offset }),
183
- enabled: options?.enabled ?? arbi.isLoggedIn
184
- });
185
- }
186
- function useProjects(options) {
187
- const arbi = useArbi();
188
- return useQuery({
189
- queryKey: arbiQueryKeys.projects(),
190
- queryFn: () => arbi.projects.list(),
191
- enabled: options?.enabled ?? arbi.isLoggedIn
192
- });
193
- }
194
- function useProjectUsage(projectExtId, monthsBack = 0, options) {
195
- const arbi = useArbi();
196
- const usage = useQuery({
197
- queryKey: arbiQueryKeys.projectUsage(projectExtId ?? "", monthsBack),
198
- queryFn: () => arbi.projects.usage(projectExtId, monthsBack),
199
- enabled: (options?.enabled ?? true) && !!projectExtId && arbi.isLoggedIn
200
- });
201
- const projects = useProjects({ enabled: !!projectExtId && (options?.enabled ?? true) });
202
- const subscription = projects.data?.find((p) => p.external_id === projectExtId)?.subscription;
203
- const metered = usage.data ? isUsageMetered(usage.data, subscription) : void 0;
204
- return { ...usage, metered };
205
- }
206
- function useProjectsUsage(projectExtIds, monthsBack = 0, options) {
207
- const arbi = useArbi();
208
- const enabled = (options?.enabled ?? true) && arbi.isLoggedIn;
209
- const projects = useProjects({ enabled: enabled && projectExtIds.length > 0 });
210
- const results = useQueries({
211
- queries: projectExtIds.map((id) => ({
212
- queryKey: arbiQueryKeys.projectUsage(id, monthsBack),
213
- queryFn: () => arbi.projects.usage(id, monthsBack),
214
- enabled: enabled && !!id
215
- }))
216
- });
217
- const signature = results.map((r) => `${r.status}:${r.dataUpdatedAt ?? 0}`).join("|") + `#${projects.dataUpdatedAt}`;
218
- return useMemo(() => {
219
- const dailyMap = /* @__PURE__ */ new Map();
220
- const modelMap = /* @__PURE__ */ new Map();
221
- let totalCredits = 0;
222
- let anyData = false;
223
- let anyMetered = false;
224
- let totalTokens = 0;
225
- let totalRequests = 0;
226
- results.forEach((r, i) => {
227
- const d = r.data;
228
- if (!d) return;
229
- anyData = true;
230
- const subscription = projects.data?.find(
231
- (p) => p.external_id === projectExtIds[i]
232
- )?.subscription;
233
- if (isUsageMetered(d, subscription)) anyMetered = true;
234
- totalCredits += d.total_spend ?? 0;
235
- totalTokens += d.total_tokens ?? 0;
236
- totalRequests += d.total_requests ?? 0;
237
- for (const day of d.daily ?? []) {
238
- const cur = dailyMap.get(day.date) ?? {
239
- date: day.date,
240
- credits: 0,
241
- totalTokens: 0,
242
- apiRequests: 0
243
- };
244
- cur.credits += day.spend ?? 0;
245
- cur.totalTokens += day.total_tokens ?? 0;
246
- cur.apiRequests += day.api_requests ?? 0;
247
- dailyMap.set(day.date, cur);
248
- }
249
- for (const [model, mg] of Object.entries(d.model_groups ?? {})) {
250
- const cur = modelMap.get(model) ?? {
251
- model,
252
- credits: 0,
253
- totalTokens: 0,
254
- promptTokens: 0,
255
- completionTokens: 0,
256
- successfulRequests: 0
257
- };
258
- cur.credits += mg.spend ?? 0;
259
- cur.totalTokens += mg.total_tokens ?? 0;
260
- cur.promptTokens += mg.prompt_tokens ?? 0;
261
- cur.completionTokens += mg.completion_tokens ?? 0;
262
- cur.successfulRequests += mg.successful_requests ?? 0;
263
- modelMap.set(model, cur);
264
- }
265
- });
36
+ function toChatCitations(source) {
37
+ if (!source?.tools) return [];
38
+ const resolved = resolveCitations(source);
39
+ const summaries = summarizeCitations(resolved);
40
+ return resolved.map((r, i) => {
41
+ const summary = summaries[i];
266
42
  return {
267
- totalCredits,
268
- totalTokens,
269
- totalRequests,
270
- daily: [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date)),
271
- modelGroups: [...modelMap.values()].sort(
272
- (a, b) => b.credits - a.credits || b.totalTokens - a.totalTokens
273
- ),
274
- metered: !anyData || anyMetered,
275
- isLoading: results.some((r) => r.isLoading),
276
- isError: results.some((r) => r.isError)
43
+ num: r.citationNum,
44
+ statement: summary.statement,
45
+ documentTitle: summary.docTitle,
46
+ page: summary.pageNumber,
47
+ passages: r.chunks.map((chunk) => ({
48
+ id: chunk.metadata.chunk_ext_id,
49
+ documentId: chunk.metadata.doc_ext_id ?? null,
50
+ documentTitle: chunk.metadata.doc_title ?? summary.docTitle,
51
+ page: chunk.metadata.page_number ?? null,
52
+ text: previewText(chunk.content)
53
+ }))
277
54
  };
278
- }, [signature]);
279
- }
280
- function useProjectInvoices(projectExtId, options) {
281
- const arbi = useArbi();
282
- return useQuery({
283
- queryKey: arbiQueryKeys.projectInvoices(projectExtId ?? ""),
284
- queryFn: () => arbi.projects.invoices(projectExtId),
285
- enabled: (options?.enabled ?? true) && !!projectExtId && arbi.isLoggedIn
286
- });
287
- }
288
- function useUserUsageToday(options) {
289
- const arbi = useArbi();
290
- return useQuery({
291
- queryKey: arbiQueryKeys.userUsageToday(),
292
- queryFn: () => arbi.user.usageToday(),
293
- enabled: options?.enabled ?? arbi.isLoggedIn
294
55
  });
295
56
  }
296
57
 
@@ -298,11 +59,38 @@ function useUserUsageToday(options) {
298
59
  var counter = 0;
299
60
  var nextId = (prefix) => `${prefix}-${Date.now().toString(36)}-${(counter++).toString(36)}`;
300
61
  var emptyThread = () => ({
301
- id: nextId("thread"),
62
+ key: nextId("thread"),
63
+ conversationId: null,
302
64
  title: "",
303
65
  messages: [],
304
- responseId: null
66
+ responseId: null,
67
+ isLoaded: true
305
68
  });
69
+ var storedThread = (conversationId) => ({
70
+ key: conversationId,
71
+ conversationId,
72
+ title: "",
73
+ messages: [],
74
+ responseId: null,
75
+ isLoaded: false
76
+ });
77
+ var publicId = (t) => t.conversationId ?? t.key;
78
+ function messagesFromHistory(history) {
79
+ return history.filter((m) => m.role !== "system" && m.content.trim() !== "").map((m) => {
80
+ if (m.role === "user") {
81
+ return { id: m.external_id, role: "user", content: m.content, status: "complete" };
82
+ }
83
+ const citations = toChatCitations({ tools: m.tools });
84
+ return {
85
+ id: m.external_id,
86
+ role: "assistant",
87
+ content: m.content,
88
+ status: m.status === "failed" ? "error" : "complete",
89
+ ...m.status === "failed" ? { error: "The assistant request failed." } : {},
90
+ ...citations.length > 0 ? { citations } : {}
91
+ };
92
+ });
93
+ }
306
94
  function titleFromQuestion(question, maxLength = 48) {
307
95
  const flat = question.replace(/\s+/g, " ").trim();
308
96
  if (flat.length <= maxLength) return flat;
@@ -312,29 +100,88 @@ function titleFromQuestion(question, maxLength = 48) {
312
100
  return `${words.replace(/[\s,;:.\-–—]+$/, "")}\u2026`;
313
101
  }
314
102
  var messageOf = (e, fallback) => e instanceof Error ? e.message : fallback;
103
+ var isAbort = (e) => e instanceof DOMException && e.name === "AbortError";
104
+ var TITLE_REFRESH_DELAYS_MS = [2500, 6e3, 12e3];
315
105
  function useArbiChat(options = {}) {
316
106
  const { workspaceId, model, titleLength = 48 } = options;
317
107
  const arbi = useArbi();
318
108
  const { data: docs = [], isLoading: isLoadingDocuments, refetch } = useDocuments(workspaceId);
109
+ const {
110
+ data: stored = [],
111
+ isLoading: isLoadingThreads,
112
+ refetch: refetchConversations
113
+ } = useConversations(workspaceId);
319
114
  const query = useAssistantQuery();
320
115
  const { mutateAsync } = query;
321
116
  const [threads, setThreads] = useState(() => [emptyThread()]);
322
- const [activeId, setActiveId] = useState(() => threads[0].id);
117
+ const [activeKey, setActiveKey] = useState(() => threads[0].key);
323
118
  const [deselected, setDeselected] = useState(/* @__PURE__ */ new Set());
324
119
  const [isRunning, setIsRunning] = useState(false);
325
120
  const [isUploading, setIsUploading] = useState(false);
326
121
  const [uploadError, setUploadError] = useState(null);
122
+ const [historyError, setHistoryError] = useState(null);
123
+ const abortRef = useRef(null);
124
+ const timersRef = useRef([]);
125
+ useEffect(() => {
126
+ const timers = timersRef.current;
127
+ return () => timers.forEach(clearTimeout);
128
+ }, []);
327
129
  const scopeRef = useRef(workspaceId);
328
130
  useEffect(() => {
329
131
  if (scopeRef.current === workspaceId) return;
330
132
  scopeRef.current = workspaceId;
331
133
  const fresh = emptyThread();
332
134
  setThreads([fresh]);
333
- setActiveId(fresh.id);
135
+ setActiveKey(fresh.key);
334
136
  setDeselected(/* @__PURE__ */ new Set());
335
137
  setUploadError(null);
138
+ setHistoryError(null);
336
139
  }, [workspaceId]);
337
- const active = threads.find((t) => t.id === activeId) ?? threads[0];
140
+ const active = threads.find((t) => t.key === activeKey) ?? threads[0];
141
+ const threadEntries = useMemo(() => {
142
+ const storedIds = new Set(stored.map((c) => c.external_id));
143
+ const byConversation = new Map(
144
+ threads.filter((t) => t.conversationId).map((t) => [t.conversationId, t])
145
+ );
146
+ const fresh = threads.filter((t) => !t.conversationId);
147
+ const justSaved = threads.filter((t) => t.conversationId && !storedIds.has(t.conversationId));
148
+ const persisted = [...stored].filter((c) => c.message_count > 0 || byConversation.has(c.external_id)).sort((a, b) => b.updated_at.localeCompare(a.updated_at)).map((c) => {
149
+ const local = byConversation.get(c.external_id);
150
+ return {
151
+ key: local?.key ?? c.external_id,
152
+ id: c.external_id,
153
+ title: c.title || local?.title || ""
154
+ };
155
+ });
156
+ return [
157
+ ...fresh.map((t) => ({ key: t.key, id: publicId(t), title: t.title })),
158
+ ...justSaved.map((t) => ({ key: t.key, id: publicId(t), title: t.title })),
159
+ ...persisted
160
+ ];
161
+ }, [threads, stored]);
162
+ const needsLoad = !!active.conversationId && !active.isLoaded;
163
+ const loaded = useConversationThreads(workspaceId, active.conversationId ?? void 0, {
164
+ enabled: needsLoad
165
+ });
166
+ useEffect(() => {
167
+ const data = loaded.data;
168
+ if (!needsLoad || !data || data.conversation_ext_id !== active.conversationId) return;
169
+ const primary = [...data.threads].sort((a, b) => b.history.length - a.history.length)[0];
170
+ setThreads(
171
+ (all) => all.map(
172
+ (t) => t.key === active.key ? {
173
+ ...t,
174
+ isLoaded: true,
175
+ messages: messagesFromHistory(primary?.history ?? []),
176
+ responseId: primary?.leaf_message_ext_id ?? null
177
+ } : t
178
+ )
179
+ );
180
+ }, [loaded.data, needsLoad, active.conversationId, active.key]);
181
+ useEffect(() => {
182
+ if (loaded.error)
183
+ setHistoryError(messageOf(loaded.error, "The conversation could not be loaded."));
184
+ }, [loaded.error]);
338
185
  const selectedIds = useMemo(
339
186
  () => docs.map((d) => d.external_id).filter((id) => !!id && !deselected.has(id)),
340
187
  [docs, deselected]
@@ -348,16 +195,23 @@ function useArbiChat(options = {}) {
348
195
  [docs, deselected]
349
196
  );
350
197
  const patchThread = useCallback(
351
- (id, patch) => setThreads((all) => all.map((t) => t.id === id ? patch(t) : t)),
198
+ (key, patch) => setThreads((all) => all.map((t) => t.key === key ? patch(t) : t)),
352
199
  []
353
200
  );
354
201
  const patchMessage = useCallback(
355
- (threadId, messageId, patch) => patchThread(threadId, (t) => ({
202
+ (threadKey, messageId, patch) => patchThread(threadKey, (t) => ({
356
203
  ...t,
357
204
  messages: t.messages.map((m) => m.id === messageId ? { ...m, ...patch } : m)
358
205
  })),
359
206
  [patchThread]
360
207
  );
208
+ const refreshTitles = useCallback(() => {
209
+ void refetchConversations();
210
+ timersRef.current.forEach(clearTimeout);
211
+ timersRef.current = TITLE_REFRESH_DELAYS_MS.map(
212
+ (delay) => setTimeout(() => void refetchConversations(), delay)
213
+ );
214
+ }, [refetchConversations]);
361
215
  const ask = useCallback(
362
216
  async (thread, text) => {
363
217
  const userMessage = {
@@ -372,11 +226,13 @@ function useArbiChat(options = {}) {
372
226
  content: "",
373
227
  status: "streaming"
374
228
  };
375
- patchThread(thread.id, (t) => ({
229
+ patchThread(thread.key, (t) => ({
376
230
  ...t,
377
231
  title: t.title || titleFromQuestion(text, titleLength),
378
232
  messages: [...t.messages, userMessage, answer]
379
233
  }));
234
+ const controller = new AbortController();
235
+ abortRef.current = controller;
380
236
  setIsRunning(true);
381
237
  let buffer = "";
382
238
  let steps = 0;
@@ -387,15 +243,34 @@ function useArbiChat(options = {}) {
387
243
  docIds: selectedIds,
388
244
  previousResponseId: thread.responseId,
389
245
  model,
246
+ signal: controller.signal,
247
+ onStreamStart: (data) => {
248
+ if (data.assistant_message_ext_id) {
249
+ patchThread(thread.key, (t) => ({
250
+ ...t,
251
+ responseId: data.assistant_message_ext_id ?? t.responseId
252
+ }));
253
+ }
254
+ },
255
+ onUserMessage: (ev) => {
256
+ patchThread(thread.key, (t) => ({
257
+ ...t,
258
+ conversationId: t.conversationId ?? ev.conversation_ext_id
259
+ }));
260
+ },
261
+ onMetadata: (meta) => {
262
+ const citations = toChatCitations(meta);
263
+ if (citations.length > 0) patchMessage(thread.key, answer.id, { citations });
264
+ },
390
265
  onToken: (token) => {
391
266
  buffer += token;
392
- patchMessage(thread.id, answer.id, { content: buffer });
267
+ patchMessage(thread.key, answer.id, { content: buffer });
393
268
  },
394
269
  onOutputItemDone: (ev) => {
395
270
  const type = ev.item.type;
396
271
  if (type === "function_call" || type === "reasoning") {
397
272
  steps += 1;
398
- patchMessage(thread.id, answer.id, { stepCount: steps });
273
+ patchMessage(thread.key, answer.id, { stepCount: steps });
399
274
  }
400
275
  },
401
276
  onError: (message) => {
@@ -405,37 +280,50 @@ function useArbiChat(options = {}) {
405
280
  const finalText = result.text || buffer;
406
281
  const failure = streamError || result.errors[0] || null;
407
282
  if (failure && !finalText) {
408
- patchMessage(thread.id, answer.id, { status: "error", error: failure });
283
+ patchMessage(thread.key, answer.id, { status: "error", error: failure });
409
284
  } else {
410
- patchMessage(thread.id, answer.id, {
285
+ const citations = toChatCitations(result.metadata);
286
+ patchMessage(thread.key, answer.id, {
411
287
  status: "complete",
412
288
  content: finalText,
413
- stepCount: result.agentSteps.length || steps || void 0
289
+ stepCount: result.agentSteps.length || steps || void 0,
290
+ ...citations.length > 0 ? { citations } : {}
414
291
  });
415
- patchThread(thread.id, (t) => ({
292
+ patchThread(thread.key, (t) => ({
416
293
  ...t,
417
294
  responseId: result.assistantMessageExtId ?? t.responseId
418
295
  }));
419
296
  }
420
297
  } catch (e) {
421
- patchMessage(thread.id, answer.id, {
422
- status: "error",
423
- error: messageOf(e, "The assistant request failed.")
424
- });
298
+ if (controller.signal.aborted || isAbort(e)) {
299
+ patchMessage(thread.key, answer.id, {
300
+ status: "complete",
301
+ content: buffer,
302
+ stopped: true
303
+ });
304
+ } else {
305
+ patchMessage(thread.key, answer.id, {
306
+ status: "error",
307
+ error: messageOf(e, "The assistant request failed.")
308
+ });
309
+ }
425
310
  } finally {
311
+ if (abortRef.current === controller) abortRef.current = null;
426
312
  setIsRunning(false);
313
+ refreshTitles();
427
314
  }
428
315
  },
429
- [mutateAsync, patchMessage, patchThread, selectedIds, model, titleLength]
316
+ [mutateAsync, patchMessage, patchThread, refreshTitles, selectedIds, model, titleLength]
430
317
  );
431
318
  const send = useCallback(
432
319
  async (text) => {
433
320
  const trimmed = text.trim();
434
- if (!trimmed || isRunning) return;
321
+ if (!trimmed || isRunning || !active.isLoaded) return;
435
322
  await ask(active, trimmed);
436
323
  },
437
324
  [active, ask, isRunning]
438
325
  );
326
+ const stop = useCallback(() => abortRef.current?.abort(), []);
439
327
  const lastMessage = active.messages[active.messages.length - 1];
440
328
  const error = lastMessage?.status === "error" ? lastMessage.error ?? null : null;
441
329
  const retry = useCallback(async () => {
@@ -445,23 +333,54 @@ function useArbiChat(options = {}) {
445
333
  const question = messages[messages.length - 2];
446
334
  if (failed?.status !== "error" || question?.role !== "user") return;
447
335
  const trimmed = { ...active, messages: messages.slice(0, -2) };
448
- patchThread(active.id, () => trimmed);
336
+ patchThread(active.key, () => trimmed);
449
337
  await ask(trimmed, question.content);
450
338
  }, [active, ask, isRunning, patchThread]);
451
339
  const newThread = useCallback(() => {
452
- if (active.messages.length === 0) return;
340
+ setHistoryError(null);
341
+ const blank = threads.find((t) => !t.conversationId && t.messages.length === 0);
342
+ if (blank) {
343
+ setActiveKey(blank.key);
344
+ return;
345
+ }
453
346
  const fresh = emptyThread();
454
347
  setThreads((all) => [fresh, ...all]);
455
- setActiveId(fresh.id);
456
- }, [active.messages.length]);
348
+ setActiveKey(fresh.key);
349
+ }, [threads]);
350
+ const selectThread = useCallback(
351
+ (id) => {
352
+ const entry = threadEntries.find((e) => e.id === id);
353
+ if (!entry) return;
354
+ setHistoryError(null);
355
+ setThreads((all) => all.some((t) => t.key === entry.key) ? all : [...all, storedThread(id)]);
356
+ setActiveKey(entry.key);
357
+ },
358
+ [threadEntries]
359
+ );
457
360
  const deleteThread = useCallback(
458
361
  (id) => {
459
- const rest = threads.filter((t) => t.id !== id);
460
- const next = rest.length > 0 ? rest : [emptyThread()];
461
- setThreads(next);
462
- if (id === activeId) setActiveId(next[0].id);
362
+ const entry = threadEntries.find((e) => e.id === id);
363
+ if (!entry) return;
364
+ const remove = () => {
365
+ const rest = threads.filter((t) => t.key !== entry.key);
366
+ const next = rest.length > 0 ? rest : [emptyThread()];
367
+ setThreads(next);
368
+ if (entry.key === active.key) setActiveKey(next[0].key);
369
+ };
370
+ const local = threads.find((t) => t.key === entry.key);
371
+ const conversationId = local ? local.conversationId : id;
372
+ if (!conversationId) return remove();
373
+ void (async () => {
374
+ try {
375
+ await arbi.conversations.delete(conversationId);
376
+ remove();
377
+ await refetchConversations();
378
+ } catch (e) {
379
+ setHistoryError(messageOf(e, "The conversation could not be deleted."));
380
+ }
381
+ })();
463
382
  },
464
- [threads, activeId]
383
+ [threadEntries, threads, active.key, arbi, refetchConversations]
465
384
  );
466
385
  const toggleDocument = useCallback((id) => {
467
386
  setDeselected((prev) => {
@@ -489,18 +408,22 @@ function useArbiChat(options = {}) {
489
408
  [arbi, refetch]
490
409
  );
491
410
  return {
492
- threads: threads.map(({ id, title }) => ({ id, title })),
493
- activeThreadId: active.id,
411
+ threads: threadEntries.map(({ id, title }) => ({ id, title })),
412
+ activeThreadId: publicId(active),
494
413
  messages: active.messages,
495
414
  isRunning,
496
415
  error,
416
+ isLoadingThread: needsLoad && !historyError,
417
+ isLoadingThreads,
418
+ historyError,
497
419
  documents,
498
420
  isLoadingDocuments,
499
421
  isUploading,
500
422
  uploadError,
501
423
  send,
502
424
  retry,
503
- selectThread: setActiveId,
425
+ stop,
426
+ selectThread,
504
427
  newThread,
505
428
  deleteThread,
506
429
  toggleDocument,
@@ -1107,6 +1030,418 @@ function useRemoveContacts() {
1107
1030
  onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.contacts() })
1108
1031
  });
1109
1032
  }
1033
+ function toProfileView(profile) {
1034
+ return {
1035
+ ...profile,
1036
+ fullName: [profile.givenName, profile.familyName].map((part) => part?.trim()).filter(Boolean).join(" ")
1037
+ };
1038
+ }
1039
+ function useProfile(options) {
1040
+ const arbi = useArbi();
1041
+ return useQuery({
1042
+ queryKey: arbiQueryKeys.profile(),
1043
+ queryFn: () => {
1044
+ const profile = arbi.user.profile();
1045
+ return profile ? toProfileView(profile) : null;
1046
+ },
1047
+ enabled: (options?.enabled ?? true) && arbi.isLoggedIn,
1048
+ staleTime: Infinity
1049
+ });
1050
+ }
1051
+ function useUpdateProfile() {
1052
+ const arbi = useArbi();
1053
+ const qc = useQueryClient();
1054
+ return useMutation({
1055
+ mutationFn: async (vars) => {
1056
+ await arbi.profile.update({
1057
+ ...vars.givenName !== void 0 && { given_name: vars.givenName.trim() },
1058
+ ...vars.familyName !== void 0 && { family_name: vars.familyName.trim() },
1059
+ ...vars.picture !== void 0 && { picture: vars.picture }
1060
+ });
1061
+ const profile = arbi.user.profile();
1062
+ return profile ? toProfileView(profile) : null;
1063
+ },
1064
+ onSuccess: (profile) => {
1065
+ qc.setQueryData(arbiQueryKeys.profile(), profile);
1066
+ return qc.invalidateQueries({ queryKey: arbiQueryKeys.workspaces() });
1067
+ }
1068
+ });
1069
+ }
1070
+ function useNameEditor({ givenName, familyName, save }) {
1071
+ const [isEditing, setIsEditing] = useState(false);
1072
+ const [given, setGiven] = useState("");
1073
+ const [family, setFamily] = useState("");
1074
+ const [isSaving, setIsSaving] = useState(false);
1075
+ const [error, setError] = useState(null);
1076
+ const onStartEdit = useCallback(() => {
1077
+ setGiven(givenName ?? "");
1078
+ setFamily(familyName ?? "");
1079
+ setError(null);
1080
+ setIsEditing(true);
1081
+ }, [givenName, familyName]);
1082
+ const onSave = useCallback(() => {
1083
+ const trimmedGiven = given.trim();
1084
+ if (!trimmedGiven) return;
1085
+ setIsSaving(true);
1086
+ setError(null);
1087
+ void Promise.resolve().then(() => save({ givenName: trimmedGiven, familyName: family.trim() })).then(() => setIsEditing(false)).catch((e) => setError(e)).finally(() => setIsSaving(false));
1088
+ }, [given, family, save]);
1089
+ return {
1090
+ isEditing,
1091
+ givenName: given,
1092
+ familyName: family,
1093
+ onGivenNameChange: setGiven,
1094
+ onFamilyNameChange: setFamily,
1095
+ onStartEdit,
1096
+ onSave,
1097
+ onCancel: () => setIsEditing(false),
1098
+ isSaving,
1099
+ error
1100
+ };
1101
+ }
1102
+ var MAX_AVATAR_FILE_BYTES = 5 * 1024 * 1024;
1103
+ function useAvatarCrop({
1104
+ upload,
1105
+ onProblem,
1106
+ maxBytes = MAX_AVATAR_FILE_BYTES
1107
+ }) {
1108
+ const [pendingImage, setPendingImage] = useState(null);
1109
+ const [isUploading, setIsUploading] = useState(false);
1110
+ const onFileChosen = useCallback(
1111
+ (file) => {
1112
+ if (!file.type.startsWith("image/")) return onProblem?.("not-image");
1113
+ if (file.size > maxBytes) return onProblem?.("too-large");
1114
+ const reader = new FileReader();
1115
+ reader.onload = () => setPendingImage(reader.result);
1116
+ reader.onerror = () => onProblem?.("unreadable");
1117
+ reader.readAsDataURL(file);
1118
+ },
1119
+ [maxBytes, onProblem]
1120
+ );
1121
+ const onCropComplete = useCallback(
1122
+ (result) => {
1123
+ setPendingImage(null);
1124
+ setIsUploading(true);
1125
+ void Promise.resolve().then(() => upload(result.dataUrl)).catch(() => {
1126
+ }).finally(() => setIsUploading(false));
1127
+ },
1128
+ [upload]
1129
+ );
1130
+ return {
1131
+ onFileChosen,
1132
+ pendingImage,
1133
+ onCropComplete,
1134
+ onCropCancel: () => setPendingImage(null),
1135
+ isUploading
1136
+ };
1137
+ }
1138
+ function useRequestEmailChange() {
1139
+ const arbi = useArbi();
1140
+ return useMutation({
1141
+ mutationFn: (newEmail) => arbi.account.requestEmailChange(newEmail)
1142
+ });
1143
+ }
1144
+ function useConfirmEmailChange() {
1145
+ const arbi = useArbi();
1146
+ const qc = useQueryClient();
1147
+ return useMutation({
1148
+ mutationFn: ({ newEmail, code }) => arbi.account.confirmEmailChange(newEmail, code),
1149
+ onSuccess: () => {
1150
+ const profile = arbi.user.profile();
1151
+ qc.setQueryData(arbiQueryKeys.profile(), profile ? toProfileView(profile) : null);
1152
+ }
1153
+ });
1154
+ }
1155
+ function useEmailChange({
1156
+ requestCode,
1157
+ confirm,
1158
+ cooldownSeconds: cooldownLength = 30
1159
+ }) {
1160
+ const [isOpen, setIsOpen] = useState(false);
1161
+ const [step, setStep] = useState("enter");
1162
+ const [newEmail, setNewEmail] = useState("");
1163
+ const [code, setCode] = useState("");
1164
+ const [cooldown, setCooldown] = useState(0);
1165
+ const [isSending, setIsSending] = useState(false);
1166
+ const [isConfirming, setIsConfirming] = useState(false);
1167
+ const [error, setError] = useState(null);
1168
+ useEffect(() => {
1169
+ if (cooldown <= 0) return;
1170
+ const timer = setTimeout(() => setCooldown((c) => c - 1), 1e3);
1171
+ return () => clearTimeout(timer);
1172
+ }, [cooldown]);
1173
+ const reset = useCallback(() => {
1174
+ setIsOpen(false);
1175
+ setStep("enter");
1176
+ setNewEmail("");
1177
+ setCode("");
1178
+ setError(null);
1179
+ }, []);
1180
+ const onSendCode = useCallback(() => {
1181
+ const email = newEmail.trim();
1182
+ if (!email) return;
1183
+ setIsSending(true);
1184
+ setError(null);
1185
+ void Promise.resolve().then(() => requestCode(email)).then(() => {
1186
+ setStep("verify");
1187
+ setCooldown(cooldownLength);
1188
+ }).catch((e) => setError(e)).finally(() => setIsSending(false));
1189
+ }, [newEmail, requestCode, cooldownLength]);
1190
+ const onConfirm = useCallback(() => {
1191
+ const email = newEmail.trim();
1192
+ const entered = code.trim();
1193
+ if (!email || !entered) return;
1194
+ setIsConfirming(true);
1195
+ setError(null);
1196
+ void Promise.resolve().then(() => confirm({ newEmail: email, code: entered })).then(reset).catch((e) => setError(e)).finally(() => setIsConfirming(false));
1197
+ }, [newEmail, code, confirm, reset]);
1198
+ return {
1199
+ isOpen,
1200
+ onStart: () => setIsOpen(true),
1201
+ step,
1202
+ newEmail,
1203
+ onNewEmailChange: setNewEmail,
1204
+ onSendCode,
1205
+ isSending,
1206
+ code,
1207
+ onCodeChange: setCode,
1208
+ onConfirm,
1209
+ isConfirming,
1210
+ cooldownSeconds: cooldown,
1211
+ onCancel: reset,
1212
+ error
1213
+ };
1214
+ }
1215
+ function usePasswordChange() {
1216
+ const arbi = useArbi();
1217
+ return useMutation({
1218
+ mutationFn: (vars) => arbi.account.changePassword(vars)
1219
+ });
1220
+ }
1221
+ var DEFAULT_PASSWORD_CHANGE_FORM_MESSAGES = {
1222
+ required: "All fields are required",
1223
+ mismatch: "New passwords do not match",
1224
+ sameAsCurrent: "New password must be different from current password",
1225
+ wrongCurrent: "Current password is incorrect.",
1226
+ wrongRecoveryKey: "Recovery key did not match this account. Try a different recovery file."
1227
+ };
1228
+ function usePasswordChangeForm({
1229
+ recoveryMode = false,
1230
+ getUnmetRequirement,
1231
+ submit,
1232
+ onClose,
1233
+ onRecoveryComplete,
1234
+ messages
1235
+ }) {
1236
+ const m = useMemo(() => ({ ...DEFAULT_PASSWORD_CHANGE_FORM_MESSAGES, ...messages }), [messages]);
1237
+ const [currentPassword, setCurrentPassword] = useState("");
1238
+ const [newPassword, setNewPassword] = useState("");
1239
+ const [confirmPassword, setConfirmPassword] = useState("");
1240
+ const [error, setError] = useState(null);
1241
+ const [isProcessing, setIsProcessing] = useState(false);
1242
+ const [result, setResult] = useState(null);
1243
+ const clear = useCallback(() => {
1244
+ setCurrentPassword("");
1245
+ setNewPassword("");
1246
+ setConfirmPassword("");
1247
+ }, []);
1248
+ const isSubmitDisabled = isProcessing || !recoveryMode && !currentPassword || !newPassword || !confirmPassword || getUnmetRequirement(newPassword) !== null || newPassword !== confirmPassword;
1249
+ const onSubmit = useCallback(() => {
1250
+ setError(null);
1251
+ if (!recoveryMode && !currentPassword || !newPassword || !confirmPassword) {
1252
+ return setError(m.required);
1253
+ }
1254
+ if (newPassword !== confirmPassword) return setError(m.mismatch);
1255
+ const unmet = getUnmetRequirement(newPassword);
1256
+ if (unmet !== null) return setError(unmet);
1257
+ if (!recoveryMode && currentPassword === newPassword) return setError(m.sameAsCurrent);
1258
+ setIsProcessing(true);
1259
+ void submit({ newPassword, currentPassword: recoveryMode ? void 0 : currentPassword }).then((outcome) => {
1260
+ clear();
1261
+ if (recoveryMode) {
1262
+ onClose();
1263
+ onRecoveryComplete?.();
1264
+ } else {
1265
+ setResult(outcome);
1266
+ }
1267
+ }).catch((e) => {
1268
+ const message = passwordChangeErrorMessage(e);
1269
+ const wrongCurrent = /invalid signature|current password|401/i.test(message);
1270
+ setError(wrongCurrent ? recoveryMode ? m.wrongRecoveryKey : m.wrongCurrent : message);
1271
+ }).finally(() => setIsProcessing(false));
1272
+ }, [
1273
+ recoveryMode,
1274
+ currentPassword,
1275
+ newPassword,
1276
+ confirmPassword,
1277
+ getUnmetRequirement,
1278
+ submit,
1279
+ clear,
1280
+ onClose,
1281
+ onRecoveryComplete,
1282
+ m
1283
+ ]);
1284
+ return {
1285
+ currentPassword,
1286
+ onCurrentPasswordChange: setCurrentPassword,
1287
+ newPassword,
1288
+ onNewPasswordChange: setNewPassword,
1289
+ confirmPassword,
1290
+ onConfirmPasswordChange: setConfirmPassword,
1291
+ error,
1292
+ isProcessing,
1293
+ isSubmitDisabled,
1294
+ onSubmit,
1295
+ result,
1296
+ onCancel: () => {
1297
+ clear();
1298
+ setError(null);
1299
+ setResult(null);
1300
+ onClose();
1301
+ },
1302
+ onDone: () => {
1303
+ setResult(null);
1304
+ onClose();
1305
+ }
1306
+ };
1307
+ }
1308
+ function toNotificationPreferences(stored, keys) {
1309
+ const checked = Object.fromEntries(keys.map((key) => [key, stored?.[key] ?? true]));
1310
+ return {
1311
+ masterChecked: keys.some((key) => checked[key]),
1312
+ checked,
1313
+ masterPatch: (value) => Object.fromEntries(keys.map((key) => [key, value])),
1314
+ categoryPatch: (key, value) => ({ [key]: value })
1315
+ };
1316
+ }
1317
+ function useUserSettings(options) {
1318
+ const arbi = useArbi();
1319
+ return useQuery({
1320
+ queryKey: arbiQueryKeys.userSettings(),
1321
+ queryFn: () => arbi.settings.get(),
1322
+ enabled: (options?.enabled ?? true) && arbi.isLoggedIn
1323
+ });
1324
+ }
1325
+ function useNotificationPreferences(keys, options) {
1326
+ const arbi = useArbi();
1327
+ const qc = useQueryClient();
1328
+ const settings = useUserSettings(options);
1329
+ const stored = settings.data?.email_notifications;
1330
+ const save = useMutation({
1331
+ mutationFn: (patch) => arbi.settings.update({ email_notifications: patch }),
1332
+ onMutate: async (patch) => {
1333
+ await qc.cancelQueries({ queryKey: arbiQueryKeys.userSettings() });
1334
+ const previous = qc.getQueryData(arbiQueryKeys.userSettings());
1335
+ qc.setQueryData(
1336
+ arbiQueryKeys.userSettings(),
1337
+ (old) => old ? { ...old, email_notifications: { ...old.email_notifications, ...patch } } : old
1338
+ );
1339
+ return { previous };
1340
+ },
1341
+ onError: (_err, _patch, context) => {
1342
+ qc.setQueryData(arbiQueryKeys.userSettings(), context?.previous);
1343
+ },
1344
+ onSettled: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.userSettings() })
1345
+ });
1346
+ const model = useMemo(() => toNotificationPreferences(stored, keys), [stored, keys]);
1347
+ const setAll = useCallback(
1348
+ (value) => save.mutate(model.masterPatch(value)),
1349
+ [save, model]
1350
+ );
1351
+ const setCategory = useCallback(
1352
+ (key, value) => save.mutate(model.categoryPatch(key, value)),
1353
+ [save, model]
1354
+ );
1355
+ return {
1356
+ ...model,
1357
+ isLoading: settings.isLoading,
1358
+ isSaving: save.isPending,
1359
+ /** The master switch handler: every category on or off. */
1360
+ setAll,
1361
+ /** One category's handler. */
1362
+ setCategory
1363
+ };
1364
+ }
1365
+ function useTerminateSession() {
1366
+ const arbi = useArbi();
1367
+ const qc = useQueryClient();
1368
+ return useMutation({
1369
+ mutationFn: (sessionId) => arbi.sessions.terminate(sessionId),
1370
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.sessions() })
1371
+ });
1372
+ }
1373
+ function useLogoutAllSessions() {
1374
+ const arbi = useArbi();
1375
+ const qc = useQueryClient();
1376
+ return useMutation({
1377
+ mutationFn: () => arbi.sessions.logoutAll(),
1378
+ onSuccess: () => qc.clear()
1379
+ });
1380
+ }
1381
+ function useCurrentSessionId() {
1382
+ return useArbi().sessions.currentId();
1383
+ }
1384
+ function parseDeleteAccountBlockers(error) {
1385
+ const body = error?.apiError ?? error;
1386
+ const detail = body?.detail;
1387
+ if (detail === "active_subscription") return [{ type: "active_subscription" }];
1388
+ if (detail && typeof detail === "object") {
1389
+ const d = detail;
1390
+ if (d.reason === "workspace_members" && Array.isArray(d.workspaces)) {
1391
+ return [{ type: "workspace_members", workspaces: d.workspaces.map(String) }];
1392
+ }
1393
+ }
1394
+ return [];
1395
+ }
1396
+ function useDeleteAccount() {
1397
+ const arbi = useArbi();
1398
+ return useMutation({ mutationFn: () => arbi.account.requestDeletion() });
1399
+ }
1400
+ function useDeleteAccountFlow({
1401
+ userEmail,
1402
+ remove,
1403
+ onClose,
1404
+ onDeleted,
1405
+ onError
1406
+ }) {
1407
+ const [step, setStep] = useState("warning");
1408
+ const [confirmEmail, setConfirmEmail] = useState("");
1409
+ const [blockers, setBlockers] = useState([]);
1410
+ const [isDeleting, setIsDeleting] = useState(false);
1411
+ const close = useCallback(() => {
1412
+ setStep("warning");
1413
+ setConfirmEmail("");
1414
+ setBlockers([]);
1415
+ onClose();
1416
+ }, [onClose]);
1417
+ const onSubmit = useCallback(() => {
1418
+ if (confirmEmail.toLowerCase() !== userEmail.toLowerCase()) return;
1419
+ setIsDeleting(true);
1420
+ void Promise.resolve().then(remove).then(() => {
1421
+ close();
1422
+ onDeleted();
1423
+ }).catch((error) => {
1424
+ const found = parseDeleteAccountBlockers(error);
1425
+ if (found.length > 0) {
1426
+ setBlockers(found);
1427
+ setStep("warning");
1428
+ } else {
1429
+ onError?.(error);
1430
+ }
1431
+ }).finally(() => setIsDeleting(false));
1432
+ }, [confirmEmail, userEmail, remove, close, onDeleted, onError]);
1433
+ return {
1434
+ step,
1435
+ blockers,
1436
+ confirmEmail,
1437
+ onConfirmEmailChange: setConfirmEmail,
1438
+ onContinue: () => setStep("confirm"),
1439
+ onBack: () => setStep("warning"),
1440
+ isDeleting,
1441
+ onSubmit,
1442
+ onClose: close
1443
+ };
1444
+ }
1110
1445
  function useWorkspaceDocs(workspaceId) {
1111
1446
  const { data: docs = [], isLoading, isError } = useDocuments(workspaceId);
1112
1447
  const docIds = useMemo(
@@ -5332,6 +5667,6 @@ function hasVideoTrack(blob) {
5332
5667
  });
5333
5668
  }
5334
5669
 
5335
- export { ActivityFeed, AiActionCard, AiAnswer, AppleIcon, ArbiBotIcon, ArbiIcon, ArbiWebSocketProvider, ArtifactCanvas, ChatBubble, CitationRenderer, ClaudeIcon, CommandPalette, CommandPaletteProvider, ConversationList, DEFAULT_ASPECT_OPTIONS, DEFAULT_START_BY_EMAIL_LABELS, DEFAULT_USD_PER_CREDIT, DateSeparator, DirectMessages, DmAvatar, DmComposer, DocIcon, DocQaPanel, DocSearch, DocumentCardMenu, DocumentGallery, DocumentReview, DocumentTable, DraftTitleControls, FileIcon, FireworksIcon, GeminiIcon, GitHubIcon, GoogleIcon, HtmlIcon, ImageCropModal, ImageCropper, LinkedInIcon, MessageArtifactCard, MessageBubble, MessageThread, OpenAIIcon, PhotoCapture, ProviderIcon, RUN_WINDOW_MS, SemanticSearch, StartByEmail, TxtIcon, TypingIndicator, VideoCapture, VoiceNoteRecorder, XlsIcon, annotateThread, arbiQueryKeys, captureFrameAsBlob, creditsToUsd, cropImage, dayKey, describeActivity, formatCredits, getNotificationTypeMeta, getWorkspaceActionText, getWorkspaceKind, getWorkspaceSentPhrase, hasVideoTrack, isUsageMetered, isWorkspaceNotificationType, parseActivityContent, pickRecentWorkspaces, redlineStats, resolveActiveWorkspace, textToArtifact, titleFromQuestion, toRedlineMarkdown, toShellWorkspace, useActivity, useAddContacts, useAgentConfig, useAgentModels, useAgentWorkspaces, useAgents, useAiTask, useArbiChat, useArbiConnected, useArbiDocumentSync, useArbiShellWorkspaces, useArbiUiStore, useArbiWebSocket, useArbiWsContext, useArbiWsMessage, useArbiWsStatus, useArbiWsStore, useArbiWsTask, useArbiWsTasks, useAssistantQuery, useCameraStream, useCommandPalette, useConfigs, useContacts, useConversations, useCreateAgent, useCreateTask, useDeleteAgents, useDeleteTask, useDirectMessages, useDocuments, useFacts, useGrantAgentAccess, useHealth, useParsedDocument, usePendingContacts, useProjectInvoices, useProjectUsage, useProjects, useProjectsUsage, useRemoveContacts, useRevokeAgentAccess, useRunTask, useSaveAgentConfig, useSemanticSearch, useSessions, useShellWorkspaces, useSkills, useTags, useTaskRuns, useTasks, useThumbnails, useUpdateTask, useUserUsageToday, useVideoRecorder, useWorkspaceDocs, useWorkspaces, userDisplayName, userInitials };
5670
+ export { ActivityFeed, AiActionCard, AiAnswer, AppleIcon, ArbiBotIcon, ArbiIcon, ArbiWebSocketProvider, ArtifactCanvas, ChatBubble, CitationRenderer, ClaudeIcon, CommandPalette, CommandPaletteProvider, ConversationList, DEFAULT_ASPECT_OPTIONS, DEFAULT_PASSWORD_CHANGE_FORM_MESSAGES, DEFAULT_START_BY_EMAIL_LABELS, DateSeparator, DirectMessages, DmAvatar, DmComposer, DocIcon, DocQaPanel, DocSearch, DocumentCardMenu, DocumentGallery, DocumentReview, DocumentTable, DraftTitleControls, FileIcon, FireworksIcon, GeminiIcon, GitHubIcon, GoogleIcon, HtmlIcon, ImageCropModal, ImageCropper, LinkedInIcon, MAX_AVATAR_FILE_BYTES, MessageArtifactCard, MessageBubble, MessageThread, OpenAIIcon, PhotoCapture, ProviderIcon, RUN_WINDOW_MS, SemanticSearch, StartByEmail, TxtIcon, TypingIndicator, VideoCapture, VoiceNoteRecorder, XlsIcon, annotateThread, captureFrameAsBlob, cropImage, dayKey, describeActivity, getNotificationTypeMeta, getWorkspaceActionText, getWorkspaceKind, getWorkspaceSentPhrase, hasVideoTrack, isWorkspaceNotificationType, parseActivityContent, parseDeleteAccountBlockers, pickRecentWorkspaces, redlineStats, resolveActiveWorkspace, textToArtifact, titleFromQuestion, toChatCitations, toNotificationPreferences, toProfileView, toRedlineMarkdown, toShellWorkspace, useAddContacts, useAgentConfig, useAgentModels, useAgentWorkspaces, useAiTask, useArbiChat, useArbiConnected, useArbiDocumentSync, useArbiShellWorkspaces, useArbiUiStore, useArbiWebSocket, useArbiWsContext, useArbiWsMessage, useArbiWsStatus, useArbiWsStore, useArbiWsTask, useArbiWsTasks, useAvatarCrop, useCameraStream, useCommandPalette, useConfirmEmailChange, useContacts, useCreateAgent, useCreateTask, useCurrentSessionId, useDeleteAccount, useDeleteAccountFlow, useDeleteTask, useDirectMessages, useEmailChange, useGrantAgentAccess, useLogoutAllSessions, useNameEditor, useNotificationPreferences, usePasswordChange, usePasswordChangeForm, usePendingContacts, useProfile, useRemoveContacts, useRequestEmailChange, useRevokeAgentAccess, useRunTask, useSaveAgentConfig, useSemanticSearch, useShellWorkspaces, useSkills, useTaskRuns, useTerminateSession, useUpdateProfile, useUpdateTask, useUserSettings, useVideoRecorder, useWorkspaceDocs, userDisplayName, userInitials };
5336
5671
  //# sourceMappingURL=index.js.map
5337
5672
  //# sourceMappingURL=index.js.map