@arbidocs/react 0.4.2 → 0.4.4

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/dist/index.js CHANGED
@@ -1,17 +1,17 @@
1
1
  import { formatRelativeTime } from './chunk-EJ2BMHYK.js';
2
2
  export { DEFAULT_NOTIFY_TONE, NotificationBell, Toaster, formatRelativeTime, notify, seedNotifications, selectUnread, toast, useNotify } from './chunk-EJ2BMHYK.js';
3
- import { MarkMenu, AiMarkdown, Card, Button, Badge, TagEntity, MarkdownRenderer, Input, FILE_TYPE_COLORS, getEntityIcon, UserEntity, getEntityColorStyles, AgentBadge, Textarea, Slider, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DocumentEntity, DocumentCard, useAudioRecorder, MicWaveformIndicator, IconToggle, classifyMediaError, formatElapsed, PassageCard } from './chunk-3CLGHHME.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, splitMarkdownBlocks, toggleVariants, useAudioRecorder } from './chunk-3CLGHHME.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
5
  import { cn, ScrollArea, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from './chunk-6ALMBER7.js';
6
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, ArbiProvider, AudioViewer, ImageViewer, VideoViewer } from './chunk-JKO3NR5L.js';
7
+ import { useArbi, AudioViewer, ImageViewer, VideoViewer } from './chunk-JKO3NR5L.js';
8
8
  export { ArbiProvider, useArbi } from './chunk-JKO3NR5L.js';
9
9
  import React, { memo, useState, createContext, useMemo, useCallback, useEffect, useRef, useLayoutEffect, useContext, useId, Fragment as Fragment$1 } from 'react';
10
- import { useQuery, useMutation, useQueries, QueryClient, QueryClientProvider, useQueryClient } from '@tanstack/react-query';
10
+ import { useQuery, useMutation, useQueries, useQueryClient } from '@tanstack/react-query';
11
+ 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';
11
12
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
12
- import { connectWithReconnect, resolveCitations, summarizeCitations } from '@arbidocs/sdk/browser';
13
+ import { connectWithReconnect } from '@arbidocs/sdk/browser';
13
14
  export { Arbi, ArbiError } from '@arbidocs/sdk/browser';
14
- 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';
15
15
  import { isMessageType } from '@arbidocs/client';
16
16
  export { isMessageType } from '@arbidocs/client';
17
17
  import { create } from 'zustand';
@@ -23,566 +23,23 @@ import Cropper from 'react-easy-crop';
23
23
  import { AgGridReact } from 'ag-grid-react';
24
24
  import { ModuleRegistry, AllCommunityModule } from 'ag-grid-community';
25
25
 
26
- // src/chat/styles.ts
27
- var DEFAULT_THEME = {
28
- primaryColor: "#00adef",
29
- backgroundColor: "#ffffff",
30
- surfaceColor: "#f9fafb",
31
- textColor: "#111827",
32
- mutedColor: "#6b7280",
33
- borderColor: "#e5e7eb",
34
- userBubbleColor: "#f3f4f6",
35
- userBubbleTextColor: "#111827",
36
- assistantBubbleColor: "rgba(0, 173, 239, 0.08)",
37
- assistantBubbleTextColor: "#111827",
38
- borderRadius: "8px",
39
- fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif"
40
- };
41
- var DEFAULT_LABELS = {
42
- title: "Chat",
43
- email: "Email",
44
- password: "Password",
45
- loginButton: "Log in",
46
- loginButtonLoading: "Logging in...",
47
- registerButton: "Create account",
48
- sendCodeButton: "Email me a code",
49
- haveCodeButton: "I have an invitation code",
50
- verificationCode: "Verification code",
51
- confirmPassword: "Confirm password",
52
- firstName: "First name",
53
- lastName: "Last name",
54
- switchToRegister: "Create account",
55
- switchToLogin: "Back to login",
56
- selectWorkspace: "Select a workspace",
57
- workspacePlaceholder: "Choose...",
58
- continueButton: "Continue",
59
- createWorkspace: "Create new workspace",
60
- workspaceName: "Workspace name",
61
- createButton: "Create",
62
- changeWorkspace: "Change workspace",
63
- documents: "Documents",
64
- sendButton: "Send",
65
- placeholder: "Ask a question...",
66
- uploadButton: "Upload",
67
- uploading: "Uploading...",
68
- emptyNoDocs: "No documents found in this workspace.",
69
- emptyNoSelection: "Select at least one document to start chatting.",
70
- emptyReady: "Ask a question about your documents."
71
- };
72
- function getTheme(partial) {
73
- return { ...DEFAULT_THEME, ...partial };
74
- }
75
- function getLabels(partial) {
76
- return { ...DEFAULT_LABELS, ...partial };
77
- }
78
- function containerStyle(theme, width, height) {
79
- return {
80
- width: typeof width === "number" ? `${width}px` : width,
81
- height: typeof height === "number" ? `${height}px` : height,
82
- display: "flex",
83
- flexDirection: "column",
84
- fontFamily: theme.fontFamily,
85
- fontSize: "14px",
86
- lineHeight: "1.5",
87
- color: theme.textColor,
88
- backgroundColor: theme.backgroundColor,
89
- border: `1px solid ${theme.borderColor}`,
90
- borderRadius: theme.borderRadius,
91
- overflow: "hidden",
92
- boxSizing: "border-box"
93
- };
94
- }
95
- function headerStyle(theme) {
96
- return {
97
- display: "flex",
98
- alignItems: "center",
99
- padding: "12px 16px",
100
- fontWeight: 600,
101
- fontSize: "15px",
102
- borderBottom: `1px solid ${theme.borderColor}`,
103
- flexShrink: 0
104
- };
105
- }
106
- function bodyStyle() {
107
- return {
108
- flex: 1,
109
- display: "flex",
110
- flexDirection: "column",
111
- overflow: "hidden"
112
- };
113
- }
114
- function formContainerStyle() {
115
- return {
116
- flex: 1,
117
- display: "flex",
118
- flexDirection: "column",
119
- justifyContent: "center",
120
- padding: "24px 16px",
121
- gap: "12px"
122
- };
123
- }
124
- function labelStyle(theme) {
125
- return {
126
- fontSize: "13px",
127
- fontWeight: 500,
128
- color: theme.textColor,
129
- marginBottom: "4px"
130
- };
131
- }
132
- function inputStyle(theme) {
133
- return {
134
- width: "100%",
135
- padding: "8px 12px",
136
- fontSize: "14px",
137
- fontFamily: theme.fontFamily,
138
- color: theme.textColor,
139
- backgroundColor: theme.backgroundColor,
140
- border: `1px solid ${theme.borderColor}`,
141
- borderRadius: "8px",
142
- outline: "none",
143
- boxSizing: "border-box"
144
- };
145
- }
146
- function selectStyle(theme) {
147
- return {
148
- ...inputStyle(theme),
149
- cursor: "pointer",
150
- appearance: "auto"
151
- };
152
- }
153
- function buttonStyle(theme, disabled) {
154
- return {
155
- padding: "8px 16px",
156
- fontSize: "14px",
157
- fontWeight: 500,
158
- fontFamily: theme.fontFamily,
159
- color: "#ffffff",
160
- backgroundColor: disabled ? theme.mutedColor : theme.primaryColor,
161
- border: "none",
162
- borderRadius: "8px",
163
- cursor: disabled ? "not-allowed" : "pointer",
164
- opacity: disabled ? 0.6 : 1,
165
- transition: "opacity 150ms"
166
- };
167
- }
168
- function errorStyle() {
169
- return {
170
- fontSize: "13px",
171
- color: "#ef4444",
172
- margin: "4px 0 0"
173
- };
174
- }
175
- function messagesContainerStyle() {
176
- return {
177
- flex: 1,
178
- overflowY: "auto",
179
- padding: "12px 16px",
180
- display: "flex",
181
- flexDirection: "column",
182
- gap: "8px"
183
- };
184
- }
185
- function messageBubbleStyle(theme, role) {
186
- const isUser = role === "user";
187
- return {
188
- maxWidth: "85%",
189
- padding: "8px 12px",
190
- borderRadius: "12px",
191
- fontSize: "14px",
192
- lineHeight: "1.5",
193
- wordBreak: "break-word",
194
- whiteSpace: "pre-wrap",
195
- alignSelf: isUser ? "flex-end" : "flex-start",
196
- backgroundColor: isUser ? theme.userBubbleColor : theme.assistantBubbleColor,
197
- color: isUser ? theme.userBubbleTextColor : theme.assistantBubbleTextColor
198
- };
199
- }
200
- function inputBarStyle(theme) {
201
- return {
202
- display: "flex",
203
- gap: "8px",
204
- padding: "12px 16px",
205
- borderTop: `1px solid ${theme.borderColor}`,
206
- flexShrink: 0
207
- };
208
- }
209
- function chatInputStyle(theme) {
210
- return {
211
- ...inputStyle(theme),
212
- flex: 1
213
- };
214
- }
215
- function sendButtonStyle(theme, disabled) {
216
- return {
217
- ...buttonStyle(theme, disabled),
218
- padding: "8px 14px",
219
- flexShrink: 0
220
- };
221
- }
222
- function docSelectorStyle(theme) {
223
- return {
224
- padding: "8px 16px",
225
- borderBottom: `1px solid ${theme.borderColor}`,
226
- flexShrink: 0
227
- };
228
- }
229
- function docToggleStyle(theme) {
230
- return {
231
- display: "flex",
232
- alignItems: "center",
233
- gap: "6px",
234
- fontSize: "13px",
235
- fontWeight: 500,
236
- color: theme.mutedColor,
237
- cursor: "pointer",
238
- background: "none",
239
- border: "none",
240
- padding: 0,
241
- fontFamily: theme.fontFamily
242
- };
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;
243
33
  }
