@arbidocs/react 0.4.27 → 0.4.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { useDocuments, useConversations, useAssistantQuery, useConversationThreads, arbiQueryKeys, useWorkspaces, useParsedDocument, useTags, useActivity, useThumbnails } from './chunk-DLT354KA.js';
2
- export { DEFAULT_USD_PER_CREDIT, arbiQueryKeys, cleanDraftMarkdown, cleanDraftWithReport, creditsToUsd, formatCredits, isUsageMetered, promoteBoldTitle, repairParsedStructure, repairParsedTitle, stripAgentPreamble, unescapeMarkdown, unwrapCitationLinks, useActivity, useAgents, useAssistantQuery, useConfigs, useConversationThreads, useConversations, useDeleteAgents, useDocuments, useFacts, useHealth, useParsedDocument, useProjectInvoices, useProjectUsage, useProjects, useProjectsUsage, useSessions, useTags, useThumbnails, useUserUsageToday, useWorkspaces } from './chunk-DLT354KA.js';
3
- import { formatRelativeTime } from './chunk-FSMIN5VX.js';
4
- export { DEFAULT_NOTIFY_TONE, NotificationBell, Toaster, formatRelativeTime, notify, seedNotifications, selectUnread, toast, useNotify } from './chunk-FSMIN5VX.js';
1
+ import { arbiQueryKeys, useDocuments, useConversations, useAssistantQuery, useConversationThreads, useWorkspaces, useParsedDocument, useTags, useActivity, useThumbnails } from './chunk-NDG44JWI.js';
2
+ export { DEFAULT_USD_PER_CREDIT, arbiQueryKeys, cleanDraftMarkdown, cleanDraftWithReport, creditsToUsd, formatCredits, isUsageMetered, promoteBoldTitle, repairParsedStructure, repairParsedTitle, stripAgentPreamble, unescapeMarkdown, unwrapCitationLinks, useActivity, useAgents, useAssistantQuery, useConfigs, useConversationThreads, useConversations, useDeleteAgents, useDocuments, useFacts, useHealth, useParsedDocument, useProjectInvoices, useProjectUsage, useProjects, useProjectsUsage, useSessions, useTags, useThumbnails, useUserUsageToday, useWorkspaceUsers, useWorkspaces } from './chunk-NDG44JWI.js';
3
+ import { formatRelativeTime } from './chunk-OBTCAI4E.js';
4
+ export { DEFAULT_NOTIFY_TONE, NotificationBell, Toaster, formatRelativeTime, notify, seedNotifications, selectUnread, toast, useNotify } from './chunk-OBTCAI4E.js';
5
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-SDUISFTV.js';
6
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, VisibilityToggle, badgeVariants, buttonVariants, cardVariants, configureEntityRegistry, configureRagLabels, configureStatusRegistry, formatElapsed, getDocIcon, getEntityColor, getEntityColorStyles, getEntityIcon, getEntityLabel, getStatusVariant, getTagTypeIcon, getTagTypeLabel, getWorkspaceColor, toggleVariants, useAudioRecorder } from './chunk-SDUISFTV.js';
7
7
  import { normalizeCitationMarkup, MarkdownRenderer } from './chunk-6YSHODMG.js';
@@ -14,6 +14,7 @@ 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 { useQueryClient, useMutation, useQuery } from '@tanstack/react-query';
17
18
  import { resolveCitations, summarizeCitations, connectWithReconnect } from '@arbidocs/sdk/browser';
18
19
  export { Arbi, ArbiError } from '@arbidocs/sdk/browser';
19
20
  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, AtSign, MessageCircleMore, FileX2, FileCheck2, Pencil, FolderPlus, SquareArrowOutUpRight, MessagesSquare, ArrowRight, MoreHorizontal, SquarePen, Mic, Pause, Play, Square, RotateCcw, Camera, Circle } from 'lucide-react';
@@ -21,7 +22,6 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
21
22
  import { isMessageType, passwordChangeErrorMessage } from '@arbidocs/client';
22
23
  export { isMessageType } from '@arbidocs/client';
23
24
  import { create } from 'zustand';
24
- import { useQueryClient, useMutation, useQuery } from '@tanstack/react-query';
25
25
  import { diffWords } from 'diff';
26
26
  import { AnimatePresence, motion } from 'framer-motion';
27
27
  import { createPortal } from 'react-dom';
