@arbidocs/react 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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, 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,6 @@ 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
- };
243
- }
244
- function docListStyle() {
245
- return {
246
- marginTop: "6px",
247
- display: "flex",
248
- flexDirection: "column",
249
- gap: "4px",
250
- maxHeight: "120px",
251
- overflowY: "auto"
252
- };
253
- }
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
- ] });
585
- }
586
26
  var arbiQueryKeys = {
587
27
  workspaces: () => ["arbi", "workspaces"],
588
28
  documents: (workspaceId) => ["arbi", "documents", workspaceId],
@@ -816,524 +256,220 @@ function useUserUsageToday(options) {
816
256
  enabled: options?.enabled ?? arbi.isLoggedIn
817
257
  });
818
258
  }
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 }) {
259
+
260
+ // src/chat/useArbiChat.ts
261
+ var counter = 0;
262
+ var nextId = (prefix) => `${prefix}-${Date.now().toString(36)}-${(counter++).toString(36)}`;
263
+ var emptyThread = () => ({
264
+ id: nextId("thread"),
265
+ title: "",
266
+ messages: [],
267
+ responseId: null
268
+ });
269
+ function titleFromQuestion(question, maxLength = 48) {
270
+ const flat = question.replace(/\s+/g, " ").trim();
271
+ if (flat.length <= maxLength) return flat;
272
+ const cut = flat.slice(0, maxLength);
273
+ const lastSpace = cut.lastIndexOf(" ");
274
+ const words = lastSpace > maxLength / 2 ? cut.slice(0, lastSpace) : cut;
275
+ return `${words.replace(/[\s,;:.\-–—]+$/, "")}\u2026`;
276
+ }
277
+ var messageOf = (e, fallback) => e instanceof Error ? e.message : fallback;
278
+ function useArbiChat(options = {}) {
279
+ const { workspaceId, model, titleLength = 48 } = options;
1057
280
  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]);
281
+ const { data: docs = [], isLoading: isLoadingDocuments, refetch } = useDocuments(workspaceId);
282
+ const query = useAssistantQuery();
283
+ const { mutateAsync } = query;
284
+ const [threads, setThreads] = useState(() => [emptyThread()]);
285
+ const [activeId, setActiveId] = useState(() => threads[0].id);
286
+ const [deselected, setDeselected] = useState(/* @__PURE__ */ new Set());
287
+ const [isRunning, setIsRunning] = useState(false);
288
+ const [isUploading, setIsUploading] = useState(false);
289
+ const [uploadError, setUploadError] = useState(null);
290
+ const scopeRef = useRef(workspaceId);
1075
291
  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]