244
- function docListStyle() {
245
- return {
246
- marginTop: "6px",
247
- display: "flex",
248
- flexDirection: "column",
249
- gap: "4px",
250
- maxHeight: "120px",
251
- overflowY: "auto"
252
- };
34
+ function creditsToUsd(credits, usdPerCredit = DEFAULT_USD_PER_CREDIT) {
35
+ return credits * usdPerCredit;
253
36
  }
254
- function docItemStyle(theme) {
255
- return {
256
- display: "flex",
257
- alignItems: "center",
258
- gap: "6px",
259
- fontSize: "13px",
260
- color: theme.textColor,
261
- cursor: "pointer",
262
- textAlign: "left"
263
- };
264
- }
265
- function emptyStateStyle(theme) {
266
- return {
267
- flex: 1,
268
- display: "flex",
269
- alignItems: "center",
270
- justifyContent: "center",
271
- color: theme.mutedColor,
272
- fontSize: "13px",
273
- padding: "24px",
274
- textAlign: "center"
275
- };
276
- }
277
- function loadingContainerStyle() {
278
- return {
279
- flex: 1,
280
- display: "flex",
281
- alignItems: "center",
282
- justifyContent: "center",
283
- gap: "8px",
284
- fontSize: "13px"
285
- };
286
- }
287
- function brandingStyle(theme) {
288
- return {
289
- display: "flex",
290
- alignItems: "center",
291
- justifyContent: "center",
292
- gap: "4px",
293
- padding: "6px 16px",
294
- fontSize: "11px",
295
- color: theme.mutedColor,
296
- borderTop: `1px solid ${theme.borderColor}`,
297
- flexShrink: 0,
298
- userSelect: "none"
299
- };
300
- }
301
- function brandingLinkStyle(theme) {
302
- return {
303
- color: theme.mutedColor,
304
- fontWeight: 600,
305
- textDecoration: "none"
306
- };
307
- }
308
- function linkButtonStyle(theme) {
309
- return {
310
- background: "none",
311
- border: "none",
312
- padding: 0,
313
- fontSize: "13px",
314
- color: theme.primaryColor,
315
- cursor: "pointer",
316
- fontFamily: theme.fontFamily,
317
- textDecoration: "underline"
318
- };
319
- }
320
- function secondaryButtonStyle(theme, disabled) {
321
- return {
322
- padding: "4px 10px",
323
- fontSize: "12px",
324
- fontWeight: 500,
325
- fontFamily: theme.fontFamily,
326
- color: disabled ? theme.mutedColor : theme.primaryColor,
327
- backgroundColor: "transparent",
328
- border: `1px solid ${disabled ? theme.borderColor : theme.primaryColor}`,
329
- borderRadius: "6px",
330
- cursor: disabled ? "not-allowed" : "pointer",
331
- opacity: disabled ? 0.6 : 1,
332
- transition: "opacity 150ms",
333
- whiteSpace: "nowrap"
334
- };
335
- }
336
- function fileInputStyle() {
337
- return { display: "none" };
338
- }
339
- function agentStatusStyle(theme) {
340
- return {
341
- fontSize: "12px",
342
- color: theme.mutedColor,
343
- fontStyle: "italic",
344
- padding: "4px 0",
345
- alignSelf: "flex-start"
346
- };
347
- }
348
- function AuthScreen({ theme, labels, onLogin }) {
349
- const arbi = useArbi();
350
- const [mode, setMode] = useState("login");
351
- const [step, setStep] = useState("email");
352
- const [email, setEmail] = useState("");
353
- const [password, setPassword] = useState("");
354
- const [confirmPw, setConfirmPw] = useState("");
355
- const [code, setCode] = useState("");
356
- const [firstName, setFirstName] = useState("");
357
- const [lastName, setLastName] = useState("");
358
- const [loading, setLoading] = useState(false);
359
- const [error, setError] = useState("");
360
- const resetForm = () => {
361
- setPassword("");
362
- setConfirmPw("");
363
- setCode("");
364
- setFirstName("");
365
- setLastName("");
366
- setError("");
367
- setStep("email");
368
- };
369
- const switchMode = (next) => {
370
- resetForm();
371
- setMode(next);
372
- };
373
- const handleLogin = async (e) => {
374
- e.preventDefault();
375
- setLoading(true);
376
- setError("");
377
- try {
378
- await arbi.login(email, password);
379
- onLogin();
380
- } catch (err) {
381
- setError(err instanceof Error ? err.message : "Login failed");
382
- } finally {
383
- setLoading(false);
384
- }
385
- };
386
- const handleHaveCode = () => {
387
- setError("");
388
- setStep("verify");
389
- };
390
- const handleEmailCode = async () => {
391
- if (!email) return;
392
- setLoading(true);
393
- setError("");
394
- try {
395
- await arbi.requestVerification(email);
396
- setStep("verify");
397
- } catch (err) {
398
- setError(err instanceof Error ? err.message : "Failed to send verification code");
399
- } finally {
400
- setLoading(false);
401
- }
402
- };
403
- const handleRegister = async (e) => {
404
- e.preventDefault();
405
- if (password !== confirmPw) {
406
- setError("Passwords do not match");
407
- return;
408
- }
409
- setLoading(true);
410
- setError("");
411
- try {
412
- await arbi.register({ email, password, verificationCode: code, firstName, lastName });
413
- await arbi.login(email, password);
414
- onLogin();
415
- } catch (err) {
416
- setError(err instanceof Error ? err.message : "Registration failed");
417
- } finally {
418
- setLoading(false);
419
- }
420
- };
421
- if (mode === "login") {
422
- return /* @__PURE__ */ jsxs("form", { onSubmit: handleLogin, style: formContainerStyle(), children: [
423
- /* @__PURE__ */ jsxs("div", { children: [
424
- /* @__PURE__ */ jsx("div", { style: labelStyle(theme), children: labels.email }),
425
- /* @__PURE__ */ jsx(
426
- "input",
427
- {
428
- type: "email",
429
- value: email,
430
- onChange: (e) => setEmail(e.target.value),
431
- placeholder: "you@example.com",
432
- autoComplete: "email",
433
- required: true,
434
- style: inputStyle(theme)
435
- }
436
- )
437
- ] }),
438
- /* @__PURE__ */ jsxs("div", { children: [
439
- /* @__PURE__ */ jsx("div", { style: labelStyle(theme), children: labels.password }),
440
- /* @__PURE__ */ jsx(
441
- "input",
442
- {
443
- type: "password",
444
- value: password,
445
- onChange: (e) => setPassword(e.target.value),
446
- autoComplete: "current-password",
447
- required: true,
448
- style: inputStyle(theme)
449
- }
450
- )
451
- ] }),
452
- /* @__PURE__ */ jsx("button", { type: "submit", disabled: loading, style: buttonStyle(theme, loading), children: loading ? labels.loginButtonLoading : labels.loginButton }),
453
- error && /* @__PURE__ */ jsx("div", { style: errorStyle(), children: error }),
454
- /* @__PURE__ */ jsx("div", { style: { textAlign: "center" }, children: /* @__PURE__ */ jsx(
455
- "button",
456
- {
457
- type: "button",
458
- onClick: () => switchMode("register"),
459
- style: linkButtonStyle(theme),
460
- children: labels.switchToRegister
461
- }
462
- ) })
463
- ] });
464
- }
465
- if (step === "email") {
466
- const noEmail = !email.trim();
467
- return /* @__PURE__ */ jsxs("div", { style: formContainerStyle(), children: [
468
- /* @__PURE__ */ jsxs("div", { children: [
469
- /* @__PURE__ */ jsx("div", { style: labelStyle(theme), children: labels.email }),
470
- /* @__PURE__ */ jsx(
471
- "input",
472
- {
473
- type: "email",
474
- value: email,
475
- onChange: (e) => setEmail(e.target.value),
476
- placeholder: "you@example.com",
477
- autoComplete: "email",
478
- style: inputStyle(theme)
479
- }
480
- )
481
- ] }),
482
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px" }, children: [
483
- /* @__PURE__ */ jsx(
484
- "button",
485
- {
486
- type: "button",
487
- onClick: handleHaveCode,
488
- disabled: noEmail,
489
- style: { ...buttonStyle(theme, noEmail), flex: 1 },
490
- children: labels.haveCodeButton
491
- }
492
- ),
493
- /* @__PURE__ */ jsx(
494
- "button",
495
- {
496
- type: "button",
497
- onClick: handleEmailCode,
498
- disabled: noEmail || loading,
499
- style: { ...buttonStyle(theme, noEmail || loading), flex: 1 },
500
- children: loading ? "..." : labels.sendCodeButton
501
- }
502
- )
503
- ] }),
504
- error && /* @__PURE__ */ jsx("div", { style: errorStyle(), children: error }),
505
- /* @__PURE__ */ jsx("div", { style: { textAlign: "center" }, children: /* @__PURE__ */ jsx("button", { type: "button", onClick: () => switchMode("login"), style: linkButtonStyle(theme), children: labels.switchToLogin }) })
506
- ] });
507
- }
508
- return /* @__PURE__ */ jsxs("form", { onSubmit: handleRegister, style: formContainerStyle(), children: [
509
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px" }, children: [
510
- /* @__PURE__ */ jsxs("div", { style: { flex: 1 }, children: [
511
- /* @__PURE__ */ jsx("div", { style: labelStyle(theme), children: labels.firstName }),
512
- /* @__PURE__ */ jsx(
513
- "input",
514
- {
515
- type: "text",
516
- value: firstName,
517
- onChange: (e) => setFirstName(e.target.value),
518
- autoComplete: "given-name",
519
- required: true,
520
- style: inputStyle(theme)
521
- }
522
- )
523
- ] }),
524
- /* @__PURE__ */ jsxs("div", { style: { flex: 1 }, children: [
525
- /* @__PURE__ */ jsx("div", { style: labelStyle(theme), children: labels.lastName }),
526
- /* @__PURE__ */ jsx(
527
- "input",
528
- {
529
- type: "text",
530
- value: lastName,
531
- onChange: (e) => setLastName(e.target.value),
532
- autoComplete: "family-name",
533
- required: true,
534
- style: inputStyle(theme)
535
- }
536
- )
537
- ] })
538
- ] }),
539
- /* @__PURE__ */ jsxs("div", { children: [
540
- /* @__PURE__ */ jsx("div", { style: labelStyle(theme), children: labels.verificationCode }),
541
- /* @__PURE__ */ jsx(
542
- "input",
543
- {
544
- type: "text",
545
- value: code,
546
- onChange: (e) => setCode(e.target.value),
547
- autoComplete: "one-time-code",
548
- required: true,
549
- style: inputStyle(theme)
550
- }
551
- )
552
- ] }),
553
- /* @__PURE__ */ jsxs("div", { children: [
554
- /* @__PURE__ */ jsx("div", { style: labelStyle(theme), children: labels.password }),
555
- /* @__PURE__ */ jsx(
556
- "input",
557
- {
558
- type: "password",
559
- value: password,
560
- onChange: (e) => setPassword(e.target.value),
561
- autoComplete: "new-password",
562
- required: true,
563
- style: inputStyle(theme)
564
- }
565
- )
566
- ] }),
567
- /* @__PURE__ */ jsxs("div", { children: [
568
- /* @__PURE__ */ jsx("div", { style: labelStyle(theme), children: labels.confirmPassword }),
569
- /* @__PURE__ */ jsx(
570
- "input",
571
- {
572
- type: "password",
573
- value: confirmPw,
574
- onChange: (e) => setConfirmPw(e.target.value),
575
- autoComplete: "new-password",
576
- required: true,
577
- style: inputStyle(theme)
578
- }
579
- )
580
- ] }),
581
- /* @__PURE__ */ jsx("button", { type: "submit", disabled: loading, style: buttonStyle(theme, loading), children: loading ? "..." : labels.registerButton }),
582
- error && /* @__PURE__ */ jsx("div", { style: errorStyle(), children: error }),
583
- /* @__PURE__ */ jsx("div", { style: { textAlign: "center" }, children: /* @__PURE__ */ jsx("button", { type: "button", onClick: () => switchMode("login"), style: linkButtonStyle(theme), children: labels.switchToLogin }) })
584
- ] });
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");
585
40
  }