@@ -30,6 +30,319 @@ 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 useUpdateConversation() {
34
+ const arbi = useArbi();
35
+ const qc = useQueryClient();
36
+ return useMutation({
37
+ mutationFn: ({
38
+ conversationId,
39
+ patch
40
+ }) => arbi.conversations.update(conversationId, patch),
41
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.allConversations() })
42
+ });
43
+ }
44
+ function useCreateWork() {
45
+ const arbi = useArbi();
46
+ const qc = useQueryClient();
47
+ return useMutation({
48
+ mutationFn: (body) => arbi.conversations.createWork(body),
49
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.allConversations() })
50
+ });
51
+ }
52
+ function useRunConversation() {
53
+ const arbi = useArbi();
54
+ const qc = useQueryClient();
55
+ return useMutation({
56
+ mutationFn: (conversationId) => arbi.conversations.runWithAuth(conversationId),
57
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.allConversations() })
58
+ });
59
+ }
60
+ function useReviewConversation() {
61
+ const arbi = useArbi();
62
+ const qc = useQueryClient();
63
+ return useMutation({
64
+ mutationFn: ({
65
+ conversationId,
66
+ review
67
+ }) => arbi.conversations.review(conversationId, review),
68
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.allConversations() })
69
+ });
70
+ }
71
+ function useRequestConversationApproval() {
72
+ const arbi = useArbi();
73
+ const qc = useQueryClient();
74
+ return useMutation({
75
+ mutationFn: ({
76
+ conversationId,
77
+ request
78
+ }) => arbi.conversations.requestApproval(conversationId, request),
79
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.allConversations() })
80
+ });
81
+ }
82
+ function usePlanWork() {
83
+ const arbi = useArbi();
84
+ return useMutation({
85
+ mutationFn: (body) => arbi.conversations.plan(body)
86
+ });
87
+ }
88
+ function useConversationPendingAuth(conversationId, options) {
89
+ const arbi = useArbi();
90
+ return useQuery({
91
+ queryKey: [...arbiQueryKeys.allConversations(), conversationId ?? "", "pending-auth"],
92
+ queryFn: () => arbi.conversations.pendingAuth(conversationId),
93
+ enabled: (options?.enabled ?? true) && !!conversationId && arbi.isLoggedIn,
94
+ retry: false
95
+ });
96
+ }
97
+ function useConversationActivity(conversationId, options) {
98
+ const arbi = useArbi();
99
+ return useQuery({
100
+ queryKey: [...arbiQueryKeys.allConversations(), conversationId ?? "", "activity"],
101
+ queryFn: () => arbi.conversations.activity(conversationId),
102
+ enabled: (options?.enabled ?? true) && !!conversationId && arbi.isLoggedIn,
103
+ refetchInterval: options?.live ? 2500 : false
104
+ });
105
+ }
106
+ function useWorkflows(options) {
107
+ const arbi = useArbi();
108
+ return useQuery({
109
+ queryKey: arbiQueryKeys.workflows(),
110
+ queryFn: () => arbi.workflows.list(),
111
+ refetchInterval: options?.live ? 15e3 : false,
112
+ enabled: options?.enabled ?? arbi.isLoggedIn
113
+ });
114
+ }
115
+ function useCreateWorkflow() {
116
+ const arbi = useArbi();
117
+ const qc = useQueryClient();
118
+ return useMutation({
119
+ mutationFn: (body) => arbi.workflows.create(body),
120
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.workflows() })
121
+ });
122
+ }
123
+ function useUpdateWorkflow() {
124
+ const arbi = useArbi();
125
+ const qc = useQueryClient();
126
+ return useMutation({
127
+ mutationFn: ({
128
+ workflowId,
129
+ patch
130
+ }) => arbi.workflows.update(workflowId, patch),
131
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.workflows() })
132
+ });
133
+ }
134
+ function useDeleteWorkflow() {
135
+ const arbi = useArbi();
136
+ const qc = useQueryClient();
137
+ return useMutation({
138
+ mutationFn: (workflowId) => arbi.workflows.delete(workflowId),
139
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.workflows() })
140
+ });
141
+ }
142
+ function useRunWorkflow() {
143
+ const arbi = useArbi();
144
+ const qc = useQueryClient();
145
+ return useMutation({
146
+ mutationFn: (workflowId) => arbi.workflows.run(workflowId),
147
+ onSuccess: () => Promise.all([
148
+ qc.invalidateQueries({ queryKey: arbiQueryKeys.workflows() }),
149
+ qc.invalidateQueries({ queryKey: arbiQueryKeys.allConversations() })
150
+ ])
151
+ });
152
+ }
153
+ function useCreateAgent() {
154
+ const arbi = useArbi();
155
+ const qc = useQueryClient();
156
+ return useMutation({
157
+ mutationFn: (name) => arbi.agents.create(name),
158
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.agents() })
159
+ });
160
+ }
161
+ function useAgentConfig(agentExtId, options) {
162
+ const arbi = useArbi();
163
+ return useQuery({
164
+ queryKey: arbiQueryKeys.agentConfig(agentExtId),
165
+ queryFn: async () => agentExtId ? await arbi.agents.getConfig(agentExtId) : await arbi.agentConfig.get("default"),
166
+ enabled: (options?.enabled ?? true) && arbi.isLoggedIn
167
+ });
168
+ }
169
+ function useAgentModels(options) {
170
+ const arbi = useArbi();
171
+ return useQuery({
172
+ queryKey: arbiQueryKeys.models(),
173
+ queryFn: async () => (await arbi.agentConfig.getModels()).models.map((m) => m.model_name),
174
+ enabled: (options?.enabled ?? true) && arbi.isLoggedIn
175
+ });
176
+ }
177
+ function useSaveAgentConfig() {
178
+ const arbi = useArbi();
179
+ const qc = useQueryClient();
180
+ return useMutation({
181
+ mutationFn: async ({ agentExtId, title, sections }) => {
182
+ const pinned = await arbi.agents.getConfig(agentExtId);
183
+ const base = pinned ?? await arbi.agentConfig.get("default");
184
+ const merged = structuredClone(base);
185
+ for (const [section, params] of Object.entries(sections)) {
186
+ merged[section] = { ...merged[section] ?? {}, ...params };
187
+ }
188
+ return arbi.agentConfig.save({
189
+ ...merged,
190
+ agent_ext_id: agentExtId,
191
+ title: title ?? `agent-${agentExtId}`
192
+ });
193
+ },
194
+ onSuccess: (_saved, { agentExtId }) => Promise.all([
195
+ qc.invalidateQueries({ queryKey: arbiQueryKeys.configs() }),
196
+ qc.invalidateQueries({ queryKey: arbiQueryKeys.agentConfig(agentExtId) }),
197
+ // The agent list shows the pinned persona/model, so it refreshes too.
198
+ qc.invalidateQueries({ queryKey: arbiQueryKeys.agents() })
199
+ ])
200
+ });
201
+ }
202
+ function useAgentWorkspaces(agentExtId, options) {
203
+ const arbi = useArbi();
204
+ return useQuery({
205
+ queryKey: arbiQueryKeys.agentWorkspaces(agentExtId ?? ""),
206
+ queryFn: () => arbi.agents.listWorkspaces(agentExtId),
207
+ enabled: (options?.enabled ?? true) && !!agentExtId && arbi.isLoggedIn
208
+ });
209
+ }
210
+ function useGrantAgentAccess() {
211
+ const arbi = useArbi();
212
+ const qc = useQueryClient();
213
+ return useMutation({
214
+ mutationFn: async ({
215
+ agentExtId,
216
+ workspaceId,
217
+ mode = { kind: "standing" }
218
+ }) => {
219
+ if (mode.kind === "temporary") {
220
+ return arbi.agents.grantTemporaryAccess(agentExtId, workspaceId, mode.ttlSeconds);
221
+ }
222
+ const agent = (await arbi.agents.list()).find((a) => a.external_id === agentExtId);
223
+ if (!agent?.email) throw new Error("The agent's address is unknown.");
224
+ return arbi.workspaces.grantAccess({ workspaceId, emails: [agent.email] });
225
+ },
226
+ onSuccess: (_res, { agentExtId }) => qc.invalidateQueries({ queryKey: arbiQueryKeys.agentWorkspaces(agentExtId) })
227
+ });
228
+ }
229
+ function useRevokeAgentAccess() {
230
+ const arbi = useArbi();
231
+ const qc = useQueryClient();
232
+ return useMutation({
233
+ mutationFn: async ({
234
+ agentExtId,
235
+ workspaceId
236
+ }) => {
237
+ const previous = arbi.hasWorkspace ? arbi.getWorkspaceId() : null;
238
+ await arbi.selectWorkspace(workspaceId);
239
+ try {
240
+ await arbi.workspaces.removeUsers([agentExtId]);
241
+ } finally {
242
+ if (previous && previous !== workspaceId) await arbi.selectWorkspace(previous);
243
+ }
244
+ },
245
+ onSuccess: (_res, { agentExtId }) => qc.invalidateQueries({ queryKey: arbiQueryKeys.agentWorkspaces(agentExtId) })
246
+ });
247
+ }
248
+ function useSkills(options) {
249
+ const arbi = useArbi();
250
+ return useQuery({
251
+ queryKey: [...arbiQueryKeys.skills(), options?.includeHidden ?? false],
252
+ queryFn: () => arbi.assistant.listSkills({ includeHidden: options?.includeHidden }),
253
+ refetchInterval: options?.live ? 5e3 : false,
254
+ enabled: (options?.enabled ?? true) && arbi.isLoggedIn,
255
+ staleTime: 3e4
256
+ });
257
+ }
258
+ function useUploadSkillFiles() {
259
+ const arbi = useArbi();
260
+ const qc = useQueryClient();
261
+ return useMutation({
262
+ mutationFn: async (entries) => {
263
+ if (!entries.length) throw new Error("Choose at least one skill file.");
264
+ for (const entry of entries) {
265
+ if (!/^skills\/[a-z0-9][a-z0-9_-]*(?:\/[^/]+)*$/.test(entry.folder) || entry.folder.split("/").some((segment) => segment === ".." || segment === "."))
266
+ throw new Error("Skill files need a folder under skills/<name>.");
267
+ }
268
+ const results = [];
269
+ for (const entry of entries) {
270
+ const result = await arbi.documents.uploadFile(entry.content, entry.fileName, {
271
+ folder: entry.folder,
272
+ wpType: "skill"
273
+ });
274
+ if (result.skipped?.length)
275
+ throw new Error(
276
+ result.skipped.map((file) => `${file.file_name}: ${file.reason}`).join("; ")
277
+ );
278
+ results.push(result);
279
+ }
280
+ return results;
281
+ },
282
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.skills() })
283
+ });
284
+ }
285
+ function useStartWorkQuery() {
286
+ const arbi = useArbi();
287
+ const qc = useQueryClient();
288
+ return useMutation({
289
+ mutationFn: async ({
290
+ question,
291
+ ...options
292
+ }) => {
293
+ const response = await arbi.assistant.submit(question, options);
294
+ for (let attempt = 0; attempt < 30; attempt++) {
295
+ if (attempt) await new Promise((resolve) => setTimeout(resolve, 500));
296
+ try {
297
+ const message = await arbi.conversations.getMessage(response.id);
298
+ if (message.conversation_ext_id)
299
+ return { conversationId: message.conversation_ext_id, responseId: response.id };
300
+ } catch (error) {
301
+ if (!(error && typeof error === "object" && "status" in error && error.status === 404))
302
+ throw error;
303
+ }
304
+ }
305
+ throw new Error(
306
+ `The run ${response.id} started, but its thread is not available yet. Do not submit it again.`
307
+ );
308
+ },
309
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.allConversations() })
310
+ });
311
+ }
312
+ function useAgentSession(agentId, options) {
313
+ const arbi = useArbi();
314
+ return useQuery({
315
+ queryKey: ["arbi", "agent-session", agentId],
316
+ queryFn: () => arbi.agents.getSession(agentId),
317
+ enabled: !!agentId && arbi.isLoggedIn && (options?.enabled ?? true),
318
+ refetchInterval: 5e3
319
+ });
320
+ }
321
+ function useAgentChats(agentId) {
322
+ const arbi = useArbi();
323
+ return useQuery({
324
+ queryKey: ["arbi", "agent-chats", agentId],
325
+ queryFn: () => arbi.agents.listChats(agentId),
326
+ enabled: !!agentId && arbi.isLoggedIn,
327
+ refetchInterval: 5e3
328
+ });
329
+ }
330
+ function useStartAgentSession() {
331
+ const arbi = useArbi();
332
+ const cache = useQueryClient();
333
+ return useMutation({
334
+ mutationFn: ({ agentId, workspaceId }) => arbi.agents.startSession(agentId, workspaceId),
335
+ onSuccess: () => cache.invalidateQueries({ queryKey: ["arbi", "agent-session"] })
336
+ });
337
+ }
338
+ function useCloseAgentSession() {
339
+ const arbi = useArbi();
340
+ const cache = useQueryClient();
341
+ return useMutation({
342
+ mutationFn: (agentId) => arbi.agents.closeSession(agentId),
343
+ onSuccess: () => cache.invalidateQueries({ queryKey: ["arbi", "agent-session"] })
344
+ });
345
+ }
33
346
  function previewText(content) {
34
347
  return content.replace(/\s+/g, " ").trim().replace(/^(?:(?:[-*•>]|#{1,6})\s+)+/, "");
35
348
  }
@@ -79,26 +392,50 @@ var UNFINISHED_STATUSES = /* @__PURE__ */ new Set(["failed", "in_progress", "que
79
392
  var FAILED_MESSAGE = "The assistant request failed.";
80
393
  var UNFINISHED_MESSAGE = "The answer did not finish. Try again.";
81
394
  var isUnfinished = (m) => UNFINISHED_STATUSES.has(m.status);
82
- function messagesFromHistory(history) {
395
+ function messagesFromHistory(history, live = false) {
83
396
  return history.filter(
84
- (m) => m.role !== "system" && (m.content.trim() !== "" || m.role === "assistant" && isUnfinished(m))
397
+ (m) => m.role !== "system" && (m.content.trim() !== "" || m.role === "assistant" && (isUnfinished(m) || m.status === "cancelled"))
85
398
  ).map((m) => {
86
399
  if (m.role === "user") {
87
- return { id: m.external_id, role: "user", content: m.content, status: "complete" };
400
+ return {
401
+ id: m.external_id,
402
+ role: "user",
403
+ content: m.content,
404
+ status: "complete",
405
+ ...m.created_by_ext_id ? { authorId: m.created_by_ext_id } : {},
406
+ ...m.mentions?.length ? { mentions: m.mentions } : {}
407
+ };
88
408
  }
89
409
  const citations = toChatCitations({ tools: m.tools });
90
410
  const unfinished = isUnfinished(m);
411
+ const runningElsewhere = live && (m.status === "in_progress" || m.status === "queued");
91
412
  return {
92
413
  id: m.external_id,
93
414
  role: "assistant",
94
415
  content: m.content,
95
- status: unfinished ? "error" : "complete",
96
- ...unfinished ? { error: m.status === "failed" ? FAILED_MESSAGE : UNFINISHED_MESSAGE } : {},
416
+ status: runningElsewhere ? "streaming" : unfinished ? "error" : "complete",
417
+ ...m.status === "cancelled" ? { stopped: true } : {},
418
+ ...unfinished && !runningElsewhere ? { error: m.status === "failed" ? FAILED_MESSAGE : UNFINISHED_MESSAGE } : {},
97
419
  ...unfinished ? {} : { responseId: m.external_id },
98
420
  ...citations.length > 0 ? { citations } : {}
99
421
  };
100
422
  });
101
423
  }
424
+ function mergeHistory(history, local) {
425
+ const merged = [...history];
426
+ for (const message of local) {
427
+ if (!message.pendingHistory) continue;
428
+ const question = local.find((candidate) => candidate.id === message.questionId);
429
+ const pending = question?.storedId && history.some((stored) => stored.id === question.storedId) ? { ...message, questionId: question.storedId } : message;
430
+ const storedId = message.storedId ?? message.responseId ?? message.id;
431
+ const index = merged.findIndex((stored) => stored.id === storedId);
432
+ if (index < 0) merged.push(pending);
433
+ else if (message.role === "assistant" && merged[index].status === "streaming") {
434
+ merged[index] = pending;
435
+ }
436
+ }
437
+ return merged;
438
+ }
102
439
  function chainId(messages) {
103
440
  for (let i = messages.length - 1; i >= 0; i--) {
104
441
  const m = messages[i];
@@ -119,43 +456,61 @@ var messageOf = (e, fallback) => e instanceof Error ? e.message : fallback;
119
456
  var isAbort = (e) => e instanceof DOMException && e.name === "AbortError";
120
457
  var TITLE_REFRESH_DELAYS_MS = [2500, 6e3, 12e3];
121
458
  function useArbiChat(options = {}) {
122
- const { workspaceId, model, titleLength = 48, streamRecovery } = options;
459
+ const { workspaceId, agentId, model, titleLength = 48, streamRecovery } = options;
123
460
  const recoveryIntervalMs = streamRecovery?.intervalMs ?? 3e3;
124
461
  const recoveryTimeoutMs = streamRecovery?.timeoutMs ?? 12e4;
125
462
  const arbi = useArbi();
126
463
  const { data: docs = [], isLoading: isLoadingDocuments, refetch } = useDocuments(workspaceId);
127
- const {
128
- data: stored = [],
129
- isLoading: isLoadingThreads,
130
- refetch: refetchConversations
131
- } = useConversations(workspaceId);
464
+ const ordinaryHistory = useConversations(workspaceId, { enabled: !agentId, live: true });
465
+ const agentHistory = useAgentChats(agentId);
466
+ const stored = useMemo(
467
+ () => agentId ? (agentHistory.data ?? []).filter((chat) => chat.workspace_ext_id === workspaceId).map((chat) => ({
468
+ external_id: chat.conversation_ext_id,
469
+ title: chat.title,
470
+ updated_at: chat.updated_at,
471
+ message_count: 1,
472
+ status: null,
473
+ assistant_mode: "always"
474
+ })) : ordinaryHistory.data ?? [],
475
+ [agentId, agentHistory.data, ordinaryHistory.data, workspaceId]
476
+ );
477
+ const isLoadingThreads = agentId ? agentHistory.isLoading : ordinaryHistory.isLoading;
478
+ const refetchConversations = agentId ? agentHistory.refetch : ordinaryHistory.refetch;
132
479
  const query = useAssistantQuery();
133
480
  const { mutateAsync } = query;
481
+ const [stagedFiles, setStagedFiles] = useState([]);
134
482
  const [threads, setThreads] = useState(() => [emptyThread()]);
135
483
  const [activeKey, setActiveKey] = useState(() => threads[0].key);
136
484
  const [deselected, setDeselected] = useState(/* @__PURE__ */ new Set());
137
485
  const [isRunning, setIsRunning] = useState(false);
486
+ const [isPostingComment, setIsPostingComment] = useState(false);
487
+ const [isUpdatingAssistantMode, setIsUpdatingAssistantMode] = useState(false);
138
488
  const [isUploading, setIsUploading] = useState(false);
139
489
  const [uploadError, setUploadError] = useState(null);
140
490
  const [historyError, setHistoryError] = useState(null);
141
491
  const abortRef = useRef(null);
492
+ const runIdRef = useRef(null);
493
+ const stopRequestedRef = useRef(false);
142
494
  const timersRef = useRef([]);
143
495
  useEffect(() => {
144
496
  const timers = timersRef.current;
145
497
  return () => timers.forEach(clearTimeout);
146
498
  }, []);
147
- const scopeRef = useRef(workspaceId);
499
+ const chatScope = `${workspaceId ?? ""}:${agentId ?? ""}`;
500
+ const scopeRef = useRef(chatScope);
148
501
  useEffect(() => {
149
- if (scopeRef.current === workspaceId) return;
150
- scopeRef.current = workspaceId;
502
+ if (scopeRef.current === chatScope) return;
503
+ scopeRef.current = chatScope;
504
+ setStagedFiles([]);
151
505
  const fresh = emptyThread();
152
506
  setThreads([fresh]);
153
507
  setActiveKey(fresh.key);
154
508
  setDeselected(/* @__PURE__ */ new Set());
155
509
  setUploadError(null);
156
510
  setHistoryError(null);
157
- }, [workspaceId]);
511
+ }, [chatScope]);
158
512
  const active = threads.find((t) => t.key === activeKey) ?? threads[0];
513
+ const assistantMode = active.assistantMode ?? stored.find((c) => c.external_id === active.conversationId)?.assistant_mode ?? "always";
159
514
  const threadEntries = useMemo(() => {
160
515
  const storedIds = new Set(stored.map((c) => c.external_id));
161
516
  const byConversation = new Map(
@@ -163,7 +518,7 @@ function useArbiChat(options = {}) {
163
518
  );
164
519
  const fresh = threads.filter((t) => !t.conversationId);
165
520
  const justSaved = threads.filter((t) => t.conversationId && !storedIds.has(t.conversationId));
166
- 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) => {
521
+ const persisted = [...stored].filter((c) => c.status != null || c.message_count > 0 || byConversation.has(c.external_id)).sort((a, b) => b.updated_at.localeCompare(a.updated_at)).map((c) => {
167
522
  const local = byConversation.get(c.external_id);
168
523
  return {
169
524
  key: local?.key ?? c.external_id,
@@ -177,41 +532,71 @@ function useArbiChat(options = {}) {
177
532
  ...persisted
178
533
  ];
179
534
  }, [threads, stored]);
535
+ const liveHistory = assistantMode === "mentioned" || stored.some(
536
+ (conversation) => conversation.external_id === active.conversationId && conversation.status === "generating"
537
+ ) || active.messages.some((message) => message.status === "streaming");
180
538
  const needsLoad = !!active.conversationId && !active.isLoaded;
181
539
  const loaded = useConversationThreads(workspaceId, active.conversationId ?? void 0, {
182
- enabled: needsLoad
540
+ enabled: needsLoad || liveHistory && !isRunning && !isPostingComment,
541
+ live: liveHistory
183
542
  });
543
+ const refetchThread = loaded.refetch;
544
+ const selectedStored = stored.find(
545
+ (conversation) => conversation.external_id === active.conversationId
546
+ );
547
+ const historyRevision = selectedStored ? `${selectedStored.external_id}:${selectedStored.updated_at}:${selectedStored.message_count}:${selectedStored.status}` : void 0;
548
+ const observedHistoryRevision = useRef(void 0);
549
+ useEffect(() => {
550
+ const previous = observedHistoryRevision.current;
551
+ observedHistoryRevision.current = historyRevision;
552
+ if (previous && previous !== historyRevision && active.isLoaded && !isRunning && !isPostingComment)
553
+ void refetchThread();
554
+ }, [historyRevision, active.isLoaded, isRunning, isPostingComment, refetchThread]);
184
555
  useEffect(() => {
185
556
  const data = loaded.data;
186
- if (!needsLoad || !data || data.conversation_ext_id !== active.conversationId) return;
557
+ if (isRunning || isPostingComment || !data || data.conversation_ext_id !== active.conversationId)
558
+ return;
187
559
  const primary = [...data.threads].sort((a, b) => b.history.length - a.history.length)[0];
188
- const messages = messagesFromHistory(primary?.history ?? []);
560
+ const messages = messagesFromHistory(primary?.history ?? [], liveHistory);
189
561
  setThreads(
190
562
  (all) => all.map(
191
- (t) => t.key === active.key ? {
192
- ...t,
193
- isLoaded: true,
194
- messages,
195
- responseId: chainId(messages)
196
- } : t
563
+ (t) => t.key === active.key ? (() => {
564
+ const merged = mergeHistory(messages, t.messages);
565
+ return {
566
+ ...t,
567
+ isLoaded: true,
568
+ assistantMode: data.assistant_mode,
569
+ messages: merged,
570
+ responseId: chainId(merged)
571
+ };
572
+ })() : t
197
573
  )
198
574
  );
199
- }, [loaded.data, needsLoad, active.conversationId, active.key]);
575
+ }, [
576
+ loaded.data,
577
+ liveHistory,
578
+ needsLoad,
579
+ active.conversationId,
580
+ active.key,
581
+ assistantMode,
582
+ isRunning,
583
+ isPostingComment
584
+ ]);
200
585
  useEffect(() => {
201
586
  if (loaded.error)
202
587
  setHistoryError(messageOf(loaded.error, "The conversation could not be loaded."));
203
588
  }, [loaded.error]);
204
589
  const selectedIds = useMemo(
205
- () => docs.map((d) => d.external_id).filter((id) => !!id && !deselected.has(id)),
206
- [docs, deselected]
590
+ () => (agentId ? [] : docs).map((d) => d.external_id).filter((id) => !!id && !deselected.has(id)),
591
+ [docs, deselected, agentId]
207
592
  );
208
593
  const documents = useMemo(
209
- () => docs.filter((d) => !!d.external_id).map((d) => ({
594
+ () => agentId ? stagedFiles.map((file) => ({ ...file, isSelected: !deselected.has(file.id) })) : docs.filter((d) => !!d.external_id).map((d) => ({
210
595
  id: d.external_id,
211
596
  name: d.file_name ?? d.external_id,
212
597
  isSelected: !deselected.has(d.external_id)
213
598
  })),
214
- [docs, deselected]
599
+ [docs, deselected, agentId, stagedFiles]
215
600
  );
216
601
  const patchThread = useCallback(
217
602
  (key, patch) => setThreads((all) => all.map((t) => t.key === key ? patch(t) : t)),
@@ -254,15 +639,31 @@ function useArbiChat(options = {}) {
254
639
  },
255
640
  [arbi, recoveryIntervalMs, recoveryTimeoutMs]
256
641
  );
642
+ const cancelNativeRun = useCallback(
643
+ async (responseId, controller) => {
644
+ try {
645
+ await arbi.assistant.cancelRun(responseId);
646
+ if (abortRef.current === controller) controller.abort();
647
+ } catch (error2) {
648
+ stopRequestedRef.current = false;
649
+ setHistoryError(messageOf(error2, "The agent run could not be stopped."));
650
+ }
651
+ },
652
+ [arbi]
653
+ );
257
654
  const ask = useCallback(
258
- async (thread, text) => {
655
+ async (thread, text, mentions) => {
259
656
  const userMessage = {
657
+ pendingHistory: true,
658
+ ...mentions?.length ? { mentions } : {},
260
659
  id: nextId("msg"),
261
660
  role: "user",
262
661
  content: text,
263
662
  status: "complete"
264
663
  };
265
664
  const answer = {
665
+ pendingHistory: true,
666
+ questionId: userMessage.id,
266
667
  id: nextId("msg"),
267
668
  role: "assistant",
268
669
  content: "",
@@ -275,6 +676,8 @@ function useArbiChat(options = {}) {
275
676
  }));
276
677
  const controller = new AbortController();
277
678
  abortRef.current = controller;
679
+ runIdRef.current = null;
680
+ stopRequestedRef.current = false;
278
681
  setIsRunning(true);
279
682
  let buffer = "";
280
683
  let steps = 0;
@@ -291,15 +694,25 @@ function useArbiChat(options = {}) {
291
694
  revertChain();
292
695
  };
293
696
  try {
697
+ const lastMessage2 = thread.messages[thread.messages.length - 1];
698
+ const parentId = lastMessage2?.role === "user" && (lastMessage2.storedId || !lastMessage2.pendingHistory) ? lastMessage2.storedId ?? lastMessage2.id : thread.responseId;
294
699
  const result = await mutateAsync({
295
- question: text,
700
+ question: agentId && stagedFiles.some((file) => !deselected.has(file.id)) ? text + "\n\nSource files staged in your sandbox:\n" + stagedFiles.filter((file) => !deselected.has(file.id)).map((file) => `${file.name}: ${file.id}`).join("\n") : text,
701
+ ...agentId ? { agentId, workspaceId } : {},
296
702
  docIds: selectedIds,
297
- previousResponseId: thread.responseId,
703
+ previousResponseId: parentId,
704
+ ...thread.conversationId ? { conversationId: thread.conversationId } : {},
705
+ ...!thread.conversationId && thread.assistantMode === "mentioned" ? { assistantMode: "mentioned" } : {},
706
+ ...mentions?.length ? { mentions } : {},
298
707
  model,
299
708
  signal: controller.signal,
300
709
  onStreamStart: (data) => {
301
710
  if (data.assistant_message_ext_id) {
302
711
  backendMessageId = data.assistant_message_ext_id;
712
+ runIdRef.current = backendMessageId;
713
+ if (agentId && stopRequestedRef.current)
714
+ void cancelNativeRun(backendMessageId, controller);
715
+ patchMessage(thread.key, answer.id, { storedId: backendMessageId });
303
716
  patchThread(thread.key, (t) => ({
304
717
  ...t,
305
718
  responseId: data.assistant_message_ext_id ?? t.responseId
@@ -308,6 +721,7 @@ function useArbiChat(options = {}) {
308
721
  },
309
722
  onUserMessage: (ev) => {
310
723
  conversationId = conversationId ?? ev.conversation_ext_id;
724
+ patchMessage(thread.key, userMessage.id, { storedId: ev.external_id });
311
725
  patchThread(thread.key, (t) => ({
312
726
  ...t,
313
727
  conversationId: t.conversationId ?? ev.conversation_ext_id
@@ -389,37 +803,139 @@ function useArbiChat(options = {}) {
389
803
  }
390
804
  },
391
805
  [
392
- mutateAsync,
393
- patchMessage,
806
+ mutateAsync,
807
+ patchMessage,
808
+ patchThread,
809
+ refreshTitles,
810
+ recoverStoredAnswer,
811
+ selectedIds,
812
+ model,
813
+ titleLength,
814
+ agentId,
815
+ cancelNativeRun,
816
+ workspaceId,
817
+ stagedFiles,
818
+ deselected
819
+ ]
820
+ );
821
+ const send = useCallback(
822
+ async (text, options2) => {
823
+ const trimmed = text.trim();
824
+ if (!trimmed || isRunning || isPostingComment || isUpdatingAssistantMode || !active.isLoaded)
825
+ return;
826
+ setHistoryError(null);
827
+ if (assistantMode === "mentioned" && !options2?.mentions?.some((m) => m.type === "assistant")) {
828
+ setIsPostingComment(true);
829
+ setHistoryError(null);
830
+ try {
831
+ const saved = await arbi.conversations.postMessage({
832
+ content: trimmed,
833
+ assistant_mode: "mentioned",
834
+ mentions: options2?.mentions ?? [],
835
+ ...active.conversationId ? { conversation_ext_id: active.conversationId } : {}
836
+ });
837
+ patchThread(active.key, (t) => ({
838
+ ...t,
839
+ conversationId: saved.conversation_ext_id,
840
+ title: t.title || titleFromQuestion(trimmed, titleLength),
841
+ assistantMode: "mentioned",
842
+ responseId: saved.external_id,
843
+ messages: [
844
+ ...t.messages,
845
+ {
846
+ pendingHistory: true,
847
+ id: saved.external_id,
848
+ role: "user",
849
+ content: saved.content,
850
+ status: "complete",
851
+ ...saved.created_by_ext_id ? { authorId: saved.created_by_ext_id } : {}
852
+ }
853
+ ]
854
+ }));
855
+ await refetchConversations();
856
+ if (active.conversationId) await refetchThread();
857
+ } catch (e) {
858
+ setHistoryError(messageOf(e, "The comment could not be posted."));
859
+ throw e;
860
+ } finally {
861
+ setIsPostingComment(false);
862
+ }
863
+ return;
864
+ }
865
+ if (active.messages.some((message) => message.status === "streaming")) {
866
+ const message = "An assistant reply is already running in this thread. You can still post a comment.";
867
+ setHistoryError(message);
868
+ throw new Error(message);
869
+ }
870
+ await ask(active, trimmed, options2?.mentions);
871
+ },
872
+ [
873
+ active,
874
+ ask,
875
+ isRunning,
876
+ isPostingComment,
877
+ isUpdatingAssistantMode,
878
+ assistantMode,
879
+ arbi,
394
880
  patchThread,
395
- refreshTitles,
396
- recoverStoredAnswer,
397
- selectedIds,
398
- model,
399
- titleLength
881
+ titleLength,
882
+ refetchConversations,
883
+ refetchThread
400
884
  ]
401
885
  );
402
- const send = useCallback(
403
- async (text) => {
404
- const trimmed = text.trim();
405
- if (!trimmed || isRunning || !active.isLoaded) return;
406
- await ask(active, trimmed);
886
+ const setAssistantMode = useCallback(
887
+ async (mode) => {
888
+ if (isRunning || isPostingComment || isUpdatingAssistantMode) return;
889
+ setIsUpdatingAssistantMode(true);
890
+ setHistoryError(null);
891
+ try {
892
+ if (active.conversationId) {
893
+ await arbi.conversations.setAssistantMode(active.conversationId, mode);
894
+ await refetchThread();
895
+ }
896
+ patchThread(active.key, (t) => ({ ...t, assistantMode: mode }));
897
+ await refetchConversations();
898
+ } catch (e) {
899
+ setHistoryError(messageOf(e, "The assistant mode could not be changed."));
900
+ } finally {
901
+ setIsUpdatingAssistantMode(false);
902
+ }
407
903
  },
408
- [active, ask, isRunning]
904
+ [
905
+ active,
906
+ arbi,
907
+ patchThread,
908
+ refetchConversations,
909
+ refetchThread,
910
+ isRunning,
911
+ isPostingComment,
912
+ isUpdatingAssistantMode
913
+ ]
409
914
  );
410
- const stop = useCallback(() => abortRef.current?.abort(), []);
915
+ const stop = useCallback(() => {
916
+ const controller = abortRef.current;
917
+ if (!controller) return;
918
+ if (!agentId) {
919
+ controller.abort();
920
+ return;
921
+ }
922
+ stopRequestedRef.current = true;
923
+ if (runIdRef.current) void cancelNativeRun(runIdRef.current, controller);
924
+ }, [agentId, cancelNativeRun]);
411
925
  const lastMessage = active.messages[active.messages.length - 1];
412
926
  const error = lastMessage?.status === "error" ? lastMessage.error ?? null : null;
413
927
  const retry = useCallback(async () => {
414
928
  if (isRunning) return;
415
929
  const messages = active.messages;
416
930
  const failed = messages[messages.length - 1];
417
- const question = messages[messages.length - 2];
931
+ const question = failed?.questionId ? messages.find((message) => message.id === failed.questionId) : messages[messages.length - 2];
418
932
  if (failed?.status !== "error" || question?.role !== "user") return;
419
- const rest = messages.slice(0, -2);
933
+ const rest = messages.filter(
934
+ (message) => message.id !== failed.id && message.id !== question.id
935
+ );
420
936
  const trimmed = { ...active, messages: rest, responseId: chainId(rest) };
421
937
  patchThread(active.key, () => trimmed);
422
- await ask(trimmed, question.content);
938
+ await ask(trimmed, question.content, question.mentions);
423
939
  }, [active, ask, isRunning, patchThread]);
424
940
  const newThread = useCallback(() => {
425
941
  setHistoryError(null);
@@ -482,17 +998,29 @@ function useArbiChat(options = {}) {
482
998
  setIsUploading(true);
483
999
  setUploadError(null);
484
1000
  try {
485
- for (const file of files) await arbi.documents.uploadFile(file, file.name);
486
- await refetch();
1001
+ for (const file of files) {
1002
+ if (agentId) {
1003
+ const staged = await arbi.agents.stageFile(agentId, file, file.name);
1004
+ setStagedFiles((all) => [
1005
+ ...all.filter((existing) => existing.id !== staged.path),
1006
+ { id: staged.path, name: staged.name }
1007
+ ]);
1008
+ } else await arbi.documents.uploadFile(file, file.name);
1009
+ }
1010
+ if (!agentId) await refetch();
487
1011
  } catch (e) {
488
1012
  setUploadError(messageOf(e, "The upload failed."));
489
1013
  } finally {
490
1014
  setIsUploading(false);
491
1015
  }
492
1016
  },
493
- [arbi, refetch]
1017
+ [arbi, refetch, agentId]
494
1018
  );
495
1019
  return {
1020
+ assistantMode,
1021
+ isUpdatingAssistantMode,
1022
+ isPostingComment,
1023
+ setAssistantMode,
496
1024
  threads: threadEntries.map(({ id, title }) => ({ id, title })),
497
1025
  activeThreadId: publicId(active),
498
1026
  messages: active.messages,
@@ -935,229 +1463,6 @@ function useArbiShellWorkspaces(options = {}) {
935
1463
  select: select ?? defaultSelect
936
1464
  });
937
1465
  }
938
- function useUpdateConversation() {
939
- const arbi = useArbi();
940
- const qc = useQueryClient();
941
- return useMutation({
942
- mutationFn: ({
943
- conversationId,
944
- patch
945
- }) => arbi.conversations.update(conversationId, patch),
946
- onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.allConversations() })
947
- });
948
- }
949
- function useCreateWork() {
950
- const arbi = useArbi();
951
- const qc = useQueryClient();
952
- return useMutation({
953
- mutationFn: (body) => arbi.conversations.createWork(body),
954
- onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.allConversations() })
955
- });
956
- }
957
- function useRunConversation() {
958
- const arbi = useArbi();
959
- const qc = useQueryClient();
960
- return useMutation({
961
- mutationFn: (conversationId) => arbi.conversations.runWithAuth(conversationId),
962
- onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.allConversations() })
963
- });
964
- }
965
- function useReviewConversation() {
966
- const arbi = useArbi();
967
- const qc = useQueryClient();
968
- return useMutation({
969
- mutationFn: ({
970
- conversationId,
971
- review
972
- }) => arbi.conversations.review(conversationId, review),
973
- onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.allConversations() })
974
- });
975
- }
976
- function useRequestConversationApproval() {
977
- const arbi = useArbi();
978
- const qc = useQueryClient();
979
- return useMutation({
980
- mutationFn: ({
981
- conversationId,
982
- request
983
- }) => arbi.conversations.requestApproval(conversationId, request),
984
- onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.allConversations() })
985
- });
986
- }
987
- function usePlanWork() {
988
- const arbi = useArbi();
989
- return useMutation({
990
- mutationFn: (body) => arbi.conversations.plan(body)
991
- });
992
- }
993
- function useConversationPendingAuth(conversationId, options) {
994
- const arbi = useArbi();
995
- return useQuery({
996
- queryKey: [...arbiQueryKeys.allConversations(), conversationId ?? "", "pending-auth"],
997
- queryFn: () => arbi.conversations.pendingAuth(conversationId),
998
- enabled: (options?.enabled ?? true) && !!conversationId && arbi.isLoggedIn,
999
- retry: false
1000
- });
1001
- }
1002
- function useConversationActivity(conversationId, options) {
1003
- const arbi = useArbi();
1004
- return useQuery({
1005
- queryKey: [...arbiQueryKeys.allConversations(), conversationId ?? "", "activity"],
1006
- queryFn: () => arbi.conversations.activity(conversationId),
1007
- enabled: (options?.enabled ?? true) && !!conversationId && arbi.isLoggedIn,
1008
- refetchInterval: options?.live ? 2500 : false
1009
- });
1010
- }
1011
- function useWorkflows(options) {
1012
- const arbi = useArbi();
1013
- return useQuery({
1014
- queryKey: arbiQueryKeys.workflows(),
1015
- queryFn: () => arbi.workflows.list(),
1016
- enabled: options?.enabled ?? arbi.isLoggedIn
1017
- });
1018
- }
1019
- function useCreateWorkflow() {
1020
- const arbi = useArbi();
1021
- const qc = useQueryClient();
1022
- return useMutation({
1023
- mutationFn: (body) => arbi.workflows.create(body),
1024
- onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.workflows() })
1025
- });
1026
- }
1027
- function useUpdateWorkflow() {
1028
- const arbi = useArbi();
1029
- const qc = useQueryClient();
1030
- return useMutation({
1031
- mutationFn: ({
1032
- workflowId,
1033
- patch
1034
- }) => arbi.workflows.update(workflowId, patch),
1035
- onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.workflows() })
1036
- });
1037
- }
1038
- function useDeleteWorkflow() {
1039
- const arbi = useArbi();
1040
- const qc = useQueryClient();
1041
- return useMutation({
1042
- mutationFn: (workflowId) => arbi.workflows.delete(workflowId),
1043
- onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.workflows() })
1044
- });
1045
- }
1046
- function useRunWorkflow() {
1047
- const arbi = useArbi();
1048
- const qc = useQueryClient();
1049
- return useMutation({
1050
- mutationFn: (workflowId) => arbi.workflows.run(workflowId),
1051
- onSuccess: () => Promise.all([
1052
- qc.invalidateQueries({ queryKey: arbiQueryKeys.workflows() }),
1053
- qc.invalidateQueries({ queryKey: arbiQueryKeys.allConversations() })
1054
- ])
1055
- });
1056
- }
1057
- function useCreateAgent() {
1058
- const arbi = useArbi();
1059
- const qc = useQueryClient();
1060
- return useMutation({
1061
- mutationFn: (name) => arbi.agents.create(name),
1062
- onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.agents() })
1063
- });
1064
- }
1065
- function useAgentConfig(agentExtId, options) {
1066
- const arbi = useArbi();
1067
- return useQuery({
1068
- queryKey: arbiQueryKeys.agentConfig(agentExtId),
1069
- queryFn: async () => agentExtId ? await arbi.agents.getConfig(agentExtId) : await arbi.agentConfig.get("default"),
1070
- enabled: (options?.enabled ?? true) && arbi.isLoggedIn
1071
- });
1072
- }
1073
- function useAgentModels(options) {
1074
- const arbi = useArbi();
1075
- return useQuery({
1076
- queryKey: arbiQueryKeys.models(),
1077
- queryFn: async () => (await arbi.agentConfig.getModels()).models.map((m) => m.model_name),
1078
- enabled: (options?.enabled ?? true) && arbi.isLoggedIn
1079
- });
1080
- }
1081
- function useSaveAgentConfig() {
1082
- const arbi = useArbi();
1083
- const qc = useQueryClient();
1084
- return useMutation({
1085
- mutationFn: async ({ agentExtId, title, sections }) => {
1086
- const pinned = await arbi.agents.getConfig(agentExtId);
1087
- const base = pinned ?? await arbi.agentConfig.get("default");
1088
- const merged = structuredClone(base);
1089
- for (const [section, params] of Object.entries(sections)) {
1090
- merged[section] = { ...merged[section] ?? {}, ...params };
1091
- }
1092
- return arbi.agentConfig.save({
1093
- ...merged,
1094
- agent_ext_id: agentExtId,
1095
- title: title ?? `agent-${agentExtId}`
1096
- });
1097
- },
1098
- onSuccess: (_saved, { agentExtId }) => Promise.all([
1099
- qc.invalidateQueries({ queryKey: arbiQueryKeys.configs() }),
1100
- qc.invalidateQueries({ queryKey: arbiQueryKeys.agentConfig(agentExtId) }),
1101
- // The agent list shows the pinned persona/model, so it refreshes too.
1102
- qc.invalidateQueries({ queryKey: arbiQueryKeys.agents() })
1103
- ])
1104
- });
1105
- }
1106
- function useAgentWorkspaces(agentExtId, options) {
1107
- const arbi = useArbi();
1108
- return useQuery({
1109
- queryKey: arbiQueryKeys.agentWorkspaces(agentExtId ?? ""),
1110
- queryFn: () => arbi.agents.listWorkspaces(agentExtId),
1111
- enabled: (options?.enabled ?? true) && !!agentExtId && arbi.isLoggedIn
1112
- });
1113
- }
1114
- function useGrantAgentAccess() {
1115
- const arbi = useArbi();
1116
- const qc = useQueryClient();
1117
- return useMutation({
1118
- mutationFn: async ({
1119
- agentExtId,
1120
- workspaceId,
1121
- mode = { kind: "standing" }
1122
- }) => {
1123
- if (mode.kind === "temporary") {
1124
- return arbi.agents.grantTemporaryAccess(agentExtId, workspaceId, mode.ttlSeconds);
1125
- }
1126
- const agent = (await arbi.agents.list()).find((a) => a.external_id === agentExtId);
1127
- if (!agent?.email) throw new Error("The agent's address is unknown.");
1128
- return arbi.workspaces.grantAccess({ workspaceId, emails: [agent.email] });
1129
- },
1130
- onSuccess: (_res, { agentExtId }) => qc.invalidateQueries({ queryKey: arbiQueryKeys.agentWorkspaces(agentExtId) })
1131
- });
1132
- }
1133
- function useRevokeAgentAccess() {
1134
- const arbi = useArbi();
1135
- const qc = useQueryClient();
1136
- return useMutation({
1137
- mutationFn: async ({
1138
- agentExtId,
1139
- workspaceId
1140
- }) => {
1141
- const previous = arbi.hasWorkspace ? arbi.getWorkspaceId() : null;
1142
- await arbi.selectWorkspace(workspaceId);
1143
- try {
1144
- await arbi.workspaces.removeUsers([agentExtId]);
1145
- } finally {
1146
- if (previous && previous !== workspaceId) await arbi.selectWorkspace(previous);
1147
- }
1148
- },
1149
- onSuccess: (_res, { agentExtId }) => qc.invalidateQueries({ queryKey: arbiQueryKeys.agentWorkspaces(agentExtId) })
1150
- });
1151
- }
1152
- function useSkills(options) {
1153
- const arbi = useArbi();
1154
- return useQuery({
1155
- queryKey: [...arbiQueryKeys.skills(), options?.includeHidden ?? false],
1156
- queryFn: () => arbi.assistant.listSkills({ includeHidden: options?.includeHidden }),
1157
- enabled: (options?.enabled ?? true) && arbi.isLoggedIn,
1158
- staleTime: 3e4
1159
- });
1160
- }
1161
1466
  function useContacts(options) {
1162
1467
  const arbi = useArbi();
1163
1468
  return useQuery({
@@ -3765,6 +4070,34 @@ function ActivityFeed({
3765
4070
  );
3766
4071
  }) });
3767
4072
  }
4073
+ var workTypes = [
4074
+ "mention",
4075
+ "workspace_access_request",
4076
+ "conversation_ready_for_review",
4077
+ "conversation_approval_request",
4078
+ "conversation_done",
4079
+ "workflow_failed"
4080
+ ];
4081
+ function useWorkNotifications(options) {
4082
+ const arbi = useArbi();
4083
+ const queries = useQueryClient();
4084
+ const queryKey = ["work-notifications", options.apiUrl, options.userId];
4085
+ const query = useQuery({
4086
+ queryKey,
4087
+ queryFn: async () => (await Promise.all(workTypes.map((type) => arbi.notifications.list(type)))).flat(),
4088
+ enabled: options.enabled && Boolean(options.userId),
4089
+ refetchInterval: 3e4
4090
+ });
4091
+ useArbiWebSocket((message) => {
4092
+ if (workTypes.some((type) => type === message.type))
4093
+ void queries.invalidateQueries({ queryKey });
4094
+ });
4095
+ const receipt = useMutation({
4096
+ mutationFn: (ids) => arbi.notifications.markRead(ids),
4097
+ onSuccess: () => queries.invalidateQueries({ queryKey })
4098
+ });
4099
+ return { ...query, markRead: receipt.mutateAsync };
4100
+ }
3768
4101
 
3769
4102
  // src/directmessages/utils.ts
3770
4103
  function userInitials(user) {
@@ -5833,6 +6166,6 @@ function hasVideoTrack(blob) {
5833
6166
  });
5834
6167
  }
5835
6168
 
5836
- 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, useConversationActivity, useConversationPendingAuth, useCreateAgent, useCreateWork, useCreateWorkflow, useCurrentSessionId, useDeleteAccount, useDeleteAccountFlow, useDeleteWorkflow, useDirectMessages, useEmailChange, useGrantAgentAccess, useLogoutAllSessions, useNameEditor, useNotificationPreferences, usePasswordChange, usePasswordChangeForm, usePendingContacts, usePlanWork, useProfile, useRemoveContacts, useRequestConversationApproval, useRequestEmailChange, useReviewConversation, useRevokeAgentAccess, useRunConversation, useRunWorkflow, useSaveAgentConfig, useSemanticSearch, useShellWorkspaces, useSkills, useTerminateSession, useUpdateConversation, useUpdateProfile, useUpdateWorkflow, useUserSettings, useVideoRecorder, useWorkflows, useWorkspaceDocs, userDisplayName, userInitials };
6169
+ 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, useAgentChats, useAgentConfig, useAgentModels, useAgentSession, useAgentWorkspaces, useAiTask, useArbiChat, useArbiConnected, useArbiDocumentSync, useArbiShellWorkspaces, useArbiUiStore, useArbiWebSocket, useArbiWsContext, useArbiWsMessage, useArbiWsStatus, useArbiWsStore, useArbiWsTask, useArbiWsTasks, useAvatarCrop, useCameraStream, useCloseAgentSession, useCommandPalette, useConfirmEmailChange, useContacts, useConversationActivity, useConversationPendingAuth, useCreateAgent, useCreateWork, useCreateWorkflow, useCurrentSessionId, useDeleteAccount, useDeleteAccountFlow, useDeleteWorkflow, useDirectMessages, useEmailChange, useGrantAgentAccess, useLogoutAllSessions, useNameEditor, useNotificationPreferences, usePasswordChange, usePasswordChangeForm, usePendingContacts, usePlanWork, useProfile, useRemoveContacts, useRequestConversationApproval, useRequestEmailChange, useReviewConversation, useRevokeAgentAccess, useRunConversation, useRunWorkflow, useSaveAgentConfig, useSemanticSearch, useShellWorkspaces, useSkills, useStartAgentSession, useStartWorkQuery, useTerminateSession, useUpdateConversation, useUpdateProfile, useUpdateWorkflow, useUploadSkillFiles, useUserSettings, useVideoRecorder, useWorkNotifications, useWorkflows, useWorkspaceDocs, userDisplayName, userInitials };
5837
6170
  //# sourceMappingURL=index.js.map
5838
6171
  //# sourceMappingURL=index.js.map