@arbidocs/react 0.3.15 → 0.3.17

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,8 +1,8 @@
1
- import { createContext, useMemo, useContext } from 'react';
2
- import { Arbi } from '@arbidocs/sdk/browser';
1
+ import { createContext, useMemo, useContext, useState, useCallback, useRef, useEffect } from 'react';
2
+ import { Arbi, resolveCitations, summarizeCitations } from '@arbidocs/sdk/browser';
3
3
  export { Arbi, ArbiError } from '@arbidocs/sdk/browser';
4
- import { jsx } from 'react/jsx-runtime';
5
- import { useQuery, useMutation } from '@tanstack/react-query';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+ import { useQuery, useMutation, QueryClient, QueryClientProvider } from '@tanstack/react-query';
6
6
 
7
7
  // src/ArbiProvider.tsx
8
8
  var ArbiContext = createContext(null);
@@ -15,6 +15,329 @@ function ArbiProvider({ children, ...options }) {
15
15
  );
16
16
  return /* @__PURE__ */ jsx(ArbiContext.Provider, { value: arbi, children });
17
17
  }
18
+
19
+ // src/chat/styles.ts
20
+ var DEFAULT_THEME = {
21
+ primaryColor: "#00adef",
22
+ backgroundColor: "#ffffff",
23
+ surfaceColor: "#f9fafb",
24
+ textColor: "#111827",
25
+ mutedColor: "#6b7280",
26
+ borderColor: "#e5e7eb",
27
+ userBubbleColor: "#f3f4f6",
28
+ userBubbleTextColor: "#111827",
29
+ assistantBubbleColor: "rgba(0, 173, 239, 0.08)",
30
+ assistantBubbleTextColor: "#111827",
31
+ borderRadius: "8px",
32
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif"
33
+ };
34
+ var DEFAULT_LABELS = {
35
+ title: "Chat",
36
+ email: "Email",
37
+ password: "Password",
38
+ loginButton: "Log in",
39
+ loginButtonLoading: "Logging in...",
40
+ registerButton: "Create account",
41
+ sendCodeButton: "Email me a code",
42
+ haveCodeButton: "I have an invitation code",
43
+ verificationCode: "Verification code",
44
+ confirmPassword: "Confirm password",
45
+ firstName: "First name",
46
+ lastName: "Last name",
47
+ switchToRegister: "Create account",
48
+ switchToLogin: "Back to login",
49
+ selectWorkspace: "Select a workspace",
50
+ workspacePlaceholder: "Choose...",
51
+ continueButton: "Continue",
52
+ createWorkspace: "Create new workspace",
53
+ workspaceName: "Workspace name",
54
+ createButton: "Create",
55
+ changeWorkspace: "Change workspace",
56
+ documents: "Documents",
57
+ sendButton: "Send",
58
+ placeholder: "Ask a question...",
59
+ uploadButton: "Upload",
60
+ uploading: "Uploading...",
61
+ emptyNoDocs: "No documents found in this workspace.",
62
+ emptyNoSelection: "Select at least one document to start chatting.",
63
+ emptyReady: "Ask a question about your documents."
64
+ };
65
+ function getTheme(partial) {
66
+ return { ...DEFAULT_THEME, ...partial };
67
+ }
68
+ function getLabels(partial) {
69
+ return { ...DEFAULT_LABELS, ...partial };
70
+ }
71
+ function containerStyle(theme, width, height) {
72
+ return {
73
+ width: typeof width === "number" ? `${width}px` : width,
74
+ height: typeof height === "number" ? `${height}px` : height,
75
+ display: "flex",
76
+ flexDirection: "column",
77
+ fontFamily: theme.fontFamily,
78
+ fontSize: "14px",
79
+ lineHeight: "1.5",
80
+ color: theme.textColor,
81
+ backgroundColor: theme.backgroundColor,
82
+ border: `1px solid ${theme.borderColor}`,
83
+ borderRadius: theme.borderRadius,
84
+ overflow: "hidden",
85
+ boxSizing: "border-box"
86
+ };
87
+ }
88
+ function headerStyle(theme) {
89
+ return {
90
+ display: "flex",
91
+ alignItems: "center",
92
+ padding: "12px 16px",
93
+ fontWeight: 600,
94
+ fontSize: "15px",
95
+ borderBottom: `1px solid ${theme.borderColor}`,
96
+ flexShrink: 0
97
+ };
98
+ }
99
+ function bodyStyle() {
100
+ return {
101
+ flex: 1,
102
+ display: "flex",
103
+ flexDirection: "column",
104
+ overflow: "hidden"
105
+ };
106
+ }
107
+ function formContainerStyle() {
108
+ return {
109
+ flex: 1,
110
+ display: "flex",
111
+ flexDirection: "column",
112
+ justifyContent: "center",
113
+ padding: "24px 16px",
114
+ gap: "12px"
115
+ };
116
+ }
117
+ function labelStyle(theme) {
118
+ return {
119
+ fontSize: "13px",
120
+ fontWeight: 500,
121
+ color: theme.textColor,
122
+ marginBottom: "4px"
123
+ };
124
+ }
125
+ function inputStyle(theme) {
126
+ return {
127
+ width: "100%",
128
+ padding: "8px 12px",
129
+ fontSize: "14px",
130
+ fontFamily: theme.fontFamily,
131
+ color: theme.textColor,
132
+ backgroundColor: theme.backgroundColor,
133
+ border: `1px solid ${theme.borderColor}`,
134
+ borderRadius: "8px",
135
+ outline: "none",
136
+ boxSizing: "border-box"
137
+ };
138
+ }
139
+ function selectStyle(theme) {
140
+ return {
141
+ ...inputStyle(theme),
142
+ cursor: "pointer",
143
+ appearance: "auto"
144
+ };
145
+ }
146
+ function buttonStyle(theme, disabled) {
147
+ return {
148
+ padding: "8px 16px",
149
+ fontSize: "14px",
150
+ fontWeight: 500,
151
+ fontFamily: theme.fontFamily,
152
+ color: "#ffffff",
153
+ backgroundColor: disabled ? theme.mutedColor : theme.primaryColor,
154
+ border: "none",
155
+ borderRadius: "8px",
156
+ cursor: disabled ? "not-allowed" : "pointer",
157
+ opacity: disabled ? 0.6 : 1,
158
+ transition: "opacity 150ms"
159
+ };
160
+ }
161
+ function errorStyle() {
162
+ return {
163
+ fontSize: "13px",
164
+ color: "#ef4444",
165
+ margin: "4px 0 0"
166
+ };
167
+ }
168
+ function messagesContainerStyle() {
169
+ return {
170
+ flex: 1,
171
+ overflowY: "auto",
172
+ padding: "12px 16px",
173
+ display: "flex",
174
+ flexDirection: "column",
175
+ gap: "8px"
176
+ };
177
+ }
178
+ function messageBubbleStyle(theme, role) {
179
+ const isUser = role === "user";
180
+ return {
181
+ maxWidth: "85%",
182
+ padding: "8px 12px",
183
+ borderRadius: "12px",
184
+ fontSize: "14px",
185
+ lineHeight: "1.5",
186
+ wordBreak: "break-word",
187
+ whiteSpace: "pre-wrap",
188
+ alignSelf: isUser ? "flex-end" : "flex-start",
189
+ backgroundColor: isUser ? theme.userBubbleColor : theme.assistantBubbleColor,
190
+ color: isUser ? theme.userBubbleTextColor : theme.assistantBubbleTextColor
191
+ };
192
+ }
193
+ function inputBarStyle(theme) {
194
+ return {
195
+ display: "flex",
196
+ gap: "8px",
197
+ padding: "12px 16px",
198
+ borderTop: `1px solid ${theme.borderColor}`,
199
+ flexShrink: 0
200
+ };
201
+ }
202
+ function chatInputStyle(theme) {
203
+ return {
204
+ ...inputStyle(theme),
205
+ flex: 1
206
+ };
207
+ }
208
+ function sendButtonStyle(theme, disabled) {
209
+ return {
210
+ ...buttonStyle(theme, disabled),
211
+ padding: "8px 14px",
212
+ flexShrink: 0
213
+ };
214
+ }
215
+ function docSelectorStyle(theme) {
216
+ return {
217
+ padding: "8px 16px",
218
+ borderBottom: `1px solid ${theme.borderColor}`,
219
+ flexShrink: 0
220
+ };
221
+ }
222
+ function docToggleStyle(theme) {
223
+ return {
224
+ display: "flex",
225
+ alignItems: "center",
226
+ gap: "6px",
227
+ fontSize: "13px",
228
+ fontWeight: 500,
229
+ color: theme.mutedColor,
230
+ cursor: "pointer",
231
+ background: "none",
232
+ border: "none",
233
+ padding: 0,
234
+ fontFamily: theme.fontFamily
235
+ };
236
+ }
237
+ function docListStyle() {
238
+ return {
239
+ marginTop: "6px",
240
+ display: "flex",
241
+ flexDirection: "column",
242
+ gap: "4px",
243
+ maxHeight: "120px",
244
+ overflowY: "auto"
245
+ };
246
+ }
247
+ function docItemStyle(theme) {
248
+ return {
249
+ display: "flex",
250
+ alignItems: "center",
251
+ gap: "6px",
252
+ fontSize: "13px",
253
+ color: theme.textColor,
254
+ cursor: "pointer",
255
+ textAlign: "left"
256
+ };
257
+ }
258
+ function emptyStateStyle(theme) {
259
+ return {
260
+ flex: 1,
261
+ display: "flex",
262
+ alignItems: "center",
263
+ justifyContent: "center",
264
+ color: theme.mutedColor,
265
+ fontSize: "13px",
266
+ padding: "24px",
267
+ textAlign: "center"
268
+ };
269
+ }
270
+ function loadingContainerStyle() {
271
+ return {
272
+ flex: 1,
273
+ display: "flex",
274
+ alignItems: "center",
275
+ justifyContent: "center",
276
+ gap: "8px",
277
+ fontSize: "13px"
278
+ };
279
+ }
280
+ function brandingStyle(theme) {
281
+ return {
282
+ display: "flex",
283
+ alignItems: "center",
284
+ justifyContent: "center",
285
+ gap: "4px",
286
+ padding: "6px 16px",
287
+ fontSize: "11px",
288
+ color: theme.mutedColor,
289
+ borderTop: `1px solid ${theme.borderColor}`,
290
+ flexShrink: 0,
291
+ userSelect: "none"
292
+ };
293
+ }
294
+ function brandingLinkStyle(theme) {
295
+ return {
296
+ color: theme.mutedColor,
297
+ fontWeight: 600,
298
+ textDecoration: "none"
299
+ };
300
+ }
301
+ function linkButtonStyle(theme) {
302
+ return {
303
+ background: "none",
304
+ border: "none",
305
+ padding: 0,
306
+ fontSize: "13px",
307
+ color: theme.primaryColor,
308
+ cursor: "pointer",
309
+ fontFamily: theme.fontFamily,
310
+ textDecoration: "underline"
311
+ };
312
+ }
313
+ function secondaryButtonStyle(theme, disabled) {
314
+ return {
315
+ padding: "4px 10px",
316
+ fontSize: "12px",
317
+ fontWeight: 500,
318
+ fontFamily: theme.fontFamily,
319
+ color: disabled ? theme.mutedColor : theme.primaryColor,
320
+ backgroundColor: "transparent",
321
+ border: `1px solid ${disabled ? theme.borderColor : theme.primaryColor}`,
322
+ borderRadius: "6px",
323
+ cursor: disabled ? "not-allowed" : "pointer",
324
+ opacity: disabled ? 0.6 : 1,
325
+ transition: "opacity 150ms",
326
+ whiteSpace: "nowrap"
327
+ };
328
+ }
329
+ function fileInputStyle() {
330
+ return { display: "none" };
331
+ }
332
+ function agentStatusStyle(theme) {
333
+ return {
334
+ fontSize: "12px",
335
+ color: theme.mutedColor,
336
+ fontStyle: "italic",
337
+ padding: "4px 0",
338
+ alignSelf: "flex-start"
339
+ };
340
+ }
18
341
  function useArbi() {
19
342
  const arbi = useContext(ArbiContext);
20
343
  if (!arbi) {
@@ -22,6 +345,244 @@ function useArbi() {
22
345
  }
23
346
  return arbi;
24
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
+ }
25
586
  var arbiQueryKeys = {
26
587
  workspaces: () => ["arbi", "workspaces"],
27
588
  documents: (workspaceId) => ["arbi", "documents", workspaceId],
@@ -75,7 +636,496 @@ function useHealth(options) {
75
636
  enabled: options?.enabled
76
637
  });
77
638
  }