41
+
42
+ // src/hooks.ts
586
43
  var arbiQueryKeys = {
587
44
  workspaces: () => ["arbi", "workspaces"],
588
45
  documents: (workspaceId) => ["arbi", "documents", workspaceId],
@@ -600,7 +57,14 @@ var arbiQueryKeys = {
600
57
  projects: () => ["arbi", "projects"],
601
58
  projectUsage: (projectExtId, monthsBack) => ["arbi", "projectUsage", projectExtId, monthsBack],
602
59
  projectInvoices: (projectExtId) => ["arbi", "projectInvoices", projectExtId],
603
- userUsageToday: () => ["arbi", "userUsageToday"]
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"]
604
68
  };
605
69
  function useWorkspaces(options) {
606
70
  const arbi = useArbi();
@@ -729,15 +193,20 @@ function useProjects(options) {
729
193
  }
730
194
  function useProjectUsage(projectExtId, monthsBack = 0, options) {
731
195
  const arbi = useArbi();
732
- return useQuery({
196
+ const usage = useQuery({
733
197
  queryKey: arbiQueryKeys.projectUsage(projectExtId ?? "", monthsBack),
734
198
  queryFn: () => arbi.projects.usage(projectExtId, monthsBack),
735
199
  enabled: (options?.enabled ?? true) && !!projectExtId && arbi.isLoggedIn
736
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 };
737
205
  }
738
206
  function useProjectsUsage(projectExtIds, monthsBack = 0, options) {
739
207
  const arbi = useArbi();
740
208
  const enabled = (options?.enabled ?? true) && arbi.isLoggedIn;
209
+ const projects = useProjects({ enabled: enabled && projectExtIds.length > 0 });
741
210
  const results = useQueries({
742
211
  queries: projectExtIds.map((id) => ({
743
212
  queryKey: arbiQueryKeys.projectUsage(id, monthsBack),
@@ -745,27 +214,34 @@ function useProjectsUsage(projectExtIds, monthsBack = 0, options) {
745
214
  enabled: enabled && !!id
746
215
  }))
747
216
  });
748
- const signature = results.map((r) => `${r.status}:${r.dataUpdatedAt ?? 0}`).join("|");
217
+ const signature = results.map((r) => `${r.status}:${r.dataUpdatedAt ?? 0}`).join("|") + `#${projects.dataUpdatedAt}`;
749
218
  return useMemo(() => {
750
219
  const dailyMap = /* @__PURE__ */ new Map();
751
220
  const modelMap = /* @__PURE__ */ new Map();
752
- let totalSpend = 0;
221
+ let totalCredits = 0;
222
+ let anyData = false;
223
+ let anyMetered = false;
753
224
  let totalTokens = 0;
754
225
  let totalRequests = 0;
755
- for (const r of results) {
226
+ results.forEach((r, i) => {
756
227
  const d = r.data;
757
- if (!d) continue;
758
- totalSpend += d.total_spend ?? 0;
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;
759
235
  totalTokens += d.total_tokens ?? 0;
760
236
  totalRequests += d.total_requests ?? 0;
761
237
  for (const day of d.daily ?? []) {
762
238
  const cur = dailyMap.get(day.date) ?? {
763
239
  date: day.date,
764
- spend: 0,
240
+ credits: 0,
765
241
  totalTokens: 0,
766
242
  apiRequests: 0
767
243
  };
768
- cur.spend += day.spend ?? 0;
244
+ cur.credits += day.spend ?? 0;
769
245
  cur.totalTokens += day.total_tokens ?? 0;
770
246
  cur.apiRequests += day.api_requests ?? 0;
771
247
  dailyMap.set(day.date, cur);
@@ -773,567 +249,264 @@ function useProjectsUsage(projectExtIds, monthsBack = 0, options) {
773
249
  for (const [model, mg] of Object.entries(d.model_groups ?? {})) {
774
250
  const cur = modelMap.get(model) ?? {
775
251
  model,
776
- spend: 0,
252
+ credits: 0,
777
253
  totalTokens: 0,
778
254
  promptTokens: 0,
779
255
  completionTokens: 0,
780
256
  successfulRequests: 0
781
257
  };
782
- cur.spend += mg.spend ?? 0;
258
+ cur.credits += mg.spend ?? 0;
783
259
  cur.totalTokens += mg.total_tokens ?? 0;
784
260
  cur.promptTokens += mg.prompt_tokens ?? 0;
785
261
  cur.completionTokens += mg.completion_tokens ?? 0;
786
262
  cur.successfulRequests += mg.successful_requests ?? 0;
787
263
  modelMap.set(model, cur);
788
264
  }
789
- }
265
+ });
790
266
  return {
791
- totalSpend,
267
+ totalCredits,
792
268
  totalTokens,
793
269
  totalRequests,
794
- daily: [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date)),
795
- modelGroups: [...modelMap.values()].sort(
796
- (a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens
797
- ),
798
- isLoading: results.some((r) => r.isLoading),
799
- isError: results.some((r) => r.isError)
800
- };
801
- }, [signature]);
802
- }
803
- function useProjectInvoices(projectExtId, options) {
804
- const arbi = useArbi();
805
- return useQuery({
806
- queryKey: arbiQueryKeys.projectInvoices(projectExtId ?? ""),
807
- queryFn: () => arbi.projects.invoices(projectExtId),
808
- enabled: (options?.enabled ?? true) && !!projectExtId && arbi.isLoggedIn
809
- });
810
- }
811
- function useUserUsageToday(options) {
812
- const arbi = useArbi();
813
- return useQuery({
814
- queryKey: arbiQueryKeys.userUsageToday(),
815
- queryFn: () => arbi.user.usageToday(),
816
- enabled: options?.enabled ?? arbi.isLoggedIn
817
- });
818
- }
819
- function WorkspaceScreen({ theme, labels, onSelect }) {
820
- const arbi = useArbi();
821
- const { data: workspaces, isLoading } = useWorkspaces();
822
- const [mode, setMode] = useState("select");
823
- const [selected, setSelected] = useState("");
824
- const [newName, setNewName] = useState("");
825
- const [loading, setLoading] = useState(false);
826
- const [error, setError] = useState("");
827
- if (isLoading) {
828
- return /* @__PURE__ */ jsx("div", { style: loadingContainerStyle(), children: "Loading workspaces..." });
829
- }
830
- const handleConfirm = async () => {
831
- if (!selected) return;
832
- setLoading(true);
833
- setError("");
834
- try {
835
- await arbi.selectWorkspace(selected);
836
- onSelect(selected);
837
- } catch (err) {
838
- setError(err instanceof Error ? err.message : "Failed to select workspace");
839
- } finally {
840
- setLoading(false);
841
- }
842
- };
843
- const handleCreate = async () => {
844
- const name = newName.trim();
845
- if (!name) return;
846
- setLoading(true);
847
- setError("");
848
- try {
849
- const projects = await arbi.projects.list();
850
- const projectExtId = projects[0]?.external_id;
851
- if (!projectExtId) {
852
- throw new Error("No project available; create one before adding a workspace");
853
- }
854
- const ws = await arbi.workspaces.create(name, projectExtId);
855
- await arbi.selectWorkspace(ws.external_id);
856
- onSelect(ws.external_id);
857
- } catch (err) {
858
- setError(err instanceof Error ? err.message : "Failed to create workspace");
859
- } finally {
860
- setLoading(false);
861
- }
862
- };
863
- if (mode === "create") {
864
- return /* @__PURE__ */ jsxs("div", { style: formContainerStyle(), children: [
865
- /* @__PURE__ */ jsxs("div", { children: [
866
- /* @__PURE__ */ jsx("div", { style: labelStyle(theme), children: labels.workspaceName }),
867
- /* @__PURE__ */ jsx(
868
- "input",
869
- {
870
- type: "text",
871
- value: newName,
872
- onChange: (e) => setNewName(e.target.value),
873
- disabled: loading,
874
- style: inputStyle(theme)
875
- }
876
- )
877
- ] }),
878
- /* @__PURE__ */ jsx(
879
- "button",
880
- {
881
- type: "button",
882
- onClick: handleCreate,
883
- disabled: !newName.trim() || loading,
884
- style: buttonStyle(theme, !newName.trim() || loading),
885
- children: loading ? "..." : labels.createButton
886
- }
887
- ),
888
- error && /* @__PURE__ */ jsx("div", { style: errorStyle(), children: error }),
889
- /* @__PURE__ */ jsx("div", { style: { textAlign: "center" }, children: /* @__PURE__ */ jsx(
890
- "button",
891
- {
892
- type: "button",
893
- onClick: () => {
894
- setError("");
895
- setMode("select");
896
- },
897
- style: linkButtonStyle(theme),
898
- children: "or select existing"
899
- }
900
- ) })
901
- ] });
902
- }
903
- return /* @__PURE__ */ jsxs("div", { style: formContainerStyle(), children: [
904
- /* @__PURE__ */ jsxs("div", { children: [
905
- /* @__PURE__ */ jsx("div", { style: labelStyle(theme), children: labels.selectWorkspace }),
906
- /* @__PURE__ */ jsxs(
907
- "select",
908
- {
909
- value: selected,
910
- onChange: (e) => setSelected(e.target.value),
911
- disabled: loading,
912
- style: selectStyle(theme),
913
- children: [
914
- /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: labels.workspacePlaceholder }),
915
- workspaces?.map((ws) => /* @__PURE__ */ jsx("option", { value: ws.external_id, children: ws.name }, ws.external_id))
916
- ]
917
- }
918
- )
919
- ] }),
920
- /* @__PURE__ */ jsx(
921
- "button",
922
- {
923
- type: "button",
924
- onClick: handleConfirm,
925
- disabled: !selected || loading,
926
- style: buttonStyle(theme, !selected || loading),
927
- children: loading ? "Connecting..." : labels.continueButton
928
- }
929
- ),
930
- error && /* @__PURE__ */ jsx("div", { style: errorStyle(), children: error }),
931
- /* @__PURE__ */ jsx("div", { style: { textAlign: "center" }, children: /* @__PURE__ */ jsx(
932
- "button",
933
- {
934
- type: "button",
935
- onClick: () => {
936
- setError("");
937
- setMode("create");
938
- },
939
- style: linkButtonStyle(theme),
940
- children: labels.createWorkspace
941
- }
942
- ) })
943
- ] });
944
- }
945
- var CITE_RE = /\[([^\]]+)\]\(#cite-(\d+)\)/g;
946
- function MessageBubble({ message, theme }) {
947
- const [expandedCite, setExpandedCite] = useState(null);
948
- const hasCitations = message.role === "assistant" && message.metadata && CITE_RE.test(message.text);
949
- CITE_RE.lastIndex = 0;
950
- const resolved = hasCitations ? resolveCitations(message.metadata) : [];
951
- const summaries = hasCitations ? summarizeCitations(resolved) : [];
952
- const summaryMap = new Map(summaries.map((s) => [s.citationNum, s]));
953
- const activeSummary = expandedCite ? summaryMap.get(expandedCite) : null;
954
- const activeResolved = expandedCite ? resolved.find((r) => r.citationNum === expandedCite) : null;
955
- const markdownComponents = useMemo(
956
- () => ({
957
- a: ({ href, children, ...props }) => {
958
- if (href?.startsWith("#cite-")) {
959
- const num = href.slice("#cite-".length);
960
- return /* @__PURE__ */ jsxs("span", { children: [
961
- children,
962
- /* @__PURE__ */ jsx(
963
- "sup",
964
- {
965
- onClick: (e) => {
966
- e.stopPropagation();
967
- setExpandedCite((prev) => prev === num ? null : num);
968
- },
969
- style: citeSupStyle(theme),
970
- title: `Citation ${num}`,
971
- children: num
972
- }
973
- )
974
- ] });
975
- }
976
- return /* @__PURE__ */ jsx(
977
- "a",
978
- {
979
- href,
980
- target: "_blank",
981
- rel: "noopener noreferrer",
982
- style: { color: theme.primaryColor },
983
- ...props,
984
- children
985
- }
986
- );
987
- }
988
- }),
989
- [theme]
990
- );
991
- return /* @__PURE__ */ jsxs("div", { children: [
992
- /* @__PURE__ */ jsx("div", { style: messageBubbleStyle(theme, message.role), children: /* @__PURE__ */ jsx(MarkdownRenderer, { components: markdownComponents, children: message.text }) }),
993
- activeSummary && activeResolved && /* @__PURE__ */ jsxs("div", { style: citePanelStyle(theme), children: [
994
- /* @__PURE__ */ jsxs("div", { style: citePanelHeaderStyle(theme), children: [
995
- /* @__PURE__ */ jsxs("strong", { children: [
996
- "[",
997
- activeSummary.citationNum,
998
- "]"
999
- ] }),
1000
- " ",
1001
- activeSummary.docTitle,
1002
- activeSummary.pageNumber != null && /* @__PURE__ */ jsxs("span", { style: { color: theme.mutedColor }, children: [
1003
- " p.",
1004
- activeSummary.pageNumber
1005
- ] })
1006
- ] }),
1007
- activeResolved.chunks.map((chunk, i) => /* @__PURE__ */ jsx("div", { style: citeChunkStyle(theme), children: chunk.content }, i))
1008
- ] })
1009
- ] });
1010
- }
1011
- function citeSupStyle(theme) {
1012
- return {
1013
- display: "inline-flex",
1014
- alignItems: "center",
1015
- justifyContent: "center",
1016
- fontSize: "10px",
1017
- fontWeight: 600,
1018
- color: theme.primaryColor,
1019
- cursor: "pointer",
1020
- marginLeft: "1px",
1021
- minWidth: "14px",
1022
- lineHeight: 1
1023
- };
1024
- }
1025
- function citePanelStyle(theme) {
1026
- return {
1027
- marginTop: "4px",
1028
- padding: "8px 10px",
1029
- fontSize: "12px",
1030
- lineHeight: "1.5",
1031
- backgroundColor: theme.surfaceColor,
1032
- border: `1px solid ${theme.borderColor}`,
1033
- borderRadius: "8px",
1034
- maxWidth: "85%"
1035
- };
1036
- }
1037
- function citePanelHeaderStyle(theme) {
1038
- return {
1039
- fontSize: "12px",
1040
- fontWeight: 500,
1041
- color: theme.textColor,
1042
- marginBottom: "4px"
1043
- };
1044
- }
1045
- function citeChunkStyle(theme) {
1046
- return {
1047
- fontSize: "11px",
1048
- color: theme.mutedColor,
1049
- borderLeft: `2px solid ${theme.borderColor}`,
1050
- paddingLeft: "8px",
1051
- marginTop: "4px",
1052
- whiteSpace: "pre-wrap",
1053
- wordBreak: "break-word"
1054
- };
1055
- }
1056
- function ChatView({ workspaceId, theme, labels }) {
1057
- const arbi = useArbi();
1058
- const { data: docs, refetch: refetchDocs } = useDocuments(workspaceId);
1059
- const mutation = useAssistantQuery();
1060
- const [messages, setMessages] = useState([]);
1061
- const [input, setInput] = useState("");
1062
- const [streaming, setStreaming] = useState("");
1063
- const [agentStatus, setAgentStatus] = useState("");
1064
- const [selectedDocs, setSelectedDocs] = useState([]);
1065
- const [docsExpanded, setDocsExpanded] = useState(false);
1066
- const [previousResponseId, setPreviousResponseId] = useState(null);
1067
- const [uploading, setUploading] = useState(false);
1068
- const messagesEndRef = useRef(null);
1069
- const fileInputRef = useRef(null);
1070
- useEffect(() => {
1071
- if (docs && docs.length > 0 && selectedDocs.length === 0) {
1072
- setSelectedDocs(docs.map((d) => d.external_id));
1073
- }
1074
- }, [docs, selectedDocs.length]);
1075
- useEffect(() => {
1076
- messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
1077
- }, [messages, streaming, agentStatus]);
1078
- const toggleDoc = (docId) => {
1079
- setSelectedDocs(
1080
- (prev) => prev.includes(docId) ? prev.filter((id) => id !== docId) : [...prev, docId]
1081
- );
1082
- };
1083
- const handleUpload = useCallback(
1084
- async (e) => {
1085
- const files = e.target.files;
1086
- if (!files || files.length === 0) return;
1087
- setUploading(true);
1088
- try {
1089
- const newIds = [];
1090
- for (const file of Array.from(files)) {
1091
- const result = await arbi.documents.uploadFile(file, file.name);
1092
- if (result.doc_ext_ids) newIds.push(...result.doc_ext_ids);
1093
- }
1094
- await refetchDocs();
1095
- setSelectedDocs((prev) => [...prev, ...newIds]);
1096
- } catch {
1097
- } finally {
1098
- setUploading(false);
1099
- if (fileInputRef.current) fileInputRef.current.value = "";
1100
- }
1101
- },
1102
- [arbi, refetchDocs]
1103
- );
1104
- const handleSubmit = useCallback(
1105
- (e) => {
1106
- e.preventDefault();
1107
- const question = input.trim();
1108
- if (!question || selectedDocs.length === 0 || mutation.isPending) return;
1109
- setMessages((prev) => [...prev, { role: "user", text: question }]);
1110
- setInput("");
1111
- setStreaming("");
1112
- setAgentStatus("");
1113
- mutation.mutate(
1114
- {
1115
- question,
1116
- docIds: selectedDocs,
1117
- previousResponseId,
1118
- onToken: (t) => setStreaming((prev) => prev + t),
1119
- onOutputItemDone: (ev) => {
1120
- const item = ev.item;
1121
- if (item.type === "function_call" || item.type === "reasoning") {
1122
- setAgentStatus(item.arbi?.focus || item.arbi?.label || "");
1123
- }
1124
- }
1125
- },
1126
- {
1127
- onSuccess: (result) => {
1128
- setStreaming("");
1129
- setAgentStatus("");
1130
- setMessages((prev) => [
1131
- ...prev,
1132
- { role: "assistant", text: result.text, metadata: result.metadata }
1133
- ]);
1134
- setPreviousResponseId(result.assistantMessageExtId);
1135
- },
1136
- onError: (err) => {
1137
- setStreaming("");
1138
- setAgentStatus("");
1139
- setMessages((prev) => [...prev, { role: "assistant", text: `Error: ${err.message}` }]);
1140
- }
1141
- }
1142
- );
1143
- },
1144
- [input, selectedDocs, previousResponseId, mutation]
1145
- );
1146
- const noDocs = !docs || docs.length === 0;
1147
- const noDocsSelected = selectedDocs.length === 0;
1148
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1149
- /* @__PURE__ */ jsx(
1150
- "input",
1151
- {
1152
- ref: fileInputRef,
1153
- type: "file",
1154
- multiple: true,
1155
- accept: ".pdf,.txt,.md,.html,.doc,.docx,.rtf,.ppt,.pptx,.xls,.xlsx",
1156
- onChange: handleUpload,
1157
- style: fileInputStyle()
1158
- }
1159
- ),
1160
- /* @__PURE__ */ jsxs("div", { style: docSelectorStyle(theme), children: [
1161
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1162
- /* @__PURE__ */ jsxs(
1163
- "button",
1164
- {
1165
- type: "button",
1166
- onClick: () => setDocsExpanded((prev) => !prev),
1167
- style: docToggleStyle(theme),
1168
- children: [
1169
- /* @__PURE__ */ jsx("span", { style: { fontSize: "10px" }, children: docsExpanded ? "\u25BC" : "\u25B6" }),
1170
- labels.documents,
1171
- " (",
1172
- selectedDocs.length,
1173
- "/",
1174
- docs?.length ?? 0,
1175
- " in chat)"
1176
- ]
1177
- }
1178
- ),
1179
- /* @__PURE__ */ jsx(
1180
- "button",
1181
- {
1182
- type: "button",
1183
- onClick: () => fileInputRef.current?.click(),
1184
- disabled: uploading,
1185
- style: secondaryButtonStyle(theme, uploading),
1186
- children: uploading ? labels.uploading : labels.uploadButton
1187
- }
1188
- )
1189
- ] }),
1190
- docsExpanded && docs && docs.length > 0 && /* @__PURE__ */ jsx("div", { style: docListStyle(), children: docs.map((doc) => /* @__PURE__ */ jsxs("label", { style: docItemStyle(theme), children: [
1191
- /* @__PURE__ */ jsx(
1192
- "input",
1193
- {
1194
- type: "checkbox",
1195
- checked: selectedDocs.includes(doc.external_id),
1196
- onChange: () => toggleDoc(doc.external_id)
1197
- }
1198
- ),
1199
- doc.file_name
1200
- ] }, doc.external_id)) })
1201
- ] }),
1202
- /* @__PURE__ */ jsxs("div", { style: messagesContainerStyle(), children: [
1203
- messages.length === 0 && !streaming && /* @__PURE__ */ jsx("div", { style: emptyStateStyle(theme), children: noDocs ? labels.emptyNoDocs : noDocsSelected ? labels.emptyNoSelection : labels.emptyReady }),
1204
- messages.map((msg, i) => /* @__PURE__ */ jsx(MessageBubble, { message: msg, theme }, i)),
1205
- agentStatus && !streaming && /* @__PURE__ */ jsxs("div", { style: agentStatusStyle(theme), children: [
1206
- agentStatus,
1207
- "..."
1208
- ] }),
1209
- streaming && /* @__PURE__ */ jsx(MessageBubble, { message: { role: "assistant", text: streaming }, theme }),
1210
- /* @__PURE__ */ jsx("div", { ref: messagesEndRef })
1211
- ] }),
1212
- /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, style: inputBarStyle(theme), children: [
1213
- /* @__PURE__ */ jsx(
1214
- "input",
1215
- {
1216
- type: "text",
1217
- value: input,
1218
- onChange: (e) => setInput(e.target.value),
1219
- placeholder: noDocs || noDocsSelected ? "Select documents first..." : labels.placeholder,
1220
- disabled: noDocs || noDocsSelected || mutation.isPending,
1221
- style: chatInputStyle(theme)
1222
- }
1223
- ),
1224
- /* @__PURE__ */ jsx(
1225
- "button",
1226
- {
1227
- type: "submit",
1228
- disabled: !input.trim() || noDocsSelected || mutation.isPending,
1229
- style: sendButtonStyle(theme, !input.trim() || noDocsSelected || mutation.isPending),
1230
- children: mutation.isPending ? "..." : labels.sendButton
1231
- }
1232
- )
1233
- ] })
1234
- ] });
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)
277
+ };
278
+ }, [signature]);
1235
279
  }
