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