@arbidocs/react 0.3.16 → 0.3.18

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