1236
- function ChatWidget({ theme, labels, initialWorkspaceId, width, height }) {
280
+ function useProjectInvoices(projectExtId, options) {
1237
281
  const arbi = useArbi();
1238
- const [screen, setScreen] = useState("auth");
1239
- const [workspaceId, setWorkspaceId] = useState(initialWorkspaceId);
1240
- const resolveWorkspace = useCallback(async () => {
1241
- if (initialWorkspaceId) {
1242
- try {
1243
- await arbi.selectWorkspace(initialWorkspaceId);
1244
- setScreen("chat");
1245
- return;
1246
- } catch {
1247
- setScreen("workspace");
1248
- return;
1249
- }
1250
- }
1251
- setScreen("workspace");
1252
- }, [arbi, initialWorkspaceId]);
1253
- const handleLogin = useCallback(() => resolveWorkspace(), [resolveWorkspace]);
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
+ });
295
+ }
296
+
297
+ // src/chat/useArbiChat.ts
298
+ var counter = 0;
299
+ var nextId = (prefix) => `${prefix}-${Date.now().toString(36)}-${(counter++).toString(36)}`;
300
+ var emptyThread = () => ({
301
+ id: nextId("thread"),
302
+ title: "",
303
+ messages: [],
304
+ responseId: null
305
+ });
306
+ function titleFromQuestion(question, maxLength = 48) {
307
+ const flat = question.replace(/\s+/g, " ").trim();
308
+ if (flat.length <= maxLength) return flat;
309
+ const cut = flat.slice(0, maxLength);
310
+ const lastSpace = cut.lastIndexOf(" ");
311
+ const words = lastSpace > maxLength / 2 ? cut.slice(0, lastSpace) : cut;
312
+ return `${words.replace(/[\s,;:.\-–—]+$/, "")}\u2026`;
313
+ }
314
+ var messageOf = (e, fallback) => e instanceof Error ? e.message : fallback;
315
+ function useArbiChat(options = {}) {
316
+ const { workspaceId, model, titleLength = 48 } = options;
317
+ const arbi = useArbi();
318
+ const { data: docs = [], isLoading: isLoadingDocuments, refetch } = useDocuments(workspaceId);
319
+ const query = useAssistantQuery();
320
+ const { mutateAsync } = query;
321
+ const [threads, setThreads] = useState(() => [emptyThread()]);
322
+ const [activeId, setActiveId] = useState(() => threads[0].id);
323
+ const [deselected, setDeselected] = useState(/* @__PURE__ */ new Set());
324
+ const [isRunning, setIsRunning] = useState(false);
325
+ const [isUploading, setIsUploading] = useState(false);
326
+ const [uploadError, setUploadError] = useState(null);
327
+ const scopeRef = useRef(workspaceId);
1254
328
  useEffect(() => {
1255
- if (arbi.isLoggedIn) void resolveWorkspace();
1256
- }, [arbi, resolveWorkspace]);
1257
- const handleWorkspaceSelect = useCallback((id) => {
1258
- setWorkspaceId(id);
1259
- setScreen("chat");
1260
- }, []);
1261
- const handleChangeWorkspace = useCallback(() => {
1262
- setScreen("workspace");
1263
- }, []);
1264
- return /* @__PURE__ */ jsxs("div", { style: containerStyle(theme, width, height), children: [
1265
- /* @__PURE__ */ jsxs("div", { style: { ...headerStyle(theme), justifyContent: "space-between" }, children: [
1266
- /* @__PURE__ */ jsx("span", { children: labels.title }),
1267
- screen === "chat" && !initialWorkspaceId && /* @__PURE__ */ jsx("button", { type: "button", onClick: handleChangeWorkspace, style: linkButtonStyle(theme), children: labels.changeWorkspace })
1268
- ] }),
1269
- /* @__PURE__ */ jsxs("div", { style: bodyStyle(), children: [
1270
- screen === "auth" && /* @__PURE__ */ jsx(AuthScreen, { theme, labels, onLogin: handleLogin }),
1271
- screen === "workspace" && /* @__PURE__ */ jsx(WorkspaceScreen, { theme, labels, onSelect: handleWorkspaceSelect }),
1272
- screen === "chat" && workspaceId && /* @__PURE__ */ jsx(ChatView, { workspaceId, theme, labels })
1273
- ] }),
1274
- /* @__PURE__ */ jsxs("div", { style: brandingStyle(theme), children: [
1275
- "Powered by",
1276
- " ",
1277
- /* @__PURE__ */ jsx(
1278
- "a",
1279
- {
1280
- href: "https://www.arbidocs.com",
1281
- target: "_blank",
1282
- rel: "noopener noreferrer",
1283
- style: brandingLinkStyle(theme),
1284
- children: "ARBI"
329
+ if (scopeRef.current === workspaceId) return;
330
+ scopeRef.current = workspaceId;
331
+ const fresh = emptyThread();
332
+ setThreads([fresh]);
333
+ setActiveId(fresh.id);
334
+ setDeselected(/* @__PURE__ */ new Set());
335
+ setUploadError(null);
336
+ }, [workspaceId]);
337
+ const active = threads.find((t) => t.id === activeId) ?? threads[0];
338
+ const selectedIds = useMemo(
339
+ () => docs.map((d) => d.external_id).filter((id) => !!id && !deselected.has(id)),
340
+ [docs, deselected]
341
+ );
342
+ const documents = useMemo(
343
+ () => docs.filter((d) => !!d.external_id).map((d) => ({
344
+ id: d.external_id,
345
+ name: d.file_name ?? d.external_id,
346
+ isSelected: !deselected.has(d.external_id)
347
+ })),
348
+ [docs, deselected]
349
+ );
350
+ const patchThread = useCallback(
351
+ (id, patch) => setThreads((all) => all.map((t) => t.id === id ? patch(t) : t)),
352
+ []
353
+ );
354
+ const patchMessage = useCallback(
355
+ (threadId, messageId, patch) => patchThread(threadId, (t) => ({
356
+ ...t,
357
+ messages: t.messages.map((m) => m.id === messageId ? { ...m, ...patch } : m)
358
+ })),
359
+ [patchThread]
360
+ );
361
+ const ask = useCallback(
362
+ async (thread, text) => {
363
+ const userMessage = {
364
+ id: nextId("msg"),
365
+ role: "user",
366
+ content: text,
367
+ status: "complete"
368
+ };
369
+ const answer = {
370
+ id: nextId("msg"),
371
+ role: "assistant",
372
+ content: "",
373
+ status: "streaming"
374
+ };
375
+ patchThread(thread.id, (t) => ({
376
+ ...t,
377
+ title: t.title || titleFromQuestion(text, titleLength),
378
+ messages: [...t.messages, userMessage, answer]
379
+ }));
380
+ setIsRunning(true);
381
+ let buffer = "";
382
+ let steps = 0;
383
+ let streamError = null;
384
+ try {
385
+ const result = await mutateAsync({
386
+ question: text,
387
+ docIds: selectedIds,
388
+ previousResponseId: thread.responseId,
389
+ model,
390
+ onToken: (token) => {
391
+ buffer += token;
392
+ patchMessage(thread.id, answer.id, { content: buffer });
393
+ },
394
+ onOutputItemDone: (ev) => {
395
+ const type = ev.item.type;
396
+ if (type === "function_call" || type === "reasoning") {
397
+ steps += 1;
398
+ patchMessage(thread.id, answer.id, { stepCount: steps });
399
+ }
400
+ },
401
+ onError: (message) => {
402
+ streamError = message;
403
+ }
404
+ });
405
+ const finalText = result.text || buffer;
406
+ const failure = streamError || result.errors[0] || null;
407
+ if (failure && !finalText) {
408
+ patchMessage(thread.id, answer.id, { status: "error", error: failure });
409
+ } else {
410
+ patchMessage(thread.id, answer.id, {
411
+ status: "complete",
412
+ content: finalText,
413
+ stepCount: result.agentSteps.length || steps || void 0
414
+ });
415
+ patchThread(thread.id, (t) => ({
416
+ ...t,
417
+ responseId: result.assistantMessageExtId ?? t.responseId
418
+ }));
1285
419
  }
1286
- )
1287
- ] })
1288
- ] });
1289
- }
1290
- function ArbiChatPanel({
1291
- workspaceId,
1292
- theme: themeOverrides,
1293
- labels: labelOverrides,
1294
- width = "100%",
1295
- height = "100%"
1296
- }) {
1297
- const theme = useMemo(() => getTheme(themeOverrides), [themeOverrides]);
1298
- const labels = useMemo(() => getLabels(labelOverrides), [labelOverrides]);
1299
- return /* @__PURE__ */ jsx(
1300
- ChatWidget,
1301
- {
1302
- theme,
1303
- labels,
1304
- initialWorkspaceId: workspaceId,
1305
- width,
1306
- height
1307
- }
420
+ } catch (e) {
421
+ patchMessage(thread.id, answer.id, {
422
+ status: "error",
423
+ error: messageOf(e, "The assistant request failed.")
424
+ });
425
+ } finally {
426
+ setIsRunning(false);
427
+ }
428
+ },
429
+ [mutateAsync, patchMessage, patchThread, selectedIds, model, titleLength]
1308
430
  );
1309
- }
1310
- function ArbiChat({
1311
- url,
1312
- workspaceId,
1313
- theme: themeOverrides,
1314
- labels: labelOverrides,
1315
- width = "400px",
1316
- height = "600px"
1317
- }) {
1318
- const queryClient = useMemo(
1319
- () => new QueryClient({
1320
- defaultOptions: {
1321
- queries: { retry: 1, refetchOnWindowFocus: false },
1322
- mutations: { retry: false }
431
+ const send = useCallback(
432
+ async (text) => {
433
+ const trimmed = text.trim();
434
+ if (!trimmed || isRunning) return;
435
+ await ask(active, trimmed);
436
+ },
437
+ [active, ask, isRunning]
438
+ );
439
+ const lastMessage = active.messages[active.messages.length - 1];
440
+ const error = lastMessage?.status === "error" ? lastMessage.error ?? null : null;
441
+ const retry = useCallback(async () => {
442
+ if (isRunning) return;
443
+ const messages = active.messages;
444
+ const failed = messages[messages.length - 1];
445
+ const question = messages[messages.length - 2];
446
+ if (failed?.status !== "error" || question?.role !== "user") return;
447
+ const trimmed = { ...active, messages: messages.slice(0, -2) };
448
+ patchThread(active.id, () => trimmed);
449
+ await ask(trimmed, question.content);
450
+ }, [active, ask, isRunning, patchThread]);
451
+ const newThread = useCallback(() => {
452
+ if (active.messages.length === 0) return;
453
+ const fresh = emptyThread();
454
+ setThreads((all) => [fresh, ...all]);
455
+ setActiveId(fresh.id);
456
+ }, [active.messages.length]);
457
+ const deleteThread = useCallback(
458
+ (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);
463
+ },
464
+ [threads, activeId]
465
+ );
466
+ const toggleDocument = useCallback((id) => {
467
+ setDeselected((prev) => {
468
+ const next = new Set(prev);
469
+ if (next.has(id)) next.delete(id);
470
+ else next.add(id);
471
+ return next;
472
+ });
473
+ }, []);
474
+ const selectAllDocuments = useCallback(() => setDeselected(/* @__PURE__ */ new Set()), []);
475
+ const upload = useCallback(
476
+ async (files) => {
477
+ if (files.length === 0) return;
478
+ setIsUploading(true);
479
+ setUploadError(null);
480
+ try {
481
+ for (const file of files) await arbi.documents.uploadFile(file, file.name);
482
+ await refetch();
483
+ } catch (e) {
484
+ setUploadError(messageOf(e, "The upload failed."));
485
+ } finally {
486
+ setIsUploading(false);
1323
487
  }
1324
- }),
1325
- []
488
+ },
489
+ [arbi, refetch]
1326
490
  );
1327
- return /* @__PURE__ */ jsx(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx(ArbiProvider, { url, children: /* @__PURE__ */ jsx(
1328
- ArbiChatPanel,
1329
- {
1330
- workspaceId,
1331
- theme: themeOverrides,
1332
- labels: labelOverrides,
1333
- width,
1334
- height
1335
- }
1336
- ) }) });
491
+ return {
492
+ threads: threads.map(({ id, title }) => ({ id, title })),
493
+ activeThreadId: active.id,
494
+ messages: active.messages,
495
+ isRunning,
496
+ error,
497
+ documents,
498
+ isLoadingDocuments,
499
+ isUploading,
500
+ uploadError,
501
+ send,
502
+ retry,
503
+ selectThread: setActiveId,
504
+ newThread,
505
+ deleteThread,
506
+ toggleDocument,
507
+ selectAllDocuments,
508
+ upload
509
+ };
1337
510
  }