639
+ function WorkspaceScreen({ theme, labels, onSelect }) {
640
+ const arbi = useArbi();
641
+ const { data: workspaces, isLoading } = useWorkspaces();
642
+ const [mode, setMode] = useState("select");
643
+ const [selected, setSelected] = useState("");
644
+ const [newName, setNewName] = useState("");
645
+ const [loading, setLoading] = useState(false);
646
+ const [error, setError] = useState("");
647
+ if (isLoading) {
648
+ return /* @__PURE__ */ jsx("div", { style: loadingContainerStyle(), children: "Loading workspaces..." });
649
+ }
650
+ const handleConfirm = async () => {
651
+ if (!selected) return;
652
+ setLoading(true);
653
+ setError("");
654
+ try {
655
+ await arbi.selectWorkspace(selected);
656
+ onSelect(selected);
657
+ } catch (err) {
658
+ setError(err instanceof Error ? err.message : "Failed to select workspace");
659
+ } finally {
660
+ setLoading(false);
661
+ }
662
+ };
663
+ const handleCreate = async () => {
664
+ const name = newName.trim();
665
+ if (!name) return;
666
+ setLoading(true);
667
+ setError("");
668
+ try {
669
+ const ws = await arbi.workspaces.create(name);
670
+ await arbi.selectWorkspace(ws.external_id);
671
+ onSelect(ws.external_id);
672
+ } catch (err) {
673
+ setError(err instanceof Error ? err.message : "Failed to create workspace");
674
+ } finally {
675
+ setLoading(false);
676
+ }
677
+ };
678
+ if (mode === "create") {
679
+ return /* @__PURE__ */ jsxs("div", { style: formContainerStyle(), children: [
680
+ /* @__PURE__ */ jsxs("div", { children: [
681
+ /* @__PURE__ */ jsx("div", { style: labelStyle(theme), children: labels.workspaceName }),
682
+ /* @__PURE__ */ jsx(
683
+ "input",
684
+ {
685
+ type: "text",
686
+ value: newName,
687
+ onChange: (e) => setNewName(e.target.value),
688
+ disabled: loading,
689
+ style: inputStyle(theme)
690
+ }
691
+ )
692
+ ] }),
693
+ /* @__PURE__ */ jsx(
694
+ "button",
695
+ {
696
+ type: "button",
697
+ onClick: handleCreate,
698
+ disabled: !newName.trim() || loading,
699
+ style: buttonStyle(theme, !newName.trim() || loading),
700
+ children: loading ? "..." : labels.createButton
701
+ }
702
+ ),
703
+ error && /* @__PURE__ */ jsx("div", { style: errorStyle(), children: error }),
704
+ /* @__PURE__ */ jsx("div", { style: { textAlign: "center" }, children: /* @__PURE__ */ jsx(
705
+ "button",
706
+ {
707
+ type: "button",
708
+ onClick: () => {
709
+ setError("");
710
+ setMode("select");
711
+ },
712
+ style: linkButtonStyle(theme),
713
+ children: "or select existing"
714
+ }
715
+ ) })
716
+ ] });
717
+ }
718
+ return /* @__PURE__ */ jsxs("div", { style: formContainerStyle(), children: [
719
+ /* @__PURE__ */ jsxs("div", { children: [
720
+ /* @__PURE__ */ jsx("div", { style: labelStyle(theme), children: labels.selectWorkspace }),
721
+ /* @__PURE__ */ jsxs(
722
+ "select",
723
+ {
724
+ value: selected,
725
+ onChange: (e) => setSelected(e.target.value),
726
+ disabled: loading,
727
+ style: selectStyle(theme),
728
+ children: [
729
+ /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: labels.workspacePlaceholder }),
730
+ workspaces?.map((ws) => /* @__PURE__ */ jsx("option", { value: ws.external_id, children: ws.name }, ws.external_id))
731
+ ]
732
+ }
733
+ )
734
+ ] }),
735
+ /* @__PURE__ */ jsx(
736
+ "button",
737
+ {
738
+ type: "button",
739
+ onClick: handleConfirm,
740
+ disabled: !selected || loading,
741
+ style: buttonStyle(theme, !selected || loading),
742
+ children: loading ? "Connecting..." : labels.continueButton
743
+ }
744
+ ),
745
+ error && /* @__PURE__ */ jsx("div", { style: errorStyle(), children: error }),
746
+ /* @__PURE__ */ jsx("div", { style: { textAlign: "center" }, children: /* @__PURE__ */ jsx(
747
+ "button",
748
+ {
749
+ type: "button",
750
+ onClick: () => {
751
+ setError("");
752
+ setMode("create");
753
+ },
754
+ style: linkButtonStyle(theme),
755
+ children: labels.createWorkspace
756
+ }
757
+ ) })
758
+ ] });
759
+ }
760
+ var CITE_RE = /\[([^\]]+)\]\(#cite-(\d+)\)/g;
761
+ function renderWithCitations(text, theme, onCiteClick) {
762
+ const nodes = [];
763
+ let lastIndex = 0;
764
+ let match;
765
+ while ((match = CITE_RE.exec(text)) !== null) {
766
+ if (match.index > lastIndex) {
767
+ nodes.push(text.slice(lastIndex, match.index));
768
+ }
769
+ const statement = match[1];
770
+ const citeNum = match[2];
771
+ nodes.push(
772
+ /* @__PURE__ */ jsxs("span", { children: [
773
+ statement,
774
+ /* @__PURE__ */ jsx(
775
+ "sup",
776
+ {
777
+ onClick: (e) => {
778
+ e.stopPropagation();
779
+ onCiteClick(citeNum);
780
+ },
781
+ style: citeSupStyle(theme),
782
+ title: `Citation ${citeNum}`,
783
+ children: citeNum
784
+ }
785
+ )
786
+ ] }, `cite-${match.index}`)
787
+ );
788
+ lastIndex = match.index + match[0].length;
789
+ }
790
+ if (lastIndex < text.length) {
791
+ nodes.push(text.slice(lastIndex));
792
+ }
793
+ return nodes.length > 0 ? nodes : [text];
794
+ }
795
+ function MessageBubble({ message, theme }) {
796
+ const [expandedCite, setExpandedCite] = useState(null);
797
+ const hasCitations = message.role === "assistant" && message.metadata && CITE_RE.test(message.text);
798
+ CITE_RE.lastIndex = 0;
799
+ const resolved = hasCitations ? resolveCitations(message.metadata) : [];
800
+ const summaries = hasCitations ? summarizeCitations(resolved) : [];
801
+ const summaryMap = new Map(summaries.map((s) => [s.citationNum, s]));
802
+ const handleCiteClick = (num) => {
803
+ setExpandedCite((prev) => prev === num ? null : num);
804
+ };
805
+ const activeSummary = expandedCite ? summaryMap.get(expandedCite) : null;
806
+ const activeResolved = expandedCite ? resolved.find((r) => r.citationNum === expandedCite) : null;
807
+ return /* @__PURE__ */ jsxs("div", { children: [
808
+ /* @__PURE__ */ jsx("div", { style: messageBubbleStyle(theme, message.role), children: hasCitations ? renderWithCitations(message.text, theme, handleCiteClick) : message.text }),
809
+ activeSummary && activeResolved && /* @__PURE__ */ jsxs("div", { style: citePanelStyle(theme), children: [
810
+ /* @__PURE__ */ jsxs("div", { style: citePanelHeaderStyle(theme), children: [
811
+ /* @__PURE__ */ jsxs("strong", { children: [
812
+ "[",
813
+ activeSummary.citationNum,
814
+ "]"
815
+ ] }),
816
+ " ",
817
+ activeSummary.docTitle,
818
+ activeSummary.pageNumber != null && /* @__PURE__ */ jsxs("span", { style: { color: theme.mutedColor }, children: [
819
+ " p.",
820
+ activeSummary.pageNumber
821
+ ] })
822
+ ] }),
823
+ activeResolved.chunks.map((chunk, i) => /* @__PURE__ */ jsx("div", { style: citeChunkStyle(theme), children: chunk.content }, i))
824
+ ] })
825
+ ] });
826
+ }
827
+ function citeSupStyle(theme) {
828
+ return {
829
+ display: "inline-flex",
830
+ alignItems: "center",
831
+ justifyContent: "center",
832
+ fontSize: "10px",
833
+ fontWeight: 600,
834
+ color: theme.primaryColor,
835
+ cursor: "pointer",
836
+ marginLeft: "1px",
837
+ minWidth: "14px",
838
+ lineHeight: 1
839
+ };
840
+ }
841
+ function citePanelStyle(theme) {
842
+ return {
843
+ marginTop: "4px",
844
+ padding: "8px 10px",
845
+ fontSize: "12px",
846
+ lineHeight: "1.5",
847
+ backgroundColor: theme.surfaceColor,
848
+ border: `1px solid ${theme.borderColor}`,
849
+ borderRadius: "8px",
850
+ maxWidth: "85%"
851
+ };
852
+ }
853
+ function citePanelHeaderStyle(theme) {
854
+ return {
855
+ fontSize: "12px",
856
+ fontWeight: 500,
857
+ color: theme.textColor,
858
+ marginBottom: "4px"
859
+ };
860
+ }
861
+ function citeChunkStyle(theme) {
862
+ return {
863
+ fontSize: "11px",
864
+ color: theme.mutedColor,
865
+ borderLeft: `2px solid ${theme.borderColor}`,
866
+ paddingLeft: "8px",
867
+ marginTop: "4px",
868
+ whiteSpace: "pre-wrap",
869
+ wordBreak: "break-word"
870
+ };
871
+ }
872
+ function ChatView({ workspaceId, theme, labels }) {
873
+ const arbi = useArbi();
874
+ const { data: docs, refetch: refetchDocs } = useDocuments(workspaceId);
875
+ const mutation = useAssistantQuery();
876
+ const [messages, setMessages] = useState([]);
877
+ const [input, setInput] = useState("");
878
+ const [streaming, setStreaming] = useState("");
879
+ const [agentStatus, setAgentStatus] = useState("");
880
+ const [selectedDocs, setSelectedDocs] = useState([]);
881
+ const [docsExpanded, setDocsExpanded] = useState(false);
882
+ const [previousResponseId, setPreviousResponseId] = useState(null);
883
+ const [uploading, setUploading] = useState(false);
884
+ const messagesEndRef = useRef(null);
885
+ const fileInputRef = useRef(null);
886
+ useEffect(() => {
887
+ if (docs && docs.length > 0 && selectedDocs.length === 0) {
888
+ setSelectedDocs(docs.map((d) => d.external_id));
889
+ }
890
+ }, [docs, selectedDocs.length]);
891
+ useEffect(() => {
892
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
893
+ }, [messages, streaming, agentStatus]);
894
+ const toggleDoc = (docId) => {
895
+ setSelectedDocs(
896
+ (prev) => prev.includes(docId) ? prev.filter((id) => id !== docId) : [...prev, docId]
897
+ );
898
+ };
899
+ const handleUpload = useCallback(
900
+ async (e) => {
901
+ const files = e.target.files;
902
+ if (!files || files.length === 0) return;
903
+ setUploading(true);
904
+ try {
905
+ const newIds = [];
906
+ for (const file of Array.from(files)) {
907
+ const result = await arbi.documents.uploadFile(file, file.name);
908
+ newIds.push(...result.doc_ext_ids);
909
+ }
910
+ await refetchDocs();
911
+ setSelectedDocs((prev) => [...prev, ...newIds]);
912
+ } catch {
913
+ } finally {
914
+ setUploading(false);
915
+ if (fileInputRef.current) fileInputRef.current.value = "";
916
+ }
917
+ },
918
+ [arbi, refetchDocs]
919
+ );
920
+ const handleSubmit = useCallback(
921
+ (e) => {
922
+ e.preventDefault();
923
+ const question = input.trim();
924
+ if (!question || selectedDocs.length === 0 || mutation.isPending) return;
925
+ setMessages((prev) => [...prev, { role: "user", text: question }]);
926
+ setInput("");
927
+ setStreaming("");
928
+ setAgentStatus("");
929
+ mutation.mutate(
930
+ {
931
+ question,
932
+ docIds: selectedDocs,
933
+ previousResponseId,
934
+ onToken: (t) => setStreaming((prev) => prev + t),
935
+ onAgentStep: (step) => {
936
+ if (step.focus) setAgentStatus(step.focus);
937
+ }
938
+ },
939
+ {
940
+ onSuccess: (result) => {
941
+ setStreaming("");
942
+ setAgentStatus("");
943
+ setMessages((prev) => [
944
+ ...prev,
945
+ { role: "assistant", text: result.text, metadata: result.metadata }
946
+ ]);
947
+ setPreviousResponseId(result.assistantMessageExtId);
948
+ },
949
+ onError: (err) => {
950
+ setStreaming("");
951
+ setAgentStatus("");
952
+ setMessages((prev) => [...prev, { role: "assistant", text: `Error: ${err.message}` }]);
953
+ }
954
+ }
955
+ );
956
+ },
957
+ [input, selectedDocs, previousResponseId, mutation]
958
+ );
959
+ const noDocs = !docs || docs.length === 0;
960
+ const noDocsSelected = selectedDocs.length === 0;
961
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
962
+ /* @__PURE__ */ jsx(
963
+ "input",
964
+ {
965
+ ref: fileInputRef,
966
+ type: "file",
967
+ multiple: true,
968
+ accept: ".pdf,.txt,.md,.html,.doc,.docx,.rtf,.ppt,.pptx,.xls,.xlsx",
969
+ onChange: handleUpload,
970
+ style: fileInputStyle()
971
+ }
972
+ ),
973
+ /* @__PURE__ */ jsxs("div", { style: docSelectorStyle(theme), children: [
974
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
975
+ /* @__PURE__ */ jsxs(
976
+ "button",
977
+ {
978
+ type: "button",
979
+ onClick: () => setDocsExpanded((prev) => !prev),
980
+ style: docToggleStyle(theme),
981
+ children: [
982
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "10px" }, children: docsExpanded ? "\u25BC" : "\u25B6" }),
983
+ labels.documents,
984
+ " (",
985
+ selectedDocs.length,
986
+ "/",
987
+ docs?.length ?? 0,
988
+ " in chat)"
989
+ ]
990
+ }
991
+ ),
992
+ /* @__PURE__ */ jsx(
993
+ "button",
994
+ {
995
+ type: "button",
996
+ onClick: () => fileInputRef.current?.click(),
997
+ disabled: uploading,
998
+ style: secondaryButtonStyle(theme, uploading),
999
+ children: uploading ? labels.uploading : labels.uploadButton
1000
+ }
1001
+ )
1002
+ ] }),
1003
+ docsExpanded && docs && docs.length > 0 && /* @__PURE__ */ jsx("div", { style: docListStyle(), children: docs.map((doc) => /* @__PURE__ */ jsxs("label", { style: docItemStyle(theme), children: [
1004
+ /* @__PURE__ */ jsx(
1005
+ "input",
1006
+ {
1007
+ type: "checkbox",
1008
+ checked: selectedDocs.includes(doc.external_id),
1009
+ onChange: () => toggleDoc(doc.external_id)
1010
+ }
1011
+ ),
1012
+ doc.file_name
1013
+ ] }, doc.external_id)) })
1014
+ ] }),
1015
+ /* @__PURE__ */ jsxs("div", { style: messagesContainerStyle(), children: [
1016
+ messages.length === 0 && !streaming && /* @__PURE__ */ jsx("div", { style: emptyStateStyle(theme), children: noDocs ? labels.emptyNoDocs : noDocsSelected ? labels.emptyNoSelection : labels.emptyReady }),
1017
+ messages.map((msg, i) => /* @__PURE__ */ jsx(MessageBubble, { message: msg, theme }, i)),
1018
+ agentStatus && !streaming && /* @__PURE__ */ jsxs("div", { style: agentStatusStyle(theme), children: [
1019
+ agentStatus,
1020
+ "..."
1021
+ ] }),
1022
+ streaming && /* @__PURE__ */ jsx(MessageBubble, { message: { role: "assistant", text: streaming }, theme }),
1023
+ /* @__PURE__ */ jsx("div", { ref: messagesEndRef })
1024
+ ] }),
1025
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, style: inputBarStyle(theme), children: [
1026
+ /* @__PURE__ */ jsx(
1027
+ "input",
1028
+ {
1029
+ type: "text",
1030
+ value: input,
1031
+ onChange: (e) => setInput(e.target.value),
1032
+ placeholder: noDocs || noDocsSelected ? "Select documents first..." : labels.placeholder,
1033
+ disabled: noDocs || noDocsSelected || mutation.isPending,
1034
+ style: chatInputStyle(theme)
1035
+ }
1036
+ ),
1037
+ /* @__PURE__ */ jsx(
1038
+ "button",
1039
+ {
1040
+ type: "submit",
1041
+ disabled: !input.trim() || noDocsSelected || mutation.isPending,
1042
+ style: sendButtonStyle(theme, !input.trim() || noDocsSelected || mutation.isPending),
1043
+ children: mutation.isPending ? "..." : labels.sendButton
1044
+ }
1045
+ )
1046
+ ] })
1047
+ ] });
1048
+ }
1049
+ function ChatWidget({ theme, labels, initialWorkspaceId, width, height }) {
1050
+ const arbi = useArbi();
1051
+ const [screen, setScreen] = useState("auth");
1052
+ const [workspaceId, setWorkspaceId] = useState(initialWorkspaceId);
1053
+ const handleLogin = useCallback(async () => {
1054
+ if (initialWorkspaceId) {
1055
+ try {
1056
+ await arbi.selectWorkspace(initialWorkspaceId);
1057
+ setScreen("chat");
1058
+ } catch {
1059
+ setScreen("workspace");
1060
+ }
1061
+ } else {
1062
+ setScreen("workspace");
1063
+ }
1064
+ }, [arbi, initialWorkspaceId]);
1065
+ const handleWorkspaceSelect = useCallback((id) => {
1066
+ setWorkspaceId(id);
1067
+ setScreen("chat");
1068
+ }, []);
1069
+ const handleChangeWorkspace = useCallback(() => {
1070
+ setScreen("workspace");
1071
+ }, []);
1072
+ return /* @__PURE__ */ jsxs("div", { style: containerStyle(theme, width, height), children: [
1073
+ /* @__PURE__ */ jsxs("div", { style: { ...headerStyle(theme), justifyContent: "space-between" }, children: [
1074
+ /* @__PURE__ */ jsx("span", { children: labels.title }),
1075
+ screen === "chat" && !initialWorkspaceId && /* @__PURE__ */ jsx("button", { type: "button", onClick: handleChangeWorkspace, style: linkButtonStyle(theme), children: labels.changeWorkspace })
1076
+ ] }),
1077
+ /* @__PURE__ */ jsxs("div", { style: bodyStyle(), children: [
1078
+ screen === "auth" && /* @__PURE__ */ jsx(AuthScreen, { theme, labels, onLogin: handleLogin }),
1079
+ screen === "workspace" && /* @__PURE__ */ jsx(WorkspaceScreen, { theme, labels, onSelect: handleWorkspaceSelect }),
1080
+ screen === "chat" && workspaceId && /* @__PURE__ */ jsx(ChatView, { workspaceId, theme, labels })
1081
+ ] }),
1082
+ /* @__PURE__ */ jsxs("div", { style: brandingStyle(theme), children: [
1083
+ "Powered by",
1084
+ " ",
1085
+ /* @__PURE__ */ jsx(
1086
+ "a",
1087
+ {
1088
+ href: "https://www.arbidocs.com",
1089
+ target: "_blank",
1090
+ rel: "noopener noreferrer",
1091
+ style: brandingLinkStyle(theme),
1092
+ children: "ARBI"
1093
+ }
1094
+ )
1095
+ ] })
1096
+ ] });
1097
+ }
1098
+ function ArbiChat({
1099
+ url,
1100
+ workspaceId,
1101
+ theme: themeOverrides,
1102
+ labels: labelOverrides,
1103
+ width = "400px",
1104
+ height = "600px"
1105
+ }) {
1106
+ const queryClient = useMemo(
1107
+ () => new QueryClient({
1108
+ defaultOptions: {
1109
+ queries: { retry: 1, refetchOnWindowFocus: false },
1110
+ mutations: { retry: false }
1111
+ }
1112
+ }),
1113
+ []
1114
+ );
1115
+ const theme = useMemo(() => getTheme(themeOverrides), [themeOverrides]);
1116
+ const labels = useMemo(() => getLabels(labelOverrides), [labelOverrides]);
1117
+ return /* @__PURE__ */ jsx(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx(ArbiProvider, { url, children: /* @__PURE__ */ jsx(
1118
+ ChatWidget,
1119
+ {
1120
+ theme,
1121
+ labels,
1122
+ initialWorkspaceId: workspaceId,
1123
+ width,
1124
+ height
1125
+ }
1126
+ ) }) });
1127
+ }
78
1128
 
79
- export { ArbiProvider, arbiQueryKeys, useArbi, useAssistantQuery, useConversations, useDocuments, useHealth, useTags, useWorkspaces };
1129
+ export { ArbiChat, ArbiProvider, arbiQueryKeys, useArbi, useAssistantQuery, useConversations, useDocuments, useHealth, useTags, useWorkspaces };
80
1130
  //# sourceMappingURL=index.js.map
81
1131
  //# sourceMappingURL=index.js.map