292
+ if (scopeRef.current === workspaceId) return;
293
+ scopeRef.current = workspaceId;
294
+ const fresh = emptyThread();
295
+ setThreads([fresh]);
296
+ setActiveId(fresh.id);
297
+ setDeselected(/* @__PURE__ */ new Set());
298
+ setUploadError(null);
299
+ }, [workspaceId]);
300
+ const active = threads.find((t) => t.id === activeId) ?? threads[0];
301
+ const selectedIds = useMemo(
302
+ () => docs.map((d) => d.external_id).filter((id) => !!id && !deselected.has(id)),
303
+ [docs, deselected]
1103
304
  );
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),
305
+ const documents = useMemo(
306
+ () => docs.filter((d) => !!d.external_id).map((d) => ({
307
+ id: d.external_id,
308
+ name: d.file_name ?? d.external_id,
309
+ isSelected: !deselected.has(d.external_id)
310
+ })),
311
+ [docs, deselected]
312
+ );
313
+ const patchThread = useCallback(
314
+ (id, patch) => setThreads((all) => all.map((t) => t.id === id ? patch(t) : t)),
315
+ []
316
+ );
317
+ const patchMessage = useCallback(
318
+ (threadId, messageId, patch) => patchThread(threadId, (t) => ({
319
+ ...t,
320
+ messages: t.messages.map((m) => m.id === messageId ? { ...m, ...patch } : m)
321
+ })),
322
+ [patchThread]
323
+ );
324
+ const ask = useCallback(
325
+ async (thread, text) => {
326
+ const userMessage = {
327
+ id: nextId("msg"),
328
+ role: "user",
329
+ content: text,
330
+ status: "complete"
331
+ };
332
+ const answer = {
333
+ id: nextId("msg"),
334
+ role: "assistant",
335
+ content: "",
336
+ status: "streaming"
337
+ };
338
+ patchThread(thread.id, (t) => ({
339
+ ...t,
340
+ title: t.title || titleFromQuestion(text, titleLength),
341
+ messages: [...t.messages, userMessage, answer]
342
+ }));
343
+ setIsRunning(true);
344
+ let buffer = "";
345
+ let steps = 0;
346
+ let streamError = null;
347
+ try {
348
+ const result = await mutateAsync({
349
+ question: text,
350
+ docIds: selectedIds,
351
+ previousResponseId: thread.responseId,
352
+ model,
353
+ onToken: (token) => {
354
+ buffer += token;
355
+ patchMessage(thread.id, answer.id, { content: buffer });
356
+ },
1119
357
  onOutputItemDone: (ev) => {
1120
- const item = ev.item;
1121
- if (item.type === "function_call" || item.type === "reasoning") {
1122
- setAgentStatus(item.arbi?.focus || item.arbi?.label || "");
358
+ const type = ev.item.type;
359
+ if (type === "function_call" || type === "reasoning") {
360
+ steps += 1;
361
+ patchMessage(thread.id, answer.id, { stepCount: steps });
1123
362
  }
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
363
  },
1136
- onError: (err) => {
1137
- setStreaming("");
1138
- setAgentStatus("");
1139
- setMessages((prev) => [...prev, { role: "assistant", text: `Error: ${err.message}` }]);
364
+ onError: (message) => {
365
+ streamError = message;
1140
366
  }
367
+ });
368
+ const finalText = result.text || buffer;
369
+ const failure = streamError || result.errors[0] || null;
370
+ if (failure && !finalText) {
371
+ patchMessage(thread.id, answer.id, { status: "error", error: failure });
372
+ } else {
373
+ patchMessage(thread.id, answer.id, {
374
+ status: "complete",
375
+ content: finalText,
376
+ stepCount: result.agentSteps.length || steps || void 0
377
+ });
378
+ patchThread(thread.id, (t) => ({
379
+ ...t,
380
+ responseId: result.assistantMessageExtId ?? t.responseId
381
+ }));
1141
382
  }
1142
- );
383
+ } catch (e) {
384
+ patchMessage(thread.id, answer.id, {
385
+ status: "error",
386
+ error: messageOf(e, "The assistant request failed.")
387
+ });
388
+ } finally {
389
+ setIsRunning(false);
390
+ }
1143
391
  },
1144
- [input, selectedDocs, previousResponseId, mutation]
392
+ [mutateAsync, patchMessage, patchThread, selectedIds, model, titleLength]
1145
393
  );
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
- ] });
1235
- }
1236
- function ChatWidget({ theme, labels, initialWorkspaceId, width, height }) {
1237
- 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]);
1254
- 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"
1285
- }
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
- }
394
+ const send = useCallback(
395
+ async (text) => {
396
+ const trimmed = text.trim();
397
+ if (!trimmed || isRunning) return;
398
+ await ask(active, trimmed);
399
+ },
400
+ [active, ask, isRunning]
1308
401
  );
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 }
402
+ const lastMessage = active.messages[active.messages.length - 1];
403
+ const error = lastMessage?.status === "error" ? lastMessage.error ?? null : null;
404
+ const retry = useCallback(async () => {
405
+ if (isRunning) return;
406
+ const messages = active.messages;
407
+ const failed = messages[messages.length - 1];
408
+ const question = messages[messages.length - 2];
409
+ if (failed?.status !== "error" || question?.role !== "user") return;
410
+ const trimmed = { ...active, messages: messages.slice(0, -2) };
411
+ patchThread(active.id, () => trimmed);
412
+ await ask(trimmed, question.content);
413
+ }, [active, ask, isRunning, patchThread]);
414
+ const newThread = useCallback(() => {
415
+ if (active.messages.length === 0) return;
416
+ const fresh = emptyThread();
417
+ setThreads((all) => [fresh, ...all]);
418
+ setActiveId(fresh.id);
419
+ }, [active.messages.length]);
420
+ const deleteThread = useCallback(
421
+ (id) => {
422
+ const rest = threads.filter((t) => t.id !== id);
423
+ const next = rest.length > 0 ? rest : [emptyThread()];
424
+ setThreads(next);
425
+ if (id === activeId) setActiveId(next[0].id);
426
+ },
427
+ [threads, activeId]
428
+ );
429
+ const toggleDocument = useCallback((id) => {
430
+ setDeselected((prev) => {
431
+ const next = new Set(prev);
432
+ if (next.has(id)) next.delete(id);
433
+ else next.add(id);
434
+ return next;
435
+ });
436
+ }, []);
437
+ const selectAllDocuments = useCallback(() => setDeselected(/* @__PURE__ */ new Set()), []);
438
+ const upload = useCallback(
439
+ async (files) => {
440
+ if (files.length === 0) return;
441
+ setIsUploading(true);
442
+ setUploadError(null);
443
+ try {
444
+ for (const file of files) await arbi.documents.uploadFile(file, file.name);
445
+ await refetch();
446
+ } catch (e) {
447
+ setUploadError(messageOf(e, "The upload failed."));
448
+ } finally {
449
+ setIsUploading(false);
1323
450
  }
1324
- }),
1325
- []
451
+ },
452
+ [arbi, refetch]
1326
453
  );
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
- ) }) });
454
+ return {
455
+ threads: threads.map(({ id, title }) => ({ id, title })),
456
+ activeThreadId: active.id,
457
+ messages: active.messages,
458
+ isRunning,
459
+ error,
460
+ documents,
461
+ isLoadingDocuments,
462
+ isUploading,
463
+ uploadError,
464
+ send,
465
+ retry,
466
+ selectThread: setActiveId,
467
+ newThread,
468
+ deleteThread,
469
+ toggleDocument,
470
+ selectAllDocuments,
471
+ upload
472
+ };
1337
473
  }