1338
511
  var TypingIndicator = memo(function TypingIndicator2() {
1339
512
  return /* @__PURE__ */ jsx("div", { className: "flex w-full justify-start mb-2", "data-testid": "dm-typing-indicator", children: /* @__PURE__ */ jsx("div", { className: "bg-card border border-primary rounded-2xl rounded-bl-md px-4 py-2.5", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-1 items-center", children: [
@@ -1616,6 +789,324 @@ function useArbiDocumentSync(workspaceId) {
1616
789
  }
1617
790
  });
1618
791
  }
792
+
793
+ // src/workspaces/shell-workspaces.ts
794
+ function getWorkspaceKind(workspace) {
795
+ if (workspace.is_public) return "common";
796
+ if ((workspace.users?.length ?? 0) > 1) return "shared";
797
+ return "private";
798
+ }
799
+ function memberName(user) {
800
+ const full = [user?.given_name, user?.family_name].filter(Boolean).join(" ").trim();
801
+ return full || user?.email || "";
802
+ }
803
+ function toShellWorkspace(workspace) {
804
+ const people = (workspace.users ?? []).filter((u) => !u.agent_ext_id);
805
+ return {
806
+ id: workspace.external_id,
807
+ name: workspace.name,
808
+ kind: getWorkspaceKind(workspace),
809
+ description: workspace.description,
810
+ projectName: workspace.project_name,
811
+ docCount: (workspace.shared_document_count ?? 0) + (workspace.private_document_count ?? 0),
812
+ memberCount: people.length,
813
+ members: people.map((u) => ({ name: memberName(u.user), picture: u.user?.picture })),
814
+ updatedAt: workspace.updated_at
815
+ };
816
+ }
817
+ function recency(workspace, lastOpened) {
818
+ const opened = lastOpened?.[workspace.id];
819
+ if (opened !== void 0) return opened;
820
+ const updated = Date.parse(workspace.updatedAt ?? "");
821
+ return Number.isNaN(updated) ? 0 : updated;
822
+ }
823
+ function resolveActiveWorkspace(items, selectedId, options = {}) {
824
+ const selected = selectedId ? items.find((w) => w.id === selectedId) : void 0;
825
+ if (selected) return selected;
826
+ if (options.isSeed && options.seedActiveId) {
827
+ const seeded = items.find((w) => w.id === options.seedActiveId);
828
+ if (seeded) return seeded;
829
+ }
830
+ return items[0];
831
+ }
832
+ function pickRecentWorkspaces(items, activeId, options = {}) {
833
+ const { limit = 4, lastOpened, formatLastSeen } = options;
834
+ return items.filter((w) => w.id !== activeId).map((w) => ({ w, at: recency(w, lastOpened) })).sort((a, b) => b.at - a.at).slice(0, limit).map(({ w, at }) => formatLastSeen ? { ...w, lastSeen: formatLastSeen(at) } : w);
835
+ }
836
+
837
+ // src/workspaces/useShellWorkspaces.ts
838
+ function useShellWorkspaces(options) {
839
+ const {
840
+ workspaces,
841
+ isLoading = false,
842
+ loadError,
843
+ selectedId,
844
+ seed,
845
+ decorate,
846
+ recentLimit,
847
+ lastOpened,
848
+ formatLastSeen
849
+ } = options;
850
+ const live = useMemo(
851
+ () => (workspaces ?? []).map((source) => {
852
+ const item = toShellWorkspace(source);
853
+ return decorate ? { ...item, ...decorate(item, source) } : item;
854
+ }),
855
+ [workspaces, decorate]
856
+ );
857
+ const isLive = live.length > 0;
858
+ const isSeed = !isLive && !isLoading && !!seed;
859
+ const items = useMemo(
860
+ () => isLive ? live : isSeed ? [...seed.workspaces] : [],
861
+ [isLive, live, isSeed, seed]
862
+ );
863
+ const active = useMemo(
864
+ () => resolveActiveWorkspace(items, selectedId, { isSeed, seedActiveId: seed?.activeId }),
865
+ [items, selectedId, isSeed, seed?.activeId]
866
+ );
867
+ const recent = useMemo(
868
+ () => pickRecentWorkspaces(items, active?.id, { limit: recentLimit, lastOpened, formatLastSeen }),
869
+ [items, active?.id, recentLimit, lastOpened, formatLastSeen]
870
+ );
871
+ const [openingId, setOpeningId] = useState(null);
872
+ const [openError, setOpenError] = useState(void 0);
873
+ const callbacks = useRef({
874
+ select: options.select,
875
+ onOpened: options.onOpened,
876
+ onOpenError: options.onOpenError
877
+ });
878
+ callbacks.current = {
879
+ select: options.select,
880
+ onOpened: options.onOpened,
881
+ onOpenError: options.onOpenError
882
+ };
883
+ const itemsRef = useRef(items);
884
+ itemsRef.current = items;
885
+ const open = useCallback(async (target) => {
886
+ const id = typeof target === "string" ? target : target.id;
887
+ const workspace = itemsRef.current.find((w) => w.id === id);
888
+ if (!workspace) return false;
889
+ setOpenError(void 0);
890
+ setOpeningId(id);
891
+ try {
892
+ await callbacks.current.select(workspace);
893
+ } catch (err) {
894
+ setOpenError(err);
895
+ setOpeningId(null);
896
+ callbacks.current.onOpenError?.(err, workspace);
897
+ return false;
898
+ }
899
+ setOpeningId(null);
900
+ callbacks.current.onOpened?.(workspace);
901
+ return true;
902
+ }, []);
903
+ return {
904
+ items,
905
+ active,
906
+ recent,
907
+ isLive,
908
+ isLoading,
909
+ error: openError ?? loadError ?? null,
910
+ openingId,
911
+ open
912
+ };
913
+ }
914
+ function useArbiShellWorkspaces(options = {}) {
915
+ const arbi = useArbi();
916
+ const query = useWorkspaces();
917
+ const { select, ...rest } = options;
918
+ const defaultSelect = useCallback(
919
+ (workspace) => arbi.selectWorkspace(workspace.id),
920
+ [arbi]
921
+ );
922
+ return useShellWorkspaces({
923
+ ...rest,
924
+ workspaces: Array.isArray(query.data) ? query.data : void 0,
925
+ isLoading: query.isLoading,
926
+ loadError: query.error,
927
+ select: select ?? defaultSelect
928
+ });
929
+ }
930
+ function useCreateTask() {
931
+ const arbi = useArbi();
932
+ const qc = useQueryClient();
933
+ return useMutation({
934
+ mutationFn: (body) => arbi.tasks.create(body),
935
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.tasks() })
936
+ });
937
+ }
938
+ function useUpdateTask() {
939
+ const arbi = useArbi();
940
+ const qc = useQueryClient();
941
+ return useMutation({
942
+ mutationFn: ({ taskId, patch }) => arbi.tasks.update(taskId, patch),
943
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.tasks() })
944
+ });
945
+ }
946
+ function useRunTask() {
947
+ const arbi = useArbi();
948
+ const qc = useQueryClient();
949
+ return useMutation({
950
+ mutationFn: (taskId) => arbi.tasks.runWithAuth(taskId),
951
+ onSuccess: (_task, taskId) => Promise.all([
952
+ qc.invalidateQueries({ queryKey: arbiQueryKeys.tasks() }),
953
+ qc.invalidateQueries({ queryKey: arbiQueryKeys.taskRuns(taskId) })
954
+ ])
955
+ });
956
+ }
957
+ function useDeleteTask() {
958
+ const arbi = useArbi();
959
+ const qc = useQueryClient();
960
+ return useMutation({
961
+ mutationFn: (taskId) => arbi.tasks.delete(taskId),
962
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.tasks() })
963
+ });
964
+ }
965
+ function useTaskRuns(taskId, options) {
966
+ const arbi = useArbi();
967
+ return useQuery({
968
+ queryKey: arbiQueryKeys.taskRuns(taskId ?? ""),
969
+ queryFn: () => arbi.tasks.listRuns(taskId),
970
+ enabled: (options?.enabled ?? true) && !!taskId && arbi.isLoggedIn,
971
+ refetchInterval: (q) => q.state.data?.some((r) => r.status === "running") ? 3e3 : false
972
+ });
973
+ }
974
+ function useCreateAgent() {
975
+ const arbi = useArbi();
976
+ const qc = useQueryClient();
977
+ return useMutation({
978
+ mutationFn: (name) => arbi.agents.create(name),
979
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.agents() })
980
+ });
981
+ }
982
+ function useAgentConfig(agentExtId, options) {
983
+ const arbi = useArbi();
984
+ return useQuery({
985
+ queryKey: arbiQueryKeys.agentConfig(agentExtId),
986
+ queryFn: async () => agentExtId ? await arbi.agents.getConfig(agentExtId) : await arbi.agentConfig.get("default"),
987
+ enabled: (options?.enabled ?? true) && arbi.isLoggedIn
988
+ });
989
+ }
990
+ function useAgentModels(options) {
991
+ const arbi = useArbi();
992
+ return useQuery({
993
+ queryKey: arbiQueryKeys.models(),
994
+ queryFn: async () => (await arbi.agentConfig.getModels()).models.map((m) => m.model_name),
995
+ enabled: (options?.enabled ?? true) && arbi.isLoggedIn
996
+ });
997
+ }
998
+ function useSaveAgentConfig() {
999
+ const arbi = useArbi();
1000
+ const qc = useQueryClient();
1001
+ return useMutation({
1002
+ mutationFn: async ({ agentExtId, title, sections }) => {
1003
+ const pinned = await arbi.agents.getConfig(agentExtId);
1004
+ const base = pinned ?? await arbi.agentConfig.get("default");
1005
+ const merged = structuredClone(base);
1006
+ for (const [section, params] of Object.entries(sections)) {
1007
+ merged[section] = { ...merged[section] ?? {}, ...params };
1008
+ }
1009
+ return arbi.agentConfig.save({
1010
+ ...merged,
1011
+ agent_ext_id: agentExtId,
1012
+ title: title ?? `agent-${agentExtId}`
1013
+ });
1014
+ },
1015
+ onSuccess: (_saved, { agentExtId }) => Promise.all([
1016
+ qc.invalidateQueries({ queryKey: arbiQueryKeys.configs() }),
1017
+ qc.invalidateQueries({ queryKey: arbiQueryKeys.agentConfig(agentExtId) })
1018
+ ])
1019
+ });
1020
+ }
1021
+ function useAgentWorkspaces(agentExtId, options) {
1022
+ const arbi = useArbi();
1023
+ return useQuery({
1024
+ queryKey: arbiQueryKeys.agentWorkspaces(agentExtId ?? ""),
1025
+ queryFn: () => arbi.agents.listWorkspaces(agentExtId),
1026
+ enabled: (options?.enabled ?? true) && !!agentExtId && arbi.isLoggedIn
1027
+ });
1028
+ }
1029
+ function useGrantAgentAccess() {
1030
+ const arbi = useArbi();
1031
+ const qc = useQueryClient();
1032
+ return useMutation({
1033
+ mutationFn: async ({
1034
+ agentExtId,
1035
+ workspaceId,
1036
+ mode = { kind: "standing" }
1037
+ }) => {
1038
+ if (mode.kind === "temporary") {
1039
+ return arbi.agents.grantTemporaryAccess(agentExtId, workspaceId, mode.ttlSeconds);
1040
+ }
1041
+ const agent = (await arbi.agents.list()).find((a) => a.external_id === agentExtId);
1042
+ if (!agent?.email) throw new Error("The agent's address is unknown.");
1043
+ return arbi.workspaces.grantAccess({ workspaceId, emails: [agent.email] });
1044
+ },
1045
+ onSuccess: (_res, { agentExtId }) => qc.invalidateQueries({ queryKey: arbiQueryKeys.agentWorkspaces(agentExtId) })
1046
+ });
1047
+ }
1048
+ function useRevokeAgentAccess() {
1049
+ const arbi = useArbi();
1050
+ const qc = useQueryClient();
1051
+ return useMutation({
1052
+ mutationFn: async ({
1053
+ agentExtId,
1054
+ workspaceId
1055
+ }) => {
1056
+ const previous = arbi.hasWorkspace ? arbi.getWorkspaceId() : null;
1057
+ await arbi.selectWorkspace(workspaceId);
1058
+ try {
1059
+ await arbi.workspaces.removeUsers([agentExtId]);
1060
+ } finally {
1061
+ if (previous && previous !== workspaceId) await arbi.selectWorkspace(previous);
1062
+ }
1063
+ },
1064
+ onSuccess: (_res, { agentExtId }) => qc.invalidateQueries({ queryKey: arbiQueryKeys.agentWorkspaces(agentExtId) })
1065
+ });
1066
+ }
1067
+ function useSkills(options) {
1068
+ const arbi = useArbi();
1069
+ return useQuery({
1070
+ queryKey: [...arbiQueryKeys.skills(), options?.includeHidden ?? false],
1071
+ queryFn: () => arbi.assistant.listSkills({ includeHidden: options?.includeHidden }),
1072
+ enabled: (options?.enabled ?? true) && arbi.isLoggedIn,
1073
+ staleTime: 3e4
1074
+ });
1075
+ }
1076
+ function useContacts(options) {
1077
+ const arbi = useArbi();
1078
+ return useQuery({
1079
+ queryKey: arbiQueryKeys.contacts(),
1080
+ queryFn: () => arbi.contacts.list(),
1081
+ enabled: (options?.enabled ?? true) && arbi.isLoggedIn
1082
+ });
1083
+ }
1084
+ function usePendingContacts(options) {
1085
+ const contacts = useContacts(options);
1086
+ const pending = useMemo(
1087
+ () => (contacts.data ?? []).filter(
1088
+ (c) => c.status === "invitation_pending" || c.status === "invitation_expired"
1089
+ ),
1090
+ [contacts.data]
1091
+ );
1092
+ return { ...contacts, pending };
1093
+ }
1094
+ function useAddContacts() {
1095
+ const arbi = useArbi();
1096
+ const qc = useQueryClient();
1097
+ return useMutation({
1098
+ mutationFn: (emails) => arbi.contacts.add(emails),
1099
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.contacts() })
1100
+ });
1101
+ }
1102
+ function useRemoveContacts() {
1103
+ const arbi = useArbi();
1104
+ const qc = useQueryClient();
1105
+ return useMutation({
1106
+ mutationFn: (contactIds) => arbi.contacts.remove(contactIds),
1107
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.contacts() })
1108
+ });
1109
+ }
1619
1110
  function useWorkspaceDocs(workspaceId) {
1620
1111
  const { data: docs = [], isLoading, isError } = useDocuments(workspaceId);
1621
1112
  const docIds = useMemo(
@@ -2753,7 +2244,11 @@ var CitationRendererComponent = ({
2753
2244
  onDismissCitation,
2754
2245
  notify2
2755
2246
  ]);
2756
- return /* @__PURE__ */ jsx("div", { className: cn("citation-container", className), "data-testid": "citation-renderer", children: renderMarkdown({ components: markdownComponents, isStreaming, children: content }) });
2247
+ return /* @__PURE__ */ jsx("div", { className: cn("citation-container", className), "data-testid": "citation-renderer", children: renderMarkdown({
2248
+ components: markdownComponents,
2249
+ isStreaming,
2250
+ children: normalizeCitationMarkup(content)
2251
+ }) });
2757
2252
  };
2758
2253
  var CitationPopover = ({
2759
2254
  citationNum,
@@ -3787,7 +3282,6 @@ function isAgentUser(user) {
3787
3282
  return user.external_id.startsWith("agt-");
3788
3283
  }
3789
3284
  var DM_QUERY_KEY = ["arbi", "directMessages"];
3790
- var CONTACTS_QUERY_KEY = ["arbi", "contacts"];
3791
3285
  function mergeContacts(threads, contacts) {
3792
3286
  const byPeer = /* @__PURE__ */ new Map();
3793
3287
  for (const t of threads) byPeer.set(t.peer.external_id, t);
@@ -3853,7 +3347,7 @@ function useDirectMessages(options) {
3853
3347
  enabled
3854
3348
  });
3855
3349
  const contactsQuery = useQuery({
3856
- queryKey: CONTACTS_QUERY_KEY,
3350
+ queryKey: arbiQueryKeys.contacts(),
3857
3351
  queryFn: () => arbi.contacts.list(),
3858
3352
  enabled
3859
3353
  });
@@ -3938,6 +3432,7 @@ function ConversationList({
3938
3432
  classNames,
3939
3433
  emptyState,
3940
3434
  header,
3435
+ lead,
3941
3436
  className
3942
3437
  }) {
3943
3438
  return /* @__PURE__ */ jsxs(
@@ -3957,6 +3452,7 @@ function ConversationList({
3957
3452
  conversations.length === 1 ? "colleague" : "colleagues"
3958
3453
  ] })
3959
3454
  ] }) : header,
3455
+ lead,
3960
3456
  /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: conversations.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-4 py-6 text-xs text-muted-foreground", children: emptyState ?? "No conversations yet." }) : /* @__PURE__ */ jsx("ul", { className: "w-full", children: conversations.map((c, i) => {
3961
3457
  const { peer, lastMessage, unreadCount } = c;
3962
3458
  const newSection = c.sectionLabel && c.sectionLabel !== conversations[i - 1]?.sectionLabel;
@@ -4099,7 +3595,7 @@ function annotateThread(messages, accessors, runWindowMs = RUN_WINDOW_MS) {
4099
3595
  function DateSeparator({ children }) {
4100
3596
  return /* @__PURE__ */ jsx("div", { className: "my-3 flex justify-center", "data-testid": "dm-date-separator", children: /* @__PURE__ */ jsx("span", { className: "rounded-full bg-background/80 px-2 py-0.5 text-xs text-muted-foreground", children }) });
4101
3597
  }
4102
- function MessageBubble2({
3598
+ function MessageBubble({
4103
3599
  message,
4104
3600
  classNames,
4105
3601
  isLastOfRun = true,
@@ -4165,7 +3661,7 @@ function MessageThread({
4165
3661
  items.map(({ message: m, isLastOfRun, isFirstOfDay }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
4166
3662
  isFirstOfDay && formatDay && /* @__PURE__ */ jsx(DateSeparator, { children: formatDay(m.createdAt) }),
4167
3663
  /* @__PURE__ */ jsx(
4168
- MessageBubble2,
3664
+ MessageBubble,
4169
3665
  {
4170
3666
  message: m,
4171
3667
  classNames,
@@ -4243,14 +3739,117 @@ function DmComposer({
4243
3739
  error && /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-destructive", "data-testid": "arbi-dm-error", children: error })
4244
3740
  ] });
4245
3741
  }
3742
+ var DEFAULT_START_BY_EMAIL_LABELS = {
3743
+ email: "Email address",
3744
+ emailPlaceholder: "name@example.com",
3745
+ message: "Message",
3746
+ messagePlaceholder: "Write the first message\u2026",
3747
+ send: "Send",
3748
+ sending: "Sending\u2026",
3749
+ invalidEmail: "Enter a valid email address."
3750
+ };
3751
+ var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3752
+ function StartByEmail({
3753
+ onStart,
3754
+ onStarted,
3755
+ classNames,
3756
+ labels,
3757
+ className
3758
+ }) {
3759
+ const l = { ...DEFAULT_START_BY_EMAIL_LABELS, ...labels };
3760
+ const uid = useId();
3761
+ const [email, setEmail] = useState("");
3762
+ const [text, setText] = useState("");
3763
+ const [busy, setBusy] = useState(false);
3764
+ const [error, setError] = useState(null);
3765
+ const address = email.trim().toLowerCase();
3766
+ const valid = EMAIL.test(address);
3767
+ const submit = async () => {
3768
+ if (!valid || !text.trim() || busy) return;
3769
+ setBusy(true);
3770
+ setError(null);
3771
+ try {
3772
+ const result = await onStart(address, text.trim());
3773
+ setEmail("");
3774
+ setText("");
3775
+ onStarted?.(result);
3776
+ } catch (e) {
3777
+ setError(e instanceof Error ? e.message : String(e));
3778
+ } finally {
3779
+ setBusy(false);
3780
+ }
3781
+ };
3782
+ return /* @__PURE__ */ jsxs(
3783
+ "form",
3784
+ {
3785
+ "data-testid": "arbi-dm-start",
3786
+ className: cn("flex flex-col gap-2 border-b border-border px-4 py-3", className),
3787
+ onSubmit: (e) => {
3788
+ e.preventDefault();
3789
+ void submit();
3790
+ },
3791
+ children: [
3792
+ /* @__PURE__ */ jsx(Label, { htmlFor: `${uid}-email`, children: l.email }),
3793
+ /* @__PURE__ */ jsx(
3794
+ Input,
3795
+ {
3796
+ id: `${uid}-email`,
3797
+ type: "email",
3798
+ value: email,
3799
+ onChange: (e) => setEmail(e.target.value),
3800
+ placeholder: l.emailPlaceholder,
3801
+ "aria-invalid": email !== "" && !valid,
3802
+ "aria-describedby": email !== "" && !valid ? `${uid}-bad` : void 0,
3803
+ disabled: busy,
3804
+ "data-testid": "arbi-dm-start-email"
3805
+ }
3806
+ ),
3807
+ email !== "" && !valid && /* @__PURE__ */ jsx("p", { id: `${uid}-bad`, className: "text-xs text-destructive", children: l.invalidEmail }),
3808
+ /* @__PURE__ */ jsx(Label, { htmlFor: `${uid}-text`, children: l.message }),
3809
+ /* @__PURE__ */ jsx(
3810
+ Textarea,
3811
+ {
3812
+ id: `${uid}-text`,
3813
+ value: text,
3814
+ onChange: (e) => setText(e.target.value),
3815
+ placeholder: l.messagePlaceholder,
3816
+ rows: 2,
3817
+ disabled: busy,
3818
+ "data-testid": "arbi-dm-start-message"
3819
+ }
3820
+ ),
3821
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-xs text-destructive", "data-testid": "arbi-dm-start-error", children: error }),
3822
+ /* @__PURE__ */ jsxs(
3823
+ Button,
3824
+ {
3825
+ type: "submit",
3826
+ size: "sm",
3827
+ disabled: !valid || !text.trim() || busy,
3828
+ className: cn("self-end", classNames?.sendButton),
3829
+ "data-testid": "arbi-dm-start-send",
3830
+ children: [
3831
+ busy ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsx(Send, { className: "size-4", "aria-hidden": true }),
3832
+ busy ? l.sending : l.send
3833
+ ]
3834
+ }
3835
+ )
3836
+ ]
3837
+ }
3838
+ );
3839
+ }
4246
3840
  function DirectMessages({
4247
3841
  className,
4248
3842
  classNames,
4249
- emptyThread,
4250
- emptyList
3843
+ emptyThread: emptyThread2,
3844
+ emptyList,
3845
+ listHeader,
3846
+ listLead,
3847
+ startByEmail,
3848
+ hideOwnContact
4251
3849
  }) {
4252
3850
  const {
4253
- conversations,
3851
+ conversations: allConversations,
3852
+ myExtId,
4254
3853
  isLoading,
4255
3854
  isError,
4256
3855
  error,
@@ -4260,6 +3859,14 @@ function DirectMessages({
4260
3859
  markRead
4261
3860
  } = useDirectMessages();
4262
3861
  const [selectedPeerId, setSelectedPeerId] = useState(null);
3862
+ const conversations = useMemo(
3863
+ () => hideOwnContact && myExtId ? allConversations.filter((c) => c.peer.external_id !== myExtId) : allConversations,
3864
+ [allConversations, hideOwnContact, myExtId]
3865
+ );
3866
+ const openStarted = (created) => {
3867
+ const peer = Array.isArray(created) ? created[0]?.recipient?.external_id : void 0;
3868
+ if (peer) setSelectedPeerId(peer);
3869
+ };
4263
3870
  const loadError = isError ? error instanceof Error ? error.message : "Couldn\u2019t load messages." : null;
4264
3871
  const selected = useMemo(
4265
3872
  () => conversations.find((c) => c.peer.external_id === selectedPeerId) ?? null,
@@ -4303,11 +3910,24 @@ function DirectMessages({
4303
3910
  selectedPeerId: selected?.peer.external_id ?? null,
4304
3911
  onSelect: setSelectedPeerId,
4305
3912
  classNames,
4306
- emptyState: isLoading ? "Loading messages\u2026" : emptyList
3913
+ emptyState: isLoading ? "Loading messages\u2026" : emptyList,
3914
+ header: listHeader,
3915
+ lead: /* @__PURE__ */ jsxs(Fragment, { children: [
3916
+ listLead,
3917
+ startByEmail && /* @__PURE__ */ jsx(
3918
+ StartByEmail,
3919
+ {
3920
+ onStart: sendMessageTo,
3921
+ onStarted: openStarted,
3922
+ classNames,
3923
+ labels: typeof startByEmail === "object" ? startByEmail.labels : void 0
3924
+ }
3925
+ )
3926
+ ] })
4307
3927
  }
4308
3928
  ),
4309
3929
  /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", children: [
4310
- /* @__PURE__ */ jsx(MessageThread, { conversation: selected, classNames, emptyState: emptyThread }),
3930
+ /* @__PURE__ */ jsx(MessageThread, { conversation: selected, classNames, emptyState: emptyThread2 }),
4311
3931
  selected ? /* @__PURE__ */ jsx(
4312
3932
  DmComposer,
4313
3933
  {
@@ -5712,6 +5332,6 @@ function hasVideoTrack(blob) {
5712
5332
  });
5713
5333
  }
5714
5334
 
5715
- export { ActivityFeed, AiActionCard, AiAnswer, AppleIcon, ArbiBotIcon, ArbiChat, ArbiChatPanel, ArbiIcon, ArbiWebSocketProvider, ArtifactCanvas, ChatBubble, CitationRenderer, ClaudeIcon, CommandPalette, CommandPaletteProvider, ConversationList, DEFAULT_ASPECT_OPTIONS, DateSeparator, DirectMessages, DmAvatar, DmComposer, DocIcon, DocQaPanel, DocSearch, DocumentCardMenu, DocumentGallery, DocumentReview, DocumentTable, DraftTitleControls, FileIcon, FireworksIcon, GeminiIcon, GitHubIcon, GoogleIcon, HtmlIcon, ImageCropModal, ImageCropper, LinkedInIcon, MessageArtifactCard, MessageBubble2 as MessageBubble, MessageThread, OpenAIIcon, PhotoCapture, ProviderIcon, RUN_WINDOW_MS, SemanticSearch, TxtIcon, TypingIndicator, VideoCapture, VoiceNoteRecorder, XlsIcon, annotateThread, arbiQueryKeys, captureFrameAsBlob, cropImage, dayKey, describeActivity, getNotificationTypeMeta, getWorkspaceActionText, getWorkspaceSentPhrase, hasVideoTrack, isWorkspaceNotificationType, parseActivityContent, redlineStats, textToArtifact, toRedlineMarkdown, useActivity, useAgents, useAiTask, useArbiConnected, useArbiDocumentSync, useArbiUiStore, useArbiWebSocket, useArbiWsContext, useArbiWsMessage, useArbiWsStatus, useArbiWsStore, useArbiWsTask, useArbiWsTasks, useAssistantQuery, useCameraStream, useCommandPalette, useConfigs, useConversations, useDeleteAgents, useDirectMessages, useDocuments, useFacts, useHealth, useParsedDocument, useProjectInvoices, useProjectUsage, useProjects, useProjectsUsage, useSemanticSearch, useSessions, useTags, useTasks, useThumbnails, useUserUsageToday, useVideoRecorder, useWorkspaceDocs, useWorkspaces, userDisplayName, userInitials };
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 };
5716
5336
  //# sourceMappingURL=index.js.map
5717
5337
  //# sourceMappingURL=index.js.map