@arbidocs/react 0.4.11 → 0.4.12
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/{chunk-TA3IZP7I.cjs → chunk-K5TOB34Q.cjs} +3 -3
- package/dist/{chunk-TA3IZP7I.cjs.map → chunk-K5TOB34Q.cjs.map} +1 -1
- package/dist/{chunk-RTULVZOS.js → chunk-NVMQLJI4.js} +3 -3
- package/dist/{chunk-RTULVZOS.js.map → chunk-NVMQLJI4.js.map} +1 -1
- package/dist/index.cjs +385 -300
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +108 -23
- package/dist/index.js.map +1 -1
- package/dist/ui/index.cjs +147 -147
- package/dist/ui/index.js +2 -2
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -82,6 +82,8 @@ interface ArbiChatMessage {
|
|
|
82
82
|
citations?: ArbiChatCitation[];
|
|
83
83
|
/** The reader stopped the answer; `content` is what had streamed. */
|
|
84
84
|
stopped?: boolean;
|
|
85
|
+
/** The backend id of a finished assistant answer: what a follow-up chains from. */
|
|
86
|
+
responseId?: string;
|
|
85
87
|
}
|
|
86
88
|
interface ArbiChatThread {
|
|
87
89
|
id: string;
|
|
@@ -100,6 +102,15 @@ interface UseArbiChatOptions {
|
|
|
100
102
|
model?: string;
|
|
101
103
|
/** Longest auto-title in characters (used until the backend has titled the conversation). Default 48. */
|
|
102
104
|
titleLength?: number;
|
|
105
|
+
/**
|
|
106
|
+
* When the answer stream ends without the terminal `response.completed` event (a dropped
|
|
107
|
+
* connection, a stalled run), the stored message is polled this often (ms) and for at most
|
|
108
|
+
* `timeoutMs` before the turn is shown as failed with a Retry. Default 3000 / 120000.
|
|
109
|
+
*/
|
|
110
|
+
streamRecovery?: {
|
|
111
|
+
intervalMs?: number;
|
|
112
|
+
timeoutMs?: number;
|
|
113
|
+
};
|
|
103
114
|
}
|
|
104
115
|
interface ArbiChat {
|
|
105
116
|
threads: ArbiChatThread[];
|
package/dist/index.d.ts
CHANGED
|
@@ -82,6 +82,8 @@ interface ArbiChatMessage {
|
|
|
82
82
|
citations?: ArbiChatCitation[];
|
|
83
83
|
/** The reader stopped the answer; `content` is what had streamed. */
|
|
84
84
|
stopped?: boolean;
|
|
85
|
+
/** The backend id of a finished assistant answer: what a follow-up chains from. */
|
|
86
|
+
responseId?: string;
|
|
85
87
|
}
|
|
86
88
|
interface ArbiChatThread {
|
|
87
89
|
id: string;
|
|
@@ -100,6 +102,15 @@ interface UseArbiChatOptions {
|
|
|
100
102
|
model?: string;
|
|
101
103
|
/** Longest auto-title in characters (used until the backend has titled the conversation). Default 48. */
|
|
102
104
|
titleLength?: number;
|
|
105
|
+
/**
|
|
106
|
+
* When the answer stream ends without the terminal `response.completed` event (a dropped
|
|
107
|
+
* connection, a stalled run), the stored message is polled this often (ms) and for at most
|
|
108
|
+
* `timeoutMs` before the turn is shown as failed with a Retry. Default 3000 / 120000.
|
|
109
|
+
*/
|
|
110
|
+
streamRecovery?: {
|
|
111
|
+
intervalMs?: number;
|
|
112
|
+
timeoutMs?: number;
|
|
113
|
+
};
|
|
103
114
|
}
|
|
104
115
|
interface ArbiChat {
|
|
105
116
|
threads: ArbiChatThread[];
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { useDocuments, useConversations, useAssistantQuery, useConversationThreads, arbiQueryKeys, useWorkspaces, useParsedDocument, useTags, useActivity, useThumbnails } from './chunk-QFELRTTG.js';
|
|
2
|
-
export { DEFAULT_USD_PER_CREDIT, arbiQueryKeys, cleanDraftMarkdown, creditsToUsd, formatCredits, isUsageMetered, promoteBoldTitle, repairParsedTitle, stripAgentPreamble, unescapeMarkdown, unwrapCitationLinks, useActivity, useAgents, useAssistantQuery, useConfigs, useConversationThreads, useConversations, useDeleteAgents, useDocuments, useFacts, useHealth, useParsedDocument, useProjectInvoices, useProjectUsage, useProjects, useProjectsUsage, useSessions, useTags, useTasks, useThumbnails, useUserUsageToday, useWorkspaces } from './chunk-QFELRTTG.js';
|
|
3
1
|
import { formatRelativeTime } from './chunk-FSMIN5VX.js';
|
|
4
2
|
export { DEFAULT_NOTIFY_TONE, NotificationBell, Toaster, formatRelativeTime, notify, seedNotifications, selectUnread, toast, useNotify } from './chunk-FSMIN5VX.js';
|
|
5
|
-
import {
|
|
6
|
-
export {
|
|
7
|
-
import {
|
|
8
|
-
export {
|
|
3
|
+
import { useDocuments, useConversations, useAssistantQuery, useConversationThreads, arbiQueryKeys, useWorkspaces, useParsedDocument, useTags, useActivity, useThumbnails } from './chunk-QFELRTTG.js';
|
|
4
|
+
export { DEFAULT_USD_PER_CREDIT, arbiQueryKeys, cleanDraftMarkdown, creditsToUsd, formatCredits, isUsageMetered, promoteBoldTitle, repairParsedTitle, stripAgentPreamble, unescapeMarkdown, unwrapCitationLinks, useActivity, useAgents, useAssistantQuery, useConfigs, useConversationThreads, useConversations, useDeleteAgents, useDocuments, useFacts, useHealth, useParsedDocument, useProjectInvoices, useProjectUsage, useProjects, useProjectsUsage, useSessions, useTags, useTasks, useThumbnails, useUserUsageToday, useWorkspaces } from './chunk-QFELRTTG.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-NVMQLJI4.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-NVMQLJI4.js';
|
|
9
7
|
import { ScrollArea, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from './chunk-N5SVBDLC.js';
|
|
10
8
|
export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, ScrollArea, ScrollBar } from './chunk-N5SVBDLC.js';
|
|
9
|
+
import { normalizeCitationMarkup, MarkdownRenderer } from './chunk-TGGQGFS3.js';
|
|
10
|
+
export { MarkdownRenderer, MarkdownStreamingContext, closeOpenEmphasis, markdownBaseComponents, normalizeCitationMarkup, splitMarkdownBlocks, trimOpenCitation } from './chunk-TGGQGFS3.js';
|
|
11
11
|
import { cn } from './chunk-SN5YV6FF.js';
|
|
12
12
|
export { cn } from './chunk-SN5YV6FF.js';
|
|
13
13
|
import { AudioViewer, ImageViewer, VideoViewer } from './chunk-HDCRPPBW.js';
|
|
@@ -75,22 +75,38 @@ var storedThread = (conversationId) => ({
|
|
|
75
75
|
isLoaded: false
|
|
76
76
|
});
|
|
77
77
|
var publicId = (t) => t.conversationId ?? t.key;
|
|
78
|
+
var UNFINISHED_STATUSES = /* @__PURE__ */ new Set(["failed", "in_progress", "queued"]);
|
|
79
|
+
var FAILED_MESSAGE = "The assistant request failed.";
|
|
80
|
+
var UNFINISHED_MESSAGE = "The answer did not finish. Try again.";
|
|
81
|
+
var isUnfinished = (m) => UNFINISHED_STATUSES.has(m.status);
|
|
78
82
|
function messagesFromHistory(history) {
|
|
79
|
-
return history.filter(
|
|
83
|
+
return history.filter(
|
|
84
|
+
(m) => m.role !== "system" && (m.content.trim() !== "" || m.role === "assistant" && isUnfinished(m))
|
|
85
|
+
).map((m) => {
|
|
80
86
|
if (m.role === "user") {
|
|
81
87
|
return { id: m.external_id, role: "user", content: m.content, status: "complete" };
|
|
82
88
|
}
|
|
83
89
|
const citations = toChatCitations({ tools: m.tools });
|
|
90
|
+
const unfinished = isUnfinished(m);
|
|
84
91
|
return {
|
|
85
92
|
id: m.external_id,
|
|
86
93
|
role: "assistant",
|
|
87
94
|
content: m.content,
|
|
88
|
-
status:
|
|
89
|
-
...m.status === "failed" ?
|
|
95
|
+
status: unfinished ? "error" : "complete",
|
|
96
|
+
...unfinished ? { error: m.status === "failed" ? FAILED_MESSAGE : UNFINISHED_MESSAGE } : {},
|
|
97
|
+
...unfinished ? {} : { responseId: m.external_id },
|
|
90
98
|
...citations.length > 0 ? { citations } : {}
|
|
91
99
|
};
|
|
92
100
|
});
|
|
93
101
|
}
|
|
102
|
+
function chainId(messages) {
|
|
103
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
104
|
+
const m = messages[i];
|
|
105
|
+
if (m.role === "assistant" && m.status === "complete" && m.responseId) return m.responseId;
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
94
110
|
function titleFromQuestion(question, maxLength = 48) {
|
|
95
111
|
const flat = question.replace(/\s+/g, " ").trim();
|
|
96
112
|
if (flat.length <= maxLength) return flat;
|
|
@@ -103,7 +119,9 @@ var messageOf = (e, fallback) => e instanceof Error ? e.message : fallback;
|
|
|
103
119
|
var isAbort = (e) => e instanceof DOMException && e.name === "AbortError";
|
|
104
120
|
var TITLE_REFRESH_DELAYS_MS = [2500, 6e3, 12e3];
|
|
105
121
|
function useArbiChat(options = {}) {
|
|
106
|
-
const { workspaceId, model, titleLength = 48 } = options;
|
|
122
|
+
const { workspaceId, model, titleLength = 48, streamRecovery } = options;
|
|
123
|
+
const recoveryIntervalMs = streamRecovery?.intervalMs ?? 3e3;
|
|
124
|
+
const recoveryTimeoutMs = streamRecovery?.timeoutMs ?? 12e4;
|
|
107
125
|
const arbi = useArbi();
|
|
108
126
|
const { data: docs = [], isLoading: isLoadingDocuments, refetch } = useDocuments(workspaceId);
|
|
109
127
|
const {
|
|
@@ -167,13 +185,14 @@ function useArbiChat(options = {}) {
|
|
|
167
185
|
const data = loaded.data;
|
|
168
186
|
if (!needsLoad || !data || data.conversation_ext_id !== active.conversationId) return;
|
|
169
187
|
const primary = [...data.threads].sort((a, b) => b.history.length - a.history.length)[0];
|
|
188
|
+
const messages = messagesFromHistory(primary?.history ?? []);
|
|
170
189
|
setThreads(
|
|
171
190
|
(all) => all.map(
|
|
172
191
|
(t) => t.key === active.key ? {
|
|
173
192
|
...t,
|
|
174
193
|
isLoaded: true,
|
|
175
|
-
messages
|
|
176
|
-
responseId:
|
|
194
|
+
messages,
|
|
195
|
+
responseId: chainId(messages)
|
|
177
196
|
} : t
|
|
178
197
|
)
|
|
179
198
|
);
|
|
@@ -212,6 +231,29 @@ function useArbiChat(options = {}) {
|
|
|
212
231
|
(delay) => setTimeout(() => void refetchConversations(), delay)
|
|
213
232
|
);
|
|
214
233
|
}, [refetchConversations]);
|
|
234
|
+
const recoverStoredAnswer = useCallback(
|
|
235
|
+
async (conversationId, messageId, signal) => {
|
|
236
|
+
if (!conversationId || !messageId) return null;
|
|
237
|
+
const deadline = Date.now() + recoveryTimeoutMs;
|
|
238
|
+
let failedReads = 0;
|
|
239
|
+
for (; ; ) {
|
|
240
|
+
try {
|
|
241
|
+
const { threads: all } = await arbi.conversations.getThreads(conversationId);
|
|
242
|
+
const found = all.flatMap((t) => t.history).find((m) => m.external_id === messageId);
|
|
243
|
+
if (found?.status === "completed" && found.content.trim() !== "") {
|
|
244
|
+
return { kind: "completed", message: found };
|
|
245
|
+
}
|
|
246
|
+
if (found?.status === "failed") return { kind: "failed" };
|
|
247
|
+
failedReads = 0;
|
|
248
|
+
} catch {
|
|
249
|
+
if (++failedReads >= 3) return null;
|
|
250
|
+
}
|
|
251
|
+
if (signal.aborted || Date.now() + recoveryIntervalMs > deadline) return null;
|
|
252
|
+
await sleep(recoveryIntervalMs);
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
[arbi, recoveryIntervalMs, recoveryTimeoutMs]
|
|
256
|
+
);
|
|
215
257
|
const ask = useCallback(
|
|
216
258
|
async (thread, text) => {
|
|
217
259
|
const userMessage = {
|
|
@@ -237,6 +279,17 @@ function useArbiChat(options = {}) {
|
|
|
237
279
|
let buffer = "";
|
|
238
280
|
let steps = 0;
|
|
239
281
|
let streamError = null;
|
|
282
|
+
let conversationId = thread.conversationId;
|
|
283
|
+
let backendMessageId = null;
|
|
284
|
+
const revertChain = () => patchThread(thread.key, (t) => ({ ...t, responseId: chainId(t.messages) }));
|
|
285
|
+
const fail = (error2, content) => {
|
|
286
|
+
patchMessage(thread.key, answer.id, {
|
|
287
|
+
status: "error",
|
|
288
|
+
error: error2,
|
|
289
|
+
...content !== void 0 ? { content } : {}
|
|
290
|
+
});
|
|
291
|
+
revertChain();
|
|
292
|
+
};
|
|
240
293
|
try {
|
|
241
294
|
const result = await mutateAsync({
|
|
242
295
|
question: text,
|
|
@@ -246,6 +299,7 @@ function useArbiChat(options = {}) {
|
|
|
246
299
|
signal: controller.signal,
|
|
247
300
|
onStreamStart: (data) => {
|
|
248
301
|
if (data.assistant_message_ext_id) {
|
|
302
|
+
backendMessageId = data.assistant_message_ext_id;
|
|
249
303
|
patchThread(thread.key, (t) => ({
|
|
250
304
|
...t,
|
|
251
305
|
responseId: data.assistant_message_ext_id ?? t.responseId
|
|
@@ -253,6 +307,7 @@ function useArbiChat(options = {}) {
|
|
|
253
307
|
}
|
|
254
308
|
},
|
|
255
309
|
onUserMessage: (ev) => {
|
|
310
|
+
conversationId = conversationId ?? ev.conversation_ext_id;
|
|
256
311
|
patchThread(thread.key, (t) => ({
|
|
257
312
|
...t,
|
|
258
313
|
conversationId: t.conversationId ?? ev.conversation_ext_id
|
|
@@ -279,20 +334,43 @@ function useArbiChat(options = {}) {
|
|
|
279
334
|
});
|
|
280
335
|
const finalText = result.text || buffer;
|
|
281
336
|
const failure = streamError || result.errors[0] || null;
|
|
337
|
+
const messageId = result.assistantMessageExtId ?? backendMessageId;
|
|
282
338
|
if (failure && !finalText) {
|
|
283
|
-
|
|
339
|
+
fail(failure);
|
|
340
|
+
} else if (!result.completed && !failure) {
|
|
341
|
+
const stored2 = await recoverStoredAnswer(conversationId, messageId, controller.signal);
|
|
342
|
+
if (stored2?.kind === "completed") {
|
|
343
|
+
const citations = toChatCitations({ tools: stored2.message.tools });
|
|
344
|
+
patchMessage(thread.key, answer.id, {
|
|
345
|
+
status: "complete",
|
|
346
|
+
content: stored2.message.content,
|
|
347
|
+
stepCount: result.agentSteps.length || steps || void 0,
|
|
348
|
+
responseId: stored2.message.external_id,
|
|
349
|
+
...citations.length > 0 ? { citations } : {}
|
|
350
|
+
});
|
|
351
|
+
patchThread(thread.key, (t) => ({ ...t, responseId: stored2.message.external_id }));
|
|
352
|
+
} else if (controller.signal.aborted) {
|
|
353
|
+
patchMessage(thread.key, answer.id, {
|
|
354
|
+
status: "complete",
|
|
355
|
+
content: finalText,
|
|
356
|
+
stopped: true
|
|
357
|
+
});
|
|
358
|
+
} else {
|
|
359
|
+
fail(
|
|
360
|
+
stored2?.kind === "failed" ? FAILED_MESSAGE : UNFINISHED_MESSAGE,
|
|
361
|
+
finalText || void 0
|
|
362
|
+
);
|
|
363
|
+
}
|
|
284
364
|
} else {
|
|
285
365
|
const citations = toChatCitations(result.metadata);
|
|
286
366
|
patchMessage(thread.key, answer.id, {
|
|
287
367
|
status: "complete",
|
|
288
368
|
content: finalText,
|
|
289
369
|
stepCount: result.agentSteps.length || steps || void 0,
|
|
370
|
+
...messageId ? { responseId: messageId } : {},
|
|
290
371
|
...citations.length > 0 ? { citations } : {}
|
|
291
372
|
});
|
|
292
|
-
patchThread(thread.key, (t) => ({
|
|
293
|
-
...t,
|
|
294
|
-
responseId: result.assistantMessageExtId ?? t.responseId
|
|
295
|
-
}));
|
|
373
|
+
patchThread(thread.key, (t) => ({ ...t, responseId: messageId ?? t.responseId }));
|
|
296
374
|
}
|
|
297
375
|
} catch (e) {
|
|
298
376
|
if (controller.signal.aborted || isAbort(e)) {
|
|
@@ -302,10 +380,7 @@ function useArbiChat(options = {}) {
|
|
|
302
380
|
stopped: true
|
|
303
381
|
});
|
|
304
382
|
} else {
|
|
305
|
-
|
|
306
|
-
status: "error",
|
|
307
|
-
error: messageOf(e, "The assistant request failed.")
|
|
308
|
-
});
|
|
383
|
+
fail(messageOf(e, FAILED_MESSAGE));
|
|
309
384
|
}
|
|
310
385
|
} finally {
|
|
311
386
|
if (abortRef.current === controller) abortRef.current = null;
|
|
@@ -313,7 +388,16 @@ function useArbiChat(options = {}) {
|
|
|
313
388
|
refreshTitles();
|
|
314
389
|
}
|
|
315
390
|
},
|
|
316
|
-
[
|
|
391
|
+
[
|
|
392
|
+
mutateAsync,
|
|
393
|
+
patchMessage,
|
|
394
|
+
patchThread,
|
|
395
|
+
refreshTitles,
|
|
396
|
+
recoverStoredAnswer,
|
|
397
|
+
selectedIds,
|
|
398
|
+
model,
|
|
399
|
+
titleLength
|
|
400
|
+
]
|
|
317
401
|
);
|
|
318
402
|
const send = useCallback(
|
|
319
403
|
async (text) => {
|
|
@@ -332,7 +416,8 @@ function useArbiChat(options = {}) {
|
|
|
332
416
|
const failed = messages[messages.length - 1];
|
|
333
417
|
const question = messages[messages.length - 2];
|
|
334
418
|
if (failed?.status !== "error" || question?.role !== "user") return;
|
|
335
|
-
const
|
|
419
|
+
const rest = messages.slice(0, -2);
|
|
420
|
+
const trimmed = { ...active, messages: rest, responseId: chainId(rest) };
|
|
336
421
|
patchThread(active.key, () => trimmed);
|
|
337
422
|
await ask(trimmed, question.content);
|
|
338
423
|
}, [active, ask, isRunning, patchThread]);
|