1338
474
  var TypingIndicator = memo(function TypingIndicator2() {
1339
475
  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 +752,144 @@ function useArbiDocumentSync(workspaceId) {
1616
752
  }
1617
753
  });
1618
754
  }
755
+
756
+ // src/workspaces/shell-workspaces.ts
757
+ function getWorkspaceKind(workspace) {
758
+ if (workspace.is_public) return "common";
759
+ if ((workspace.users?.length ?? 0) > 1) return "shared";
760
+ return "private";
761
+ }
762
+ function memberName(user) {
763
+ const full = [user?.given_name, user?.family_name].filter(Boolean).join(" ").trim();
764
+ return full || user?.email || "";
765
+ }
766
+ function toShellWorkspace(workspace) {
767
+ const people = (workspace.users ?? []).filter((u) => !u.agent_ext_id);
768
+ return {
769
+ id: workspace.external_id,
770
+ name: workspace.name,
771
+ kind: getWorkspaceKind(workspace),
772
+ description: workspace.description,
773
+ projectName: workspace.project_name,
774
+ docCount: (workspace.shared_document_count ?? 0) + (workspace.private_document_count ?? 0),
775
+ memberCount: people.length,
776
+ members: people.map((u) => ({ name: memberName(u.user), picture: u.user?.picture })),
777
+ updatedAt: workspace.updated_at
778
+ };
779
+ }
780
+ function recency(workspace, lastOpened) {
781
+ const opened = lastOpened?.[workspace.id];
782
+ if (opened !== void 0) return opened;
783
+ const updated = Date.parse(workspace.updatedAt ?? "");
784
+ return Number.isNaN(updated) ? 0 : updated;
785
+ }
786
+ function resolveActiveWorkspace(items, selectedId, options = {}) {
787
+ const selected = selectedId ? items.find((w) => w.id === selectedId) : void 0;
788
+ if (selected) return selected;
789
+ if (options.isSeed && options.seedActiveId) {
790
+ const seeded = items.find((w) => w.id === options.seedActiveId);
791
+ if (seeded) return seeded;
792
+ }
793
+ return items[0];
794
+ }
795
+ function pickRecentWorkspaces(items, activeId, options = {}) {
796
+ const { limit = 4, lastOpened, formatLastSeen } = options;
797
+ 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);
798
+ }
799
+
800
+ // src/workspaces/useShellWorkspaces.ts
801
+ function useShellWorkspaces(options) {
802
+ const {
803
+ workspaces,
804
+ isLoading = false,
805
+ loadError,
806
+ selectedId,
807
+ seed,
808
+ decorate,
809
+ recentLimit,
810
+ lastOpened,
811
+ formatLastSeen
812
+ } = options;
813
+ const live = useMemo(
814
+ () => (workspaces ?? []).map((source) => {
815
+ const item = toShellWorkspace(source);
816
+ return decorate ? { ...item, ...decorate(item, source) } : item;
817
+ }),
818
+ [workspaces, decorate]
819
+ );
820
+ const isLive = live.length > 0;
821
+ const isSeed = !isLive && !isLoading && !!seed;
822
+ const items = useMemo(
823
+ () => isLive ? live : isSeed ? [...seed.workspaces] : [],
824
+ [isLive, live, isSeed, seed]
825
+ );
826
+ const active = useMemo(
827
+ () => resolveActiveWorkspace(items, selectedId, { isSeed, seedActiveId: seed?.activeId }),
828
+ [items, selectedId, isSeed, seed?.activeId]
829
+ );
830
+ const recent = useMemo(
831
+ () => pickRecentWorkspaces(items, active?.id, { limit: recentLimit, lastOpened, formatLastSeen }),
832
+ [items, active?.id, recentLimit, lastOpened, formatLastSeen]
833
+ );
834
+ const [openingId, setOpeningId] = useState(null);
835
+ const [openError, setOpenError] = useState(void 0);
836
+ const callbacks = useRef({
837
+ select: options.select,
838
+ onOpened: options.onOpened,
839
+ onOpenError: options.onOpenError
840
+ });
841
+ callbacks.current = {
842
+ select: options.select,
843
+ onOpened: options.onOpened,
844
+ onOpenError: options.onOpenError
845
+ };
846
+ const itemsRef = useRef(items);
847
+ itemsRef.current = items;
848
+ const open = useCallback(async (target) => {
849
+ const id = typeof target === "string" ? target : target.id;
850
+ const workspace = itemsRef.current.find((w) => w.id === id);
851
+ if (!workspace) return false;
852
+ setOpenError(void 0);
853
+ setOpeningId(id);
854
+ try {
855
+ await callbacks.current.select(workspace);
856
+ } catch (err) {
857
+ setOpenError(err);
858
+ setOpeningId(null);
859
+ callbacks.current.onOpenError?.(err, workspace);
860
+ return false;
861
+ }
862
+ setOpeningId(null);
863
+ callbacks.current.onOpened?.(workspace);
864
+ return true;
865
+ }, []);
866
+ return {
867
+ items,
868
+ active,
869
+ recent,
870
+ isLive,
871
+ isLoading,
872
+ error: openError ?? loadError ?? null,
873
+ openingId,
874
+ open
875
+ };
876
+ }
877
+ function useArbiShellWorkspaces(options = {}) {
878
+ const arbi = useArbi();
879
+ const query = useWorkspaces();
880
+ const { select, ...rest } = options;
881
+ const defaultSelect = useCallback(
882
+ (workspace) => arbi.selectWorkspace(workspace.id),
883
+ [arbi]
884
+ );
885
+ return useShellWorkspaces({
886
+ ...rest,
887
+ workspaces: Array.isArray(query.data) ? query.data : void 0,
888
+ isLoading: query.isLoading,
889
+ loadError: query.error,
890
+ select: select ?? defaultSelect
891
+ });
892
+ }
1619
893
  function useWorkspaceDocs(workspaceId) {
1620
894
  const { data: docs = [], isLoading, isError } = useDocuments(workspaceId);
1621
895
  const docIds = useMemo(
@@ -2753,7 +2027,11 @@ var CitationRendererComponent = ({
2753
2027
  onDismissCitation,
2754
2028
  notify2
2755
2029
  ]);
2756
- return /* @__PURE__ */ jsx("div", { className: cn("citation-container", className), "data-testid": "citation-renderer", children: renderMarkdown({ components: markdownComponents, isStreaming, children: content }) });
2030
+ return /* @__PURE__ */ jsx("div", { className: cn("citation-container", className), "data-testid": "citation-renderer", children: renderMarkdown({
2031
+ components: markdownComponents,
2032
+ isStreaming,
2033
+ children: normalizeCitationMarkup(content)
2034
+ }) });
2757
2035
  };
2758
2036
  var CitationPopover = ({
2759
2037
  citationNum,
@@ -4099,7 +3377,7 @@ function annotateThread(messages, accessors, runWindowMs = RUN_WINDOW_MS) {
4099
3377
  function DateSeparator({ children }) {
4100
3378
  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
3379
  }
4102
- function MessageBubble2({
3380
+ function MessageBubble({
4103
3381
  message,
4104
3382
  classNames,
4105
3383
  isLastOfRun = true,
@@ -4165,7 +3443,7 @@ function MessageThread({
4165
3443
  items.map(({ message: m, isLastOfRun, isFirstOfDay }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
4166
3444
  isFirstOfDay && formatDay && /* @__PURE__ */ jsx(DateSeparator, { children: formatDay(m.createdAt) }),
4167
3445
  /* @__PURE__ */ jsx(
4168
- MessageBubble2,
3446
+ MessageBubble,
4169
3447
  {
4170
3448
  message: m,
4171
3449
  classNames,
@@ -4246,7 +3524,7 @@ function DmComposer({
4246
3524
  function DirectMessages({
4247
3525
  className,
4248
3526
  classNames,
4249
- emptyThread,
3527
+ emptyThread: emptyThread2,
4250
3528
  emptyList
4251
3529
  }) {
4252
3530
  const {
@@ -4307,7 +3585,7 @@ function DirectMessages({
4307
3585
  }
4308
3586
  ),
4309
3587
  /* @__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 }),
3588
+ /* @__PURE__ */ jsx(MessageThread, { conversation: selected, classNames, emptyState: emptyThread2 }),
4311
3589
  selected ? /* @__PURE__ */ jsx(
4312
3590
  DmComposer,
4313
3591
  {
@@ -5712,6 +4990,6 @@ function hasVideoTrack(blob) {
5712
4990
  });
5713
4991
  }
5714
4992
 
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 };
4993
+ export { ActivityFeed, AiActionCard, AiAnswer, AppleIcon, ArbiBotIcon, 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, MessageBubble, MessageThread, OpenAIIcon, PhotoCapture, ProviderIcon, RUN_WINDOW_MS, SemanticSearch, TxtIcon, TypingIndicator, VideoCapture, VoiceNoteRecorder, XlsIcon, annotateThread, arbiQueryKeys, captureFrameAsBlob, cropImage, dayKey, describeActivity, getNotificationTypeMeta, getWorkspaceActionText, getWorkspaceKind, getWorkspaceSentPhrase, hasVideoTrack, isWorkspaceNotificationType, parseActivityContent, pickRecentWorkspaces, redlineStats, resolveActiveWorkspace, textToArtifact, titleFromQuestion, toRedlineMarkdown, toShellWorkspace, useActivity, useAgents, useAiTask, useArbiChat, useArbiConnected, useArbiDocumentSync, useArbiShellWorkspaces, 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, useShellWorkspaces, useTags, useTasks, useThumbnails, useUserUsageToday, useVideoRecorder, useWorkspaceDocs, useWorkspaces, userDisplayName, userInitials };
5716
4994
  //# sourceMappingURL=index.js.map
5717
4995
  //# sourceMappingURL=index.js.map