@arbidocs/react 0.5.120 → 0.5.121

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.
Files changed (88) hide show
  1. package/dist/PdfViewerImpl-ZVJZ4KNW.cjs +22 -0
  2. package/dist/PdfViewerImpl-ZVJZ4KNW.cjs.map +1 -0
  3. package/dist/{PdfViewerImpl.d-B28hLEuj.d.ts → PdfViewerImpl.d-CqnEzQO7.d.ts} +9 -1
  4. package/dist/annotations/freeze/index.d.ts +1 -1
  5. package/dist/artifacts/artifact-kind.js +1 -0
  6. package/dist/artifacts/artifact-kind.js.map +2 -2
  7. package/dist/artifacts/index.cjs +3 -2
  8. package/dist/artifacts/index.cjs.map +1 -1
  9. package/dist/artifacts/index.d.ts +3 -2
  10. package/dist/calls/livekit/index.cjs +41 -41
  11. package/dist/chat/turn-flush.js +16 -0
  12. package/dist/chat/turn-flush.js.map +7 -0
  13. package/dist/chat/useConversationActions.js +3 -0
  14. package/dist/chat/useConversationActions.js.map +2 -2
  15. package/dist/{chunk-QP3WQSAA.cjs → chunk-4B2GW3AY.cjs} +3 -3
  16. package/dist/{chunk-QP3WQSAA.cjs.map → chunk-4B2GW3AY.cjs.map} +1 -1
  17. package/dist/{chunk-YMB6XUR2.cjs → chunk-7L7QRIQ2.cjs} +11 -7
  18. package/dist/chunk-7L7QRIQ2.cjs.map +1 -0
  19. package/dist/{chunk-KDYYAZXP.cjs → chunk-DLEHM4IA.cjs} +227 -155
  20. package/dist/chunk-DLEHM4IA.cjs.map +1 -0
  21. package/dist/{PdfViewerImpl-QUSDLNNM.cjs → chunk-ICBT2XGF.cjs} +5 -2
  22. package/dist/chunk-ICBT2XGF.cjs.map +1 -0
  23. package/dist/{chunk-AJRYZ3IC.cjs → chunk-K6ELFSG6.cjs} +3 -3
  24. package/dist/{chunk-AJRYZ3IC.cjs.map → chunk-K6ELFSG6.cjs.map} +1 -1
  25. package/dist/chunk-MAOGK7IC.cjs +61 -0
  26. package/dist/chunk-MAOGK7IC.cjs.map +1 -0
  27. package/dist/{chunk-5TRUFWKT.cjs → chunk-X5OZRLFZ.cjs} +5 -5
  28. package/dist/{chunk-5TRUFWKT.cjs.map → chunk-X5OZRLFZ.cjs.map} +1 -1
  29. package/dist/{chunk-C72FSQUE.cjs → chunk-Z3W4XFWB.cjs} +3 -3
  30. package/dist/{chunk-C72FSQUE.cjs.map → chunk-Z3W4XFWB.cjs.map} +1 -1
  31. package/dist/datasets/sources/documents.js +1 -1
  32. package/dist/datasets/sources/documents.js.map +2 -2
  33. package/dist/document-tools/core.cjs +80 -80
  34. package/dist/document-tools/images-to-pdf.js +43 -0
  35. package/dist/document-tools/images-to-pdf.js.map +7 -0
  36. package/dist/document-tools/index.cjs +184 -80
  37. package/dist/document-tools/index.cjs.map +1 -1
  38. package/dist/document-tools/index.d.ts +41 -1
  39. package/dist/document-tools/index.js +6 -1
  40. package/dist/document-tools/index.js.map +2 -2
  41. package/dist/document-tools/pdf-attachments.js +63 -0
  42. package/dist/document-tools/pdf-attachments.js.map +7 -0
  43. package/dist/entities/DocumentEntity.js +7 -2
  44. package/dist/entities/DocumentEntity.js.map +2 -2
  45. package/dist/entities/file-type.js +14 -2
  46. package/dist/entities/file-type.js.map +2 -2
  47. package/dist/entities/index.js +9 -1
  48. package/dist/entities/index.js.map +2 -2
  49. package/dist/icons/PresIcon.js +53 -0
  50. package/dist/icons/PresIcon.js.map +7 -0
  51. package/dist/icons/colors.js +2 -0
  52. package/dist/icons/colors.js.map +2 -2
  53. package/dist/icons/file-type.js +3 -0
  54. package/dist/icons/file-type.js.map +2 -2
  55. package/dist/icons/index.js +2 -0
  56. package/dist/icons/index.js.map +2 -2
  57. package/dist/index.cjs +898 -865
  58. package/dist/index.cjs.map +1 -1
  59. package/dist/{index.d-DYlUprG9.d.ts → index.d-D2puqWeN.d.ts} +17 -3
  60. package/dist/index.d.ts +41 -8
  61. package/dist/index.js +1 -0
  62. package/dist/index.js.map +2 -2
  63. package/dist/pdf-region-impl-ZG5G2OX2.cjs +98 -0
  64. package/dist/pdf-region-impl-ZG5G2OX2.cjs.map +1 -0
  65. package/dist/session/documents.js +9 -5
  66. package/dist/session/documents.js.map +2 -2
  67. package/dist/shell/index.cjs +37 -37
  68. package/dist/ui/index.cjs +182 -170
  69. package/dist/ui/index.d.ts +1 -1
  70. package/dist/ui/slider.js +7 -1
  71. package/dist/ui/slider.js.map +2 -2
  72. package/dist/viewers/PdfViewer.cjs +3 -3
  73. package/dist/viewers/PdfViewer.d.ts +2 -2
  74. package/dist/viewers/PdfViewerImpl.js +4 -1
  75. package/dist/viewers/PdfViewerImpl.js.map +2 -2
  76. package/dist/viewers/index.cjs +28 -3
  77. package/dist/viewers/index.cjs.map +1 -1
  78. package/dist/viewers/index.d.ts +68 -2
  79. package/dist/viewers/index.js +14 -0
  80. package/dist/viewers/index.js.map +2 -2
  81. package/dist/viewers/pdf-region-impl.js +96 -0
  82. package/dist/viewers/pdf-region-impl.js.map +7 -0
  83. package/dist/viewers/pdf-region.js +57 -0
  84. package/dist/viewers/pdf-region.js.map +7 -0
  85. package/package.json +3 -3
  86. package/dist/PdfViewerImpl-QUSDLNNM.cjs.map +0 -1
  87. package/dist/chunk-KDYYAZXP.cjs.map +0 -1
  88. package/dist/chunk-YMB6XUR2.cjs.map +0 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/chat/useConversationActions.ts"],
4
- "sourcesContent": ["/**\n * The chat engine's actions on a conversation: send (a turn, or a comment to the people in a\n * thread), branch, retry, stop, and the run-control commands typed while a turn runs. A turn goes\n * through `arbi.assistant.ask`, its frames through the stream handlers, and its state lives in the\n * session (`ArbiSessionState`) and the conversation cache.\n */\n\nimport { pendingChatModelSave } from '../config/config-hooks'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { useQueryClient, type QueryClient } from '@tanstack/react-query'\nimport {\n ArbiApiError,\n STOP_REPEAT_WINDOW_MS,\n getRunToCancel,\n quotaFromStreamError,\n type ConversationResponse as Conversation,\n type ThreadsResponse,\n} from '@arbidocs/sdk/browser'\nimport type { components } from '@arbidocs/client'\nimport { notify } from '../shell'\nimport { arbiQueryKeys } from '../hooks'\nimport { useArbi } from '../useArbi'\nimport { useArbiHost } from '../host/config'\nimport { selectDraft, useArbiSessionStore } from '../session'\nimport type { ArbiSessionStore } from '../session/store'\nimport {\n invalidateConversationLists,\n isConversationShared,\n patchConversation,\n patchConversationList,\n removeConversationFromLists,\n} from '../conversation-list'\nimport {\n allConversationThreads,\n clearLoadingState,\n flushStreamingTokens,\n getThreadsQueryKey,\n updateUserMessageStateInCache,\n} from './conversation-cache'\nimport {\n assistantLabels,\n cachedAssistantMode,\n cachedMentionCandidates,\n isCommentToPeople,\n} from './conversation-mentions'\nimport { useAssistantName } from '../host/assistant-copy'\nimport { hasMessageBody, toMessageMentions, extractMentions } from './mentions'\nimport { takeDocumentForTurn } from './open-document'\nimport { agentChatOwnerOf, creatorOf } from './agent-chat-owner'\nimport { bindThreadDocument, boundDocumentOf } from './thread-binding'\nimport { resolveReplyTarget } from './reply-target'\nimport {\n confirmConversationWorkspace,\n conversationWorkspaceOf,\n watchConversationWorkspaces,\n} from './conversation-workspace'\nimport { useStreamHandlers, type StreamState } from './stream-handlers'\nimport {\n AGENT_CONTROL_COMMANDS,\n cachedQuotaUsedUp,\n disconnectAttachedRun,\n refuseChatWhenOffline,\n waitForResponseId,\n} from './turn-control'\n\ntype MessageInput = components['schemas']['MessageInput']\ntype MessageMention = components['schemas']['MessageMention']\n\n/** How long a dropped turn holds its generating state while the thread is re-read. */\nconst RECONCILE_TIMEOUT_MS = 5000\n\nexport interface SendMessageOptions {\n /** An explicit edit/fork, rather than appending a human message to the shared thread. */\n branch?: boolean\n queuedContinuation?: boolean\n queueOwnerAssistantId?: string\n queuedClientId?: string\n /**\n * Bypasses the normal single-pinned-document tools computation (the chat's documents and its\n * pin, see `buildTurnRequest`) for this one send.\n * The app's general \"pin a document\" feature is deliberately one-at-a-time\n * (`selectFullContextDocument`) \u2014 a pinned TEXT doc's full content goes\n * straight into the AGENT's chat prompt, so more than one at a time risks\n * blowing that context window. Generate image is a different resource\n * entirely: reference images never touch the agent's chat context \u2014 they go\n * to the separate ImageGen model in its own request \u2014 so multiple\n * references (e.g. \"combine these two logos\") don't compound against that\n * budget, even though they still cost real tokens against ImageGen's own\n * call. This lets that caller build its own tools object with every\n * selected reference doc instead of touching the single global pin.\n */\n toolsOverride?: MessageInput['tools']\n /**\n * Send in this conversation and workspace rather than the open ones: a follow-up captured when\n * its turn started (a deferred interjection), sent after the view may have moved on.\n */\n target?: TurnTarget\n}\n\n/** The conversation and workspace a turn belongs to, captured when it starts. */\nexport interface TurnTarget {\n workspaceId: string\n conversationId: string\n}\n\n/**\n * The draft a send leaves behind. A shared chat addresses ARBI again, except after a comment to\n * people: then the box is left empty, which a shared chat reads as a comment to the thread.\n */\nfunction draftAfterSend(\n queryClient: QueryClient,\n session: ArbiSessionStore,\n wasComment: boolean,\n assistantName: string\n): string {\n const shared = isConversationShared(queryClient, session.getState().conversationId)\n return shared && !wasComment ? `@${assistantName} ` : ''\n}\n\n/**\n * A queued follow-up the backend refused before its turn started: mark its bubble failed and\n * take it out of the queue, so it does not sit there as if it were still waiting.\n */\nfunction failQueuedContinuation(\n queryClient: QueryClient,\n session: ArbiSessionStore,\n streamState: {\n conversationId: string\n queuedUserMessageId: string | null\n queueOwnerAssistantId: string | null\n queuedClientId: string | null\n }\n): void {\n if (streamState.queuedUserMessageId) {\n updateUserMessageStateInCache(\n queryClient,\n streamState.conversationId,\n streamState.queuedUserMessageId,\n 'failed',\n false\n )\n }\n if (streamState.queuedClientId) {\n session.removeQueuedMessage(streamState.queuedClientId)\n }\n notify.error('Queued message failed to send')\n}\n\n/** The signed-in user's id, or null before the session is known. */\nfunction signedInUserId(arbi: { user: { identity: () => { externalId: string } | null } }) {\n try {\n return arbi.user.identity()?.externalId ?? null\n } catch {\n return null\n }\n}\n\n/** What the actions act on: the open workspace and conversation, as the session holds them. */\nexport interface ConversationActionsInput {\n selectedWorkspaceId: string | null\n /** The cursor when the view rendered (the stream handlers place a branch against it). */\n parentMessageId: string | null\n /** The open conversation's key (`new` for a new chat). */\n conversationKey: string\n isGenerating: boolean\n lastFailedMessage: string | null\n}\n\n/** The actions on the open conversation. */\nexport function useConversationActions({\n selectedWorkspaceId,\n parentMessageId,\n conversationKey,\n isGenerating,\n lastFailedMessage,\n}: ConversationActionsInput) {\n const queryClient = useQueryClient()\n const session = useArbiSessionStore()\n const currentConversationId = conversationKey === 'new' ? null : conversationKey\n const invalidateUserWorkspaces = useCallback(\n () => void queryClient.invalidateQueries({ queryKey: arbiQueryKeys.workspaces() }),\n [queryClient]\n )\n\n const arbi = useArbi()\n // Each conversation stays in the workspace it was opened in, though the surface switches.\n useState(() => watchConversationWorkspaces(queryClient, session))\n const createHandlers = useStreamHandlers()\n const chatHost = useArbiHost().chat\n const assistantName = useAssistantName()\n const mentionCandidates = useCallback(\n () =>\n cachedMentionCandidates(\n queryClient,\n session.getState().workspaceId,\n signedInUserId(arbi),\n assistantName\n ),\n [queryClient, session, arbi, assistantName]\n )\n // The local stream a Stop is closing. Stop cancels the run on the server before it aborts the\n // stream, so the connection can drop first with a network error that is not an abort.\n const stoppedStreamRef = useRef<AbortController | null>(null)\n // True while a Stop's server cancel is in flight, so a second press does not cancel again.\n const stopInFlightRef = useRef(false)\n // When the last Stop was pressed. A press within STOP_REPEAT_WINDOW_MS of it is a double tap.\n const lastStopAtRef = useRef(0)\n // True while a Retry deletes and resends, so a second press does not do it again.\n const retryInFlightRef = useRef(false)\n // Lets sendMessage resubmit a deferred interjection as a new turn without a\n // self-reference (it is a useCallback that would otherwise close over a stale\n // or not-yet-defined copy of itself). Kept current just below its definition.\n const sendMessageRef = useRef<\n | ((content: string, parentId?: string | null, options?: SendMessageOptions) => Promise<void>)\n | null\n >(null)\n\n /**\n * Shared posts, and posts in a `mentioned` thread, run ARBI only when addressed; posts in an\n * ordinary solo chat address it implicitly.\n */\n const routeMessage = useCallback(\n (content: string, conversationId?: string | null) => {\n const state = session.getState()\n const convId = conversationId === undefined ? state.conversationId : conversationId\n const { newConversationAssistantMode } = state\n const candidates = mentionCandidates()\n const mentioned = extractMentions(content, candidates)\n const assistantMode = cachedAssistantMode(queryClient, convId, newConversationAssistantMode)\n const isComment = isCommentToPeople(queryClient, convId, content, candidates)\n const mentions = toMessageMentions(mentioned) as MessageMention[]\n // A message not taken as a comment addresses ARBI. In an existing `mentioned` thread that is\n // only one that @mentions it; what is left is the first message of a thread not created\n // yet, whose turn creates it in `mentioned` mode.\n if (\n !isComment &&\n assistantMode === 'mentioned' &&\n !mentions.some((m) => m.type === 'assistant')\n ) {\n mentions.push({ type: 'assistant' })\n }\n return {\n mentions,\n assistantMode,\n isComment,\n }\n },\n [queryClient, session, mentionCandidates]\n )\n\n /** Post a comment (no assistant run) and land the thread on it. */\n const postComment = useCallback(\n async (\n content: string,\n parentId: string | null,\n mentions: MessageMention[],\n branch = false\n ) => {\n const convId = session.getState().conversationId\n try {\n const saved = await arbi.conversations.postMessage({\n content,\n mentions,\n // Only an explicit edit creates a branch. Ordinary comments append to the\n // server's latest message, so simultaneous sends stay visible together. Else\n // (nothing yet) start a thread. The mode only applies to a conversation it creates.\n ...(branch && parentId\n ? { previous_message_ext_id: parentId }\n : convId\n ? { conversation_ext_id: convId }\n : {}),\n assistant_mode: 'mentioned',\n })\n const conversationId = saved.conversation_ext_id\n const nowIso = new Date().toISOString()\n if (!convId) {\n // A comment starts the thread without a turn to carry its binding: bind it right after.\n const bindTo = session.getState().newChatDocumentId\n session.moveConversation('new', conversationId)\n if (bindTo) session.setNewChatDocumentId(null)\n patchConversationList(queryClient, session.getState().workspaceId, (old) => [\n {\n external_id: conversationId,\n title: content.slice(0, 40),\n created_by_ext_id: saved.created_by_ext_id,\n updated_by_ext_id: null,\n created_at: nowIso,\n updated_at: nowIso,\n // A mention of someone who cannot see it yet shares it server-side: refetched below.\n is_shared: false,\n message_count: 1,\n last_message_status: 'completed',\n assistant_mode: 'mentioned',\n } as Conversation,\n ...old,\n ])\n if (bindTo)\n bindThreadDocument(arbi, queryClient, conversationId, bindTo).catch(() =>\n notify.error('Could not attach the document to this thread')\n )\n invalidateUserWorkspaces()\n } else {\n patchConversation(queryClient, conversationId, (c) => ({\n ...c,\n message_count: c.message_count + 1,\n updated_at: nowIso,\n }))\n }\n // Mentioning someone shares the conversation on the server, which this cache cannot know.\n if (mentions.some((m) => m.type === 'user')) {\n await invalidateConversationLists(queryClient)\n }\n await queryClient.invalidateQueries({ queryKey: getThreadsQueryKey(conversationId) })\n session.setCursor(saved.external_id)\n } catch (error) {\n notify.error(\n (error instanceof ArbiApiError && error.detail) || 'Failed to post the message'\n )\n // Give the text back rather than lose it.\n if (!selectDraft(session.getState())) session.setDraft(content)\n }\n },\n [arbi, session, queryClient, invalidateUserWorkspaces]\n )\n\n const sendMessage = useCallback(\n async (content: string, requestedParentId?: string | null, options?: SendMessageOptions) => {\n // Never reply to a failed or deleted message: the server refuses it, so every Retry and every\n // new message would fail again. A queued follow-up names the running turn on purpose. With\n // nothing above to reply to, the parent stays: only a fresh conversation could take it.\n const target = options?.target\n const parentId =\n options?.queuedContinuation || !requestedParentId\n ? requestedParentId\n : (resolveReplyTarget(\n queryClient,\n (target ? target.conversationId : session.getState().conversationId) || 'new',\n requestedParentId\n ) ?? requestedParentId)\n const route = routeMessage(content, target?.conversationId)\n if (route.isComment && !options?.queuedContinuation && !target) {\n await postComment(content, parentId ?? null, route.mentions, options?.branch)\n return\n }\n // A model picked just now is still being saved: the turn runs on it, not on the config before.\n const modelSave = pendingChatModelSave(session)\n if (modelSave) await modelSave.catch(() => undefined)\n const state = session.getState()\n const convId = target ? target.conversationId : state.conversationId\n const convKey = convId || 'new'\n // The turn's workspace, captured now: the conversation's own (a docked or scoped chat keeps\n // its conversation across a switch), else the surface's for a new conversation. Named on the\n // request, so neither a switch nor the session's open workspace can move it.\n const turnWorkspaceId =\n target?.workspaceId ?? conversationWorkspaceOf(queryClient, convId) ?? selectedWorkspaceId\n const agentChat = state.newAgentChat\n // An existing conversation's agent: as recorded, else its creator when that is one of the\n // person's own agents (an agent chat runs under the agent's session; the record does not\n // survive a sign-out, or another device).\n let agentExtId = convId\n ? state.agentChatOwners[convId]\n : agentChat?.workspaceId === turnWorkspaceId\n ? agentChat.agentId\n : undefined\n // Only a conversation an agent created is looked up (an ordinary send never waits on it).\n if (convId && !agentExtId && creatorOf(queryClient, convId)?.startsWith('agt-')) {\n agentExtId = await agentChatOwnerOf(queryClient, () => arbi.agents.list(), convId)\n if (agentExtId) session.setAgentChatOwner(convId, agentExtId)\n }\n const queuedContinuation = options?.queuedContinuation === true\n // First message of a brand-new conversation bumps the workspace's\n // conversation_count \u2014 used to tick the \"Ask ARBI\" onboarding step.\n const isNewConversation = !convId\n\n const documentIds = (state.conversationDocuments[convKey] || []).map((doc) => doc.docId)\n // The chat's facts ride every message of it (snapshot semantics, like its documents).\n const factIds = [...(state.conversationFacts[convKey] || [])]\n\n const newConfigId = state.chatModel\n\n // Frontend is the single source of truth for which config a request\n // uses (Option B): send the same config the model selector resolves &\n // displays (see useConversationSettings) \u2014 explicit per-conversation\n // choice (`newConfigId`) wins, otherwise the user's persisted\n // `last_config`, so a selected config (e.g. one with ARTIFACTS_ENABLED)\n // actually drives the chat instead of being silently dropped to an\n // empty config. `newConfigId` alone is insufficient \u2014 it is transient\n // and cleared on every new conversation. Read `last_config` from the\n // shared settings cache by partial key match: openapi-react-query\n // suffixes the query key with request options and the settings hook is\n // token-gated, so the hook value can momentarily be undefined here even\n // though the data is cached app-wide.\n const cachedSettings = queryClient\n .getQueriesData<{ last_config?: string }>({ queryKey: arbiQueryKeys.userSettings() })\n .map(([, data]) => data)\n .find((data) => data?.last_config)\n const lastConfigId = cachedSettings?.last_config\n const resolvedConfigId = newConfigId || (agentExtId ? undefined : lastConfigId)\n\n // PA mode delegates the turn to the personal agent (`buildTurnRequest` prefixes `/pa ` so\n // the backend routes it straight to the delegation stream).\n const isPaMode = !agentExtId && state.paMode\n\n // Drafting mode: the thread's own document is named (a reference, never its content): the one\n // the thread is bound to (started from it, or attached in it), else one made in this thread\n // and open. A document merely open beside a chat is not: a new chat starts unbound. While an\n // earlier version of it is on screen the viewer returns to the live document. For a Word\n // document the server binds the live document: the assistant reads what it needs on demand\n // and edits it as tracked changes. For an html/react artifact it gets the source and a\n // targeted edit tool. PA-mode turns are delegated to a separate agent and never touch\n // artifacts.\n const threadDocumentId = boundDocumentOf(queryClient, { ...state, conversationId: convId })\n const activeArtifactExtId = isPaMode\n ? null\n : takeDocumentForTurn({\n boundDocumentId: threadDocumentId,\n conversationId: convId,\n workspaceId: turnWorkspaceId,\n })\n\n // A workflow launch can leave structured tools for its one send; consume them here.\n const pendingTools = state.pendingTools\n if (pendingTools) session.setPendingTools(null)\n const toolsOverride = options?.toolsOverride ?? pendingTools ?? undefined\n\n const request = {\n question: content,\n workspaceId: turnWorkspaceId!,\n docIds: documentIds,\n pinnedDocumentId: state.pinnedDocumentId,\n factIds,\n tools: toolsOverride,\n personalAgent: isPaMode,\n activeArtifactExtId,\n // A new chat about a document is bound by its first turn, on the server (every device).\n ...(!convId && threadDocumentId ? { bindDocumentId: threadDocumentId } : {}),\n previousResponseId: parentId,\n agentId: agentExtId,\n model: resolvedConfigId,\n mentions: route.mentions,\n // A brand-new conversation starts in the mode picked in the composer.\n ...(!convId && route.assistantMode === 'mentioned'\n ? { assistantMode: 'mentioned' as const }\n : {}),\n }\n\n const abortController = new AbortController()\n if (!queuedContinuation) {\n session.setConversationGenerating(convKey, true, abortController)\n }\n\n // Optimistically mark this conversation in-progress the instant we start a\n // turn. Without this, Conversation's \"reconcile stale isGenerating\" effect\n // runs on the setConversationGenerating(true) above, sees the conversation\n // list's last_message_status still 'completed' from the PREVIOUS turn (the\n // stream_start patch below only lands ~50ms later), and immediately clears\n // isGenerating \u2014 which race-hid the live status bar on every follow-up\n // turn (first turns were exempt: the new conversation isn't in the list\n // yet). Patching here closes that window. No-op for a brand-new\n // conversation (convKey === 'new' isn't in the list).\n if (!queuedContinuation) {\n patchConversation(queryClient, convKey, { last_message_status: 'in_progress' })\n }\n\n // Mutable stream state \u2014 shared by all SSE handlers via closure\n const streamState: StreamState = {\n conversationId: convKey,\n workspaceId: turnWorkspaceId,\n assistantId: null,\n // 0 = nothing narrated yet, so nothing is owed a reading window.\n lastNarrationSettledAt: 0,\n // True once the model has pivoted to a tool call and not yet resumed\n // prose. Prose arriving while this is set opens a NEW narration, so the\n // one before it closes: paragraph break, and everything above becomes\n // superseded. Item boundaries alone cannot say this \u2014 the backend splits\n // a single sentence across several items.\n pivotedSinceProse: false,\n // Steering that landed too late to steer this turn; resubmitted as a new\n // turn once this one completes (see after the ask below).\n deferredInterjections: [],\n queuedContinuation,\n queuedUserMessageId: null,\n hasStreamStarted: false,\n queuedFailed: false,\n abortController,\n isStopped: () => stoppedStreamRef.current === abortController,\n queueOwnerAssistantId: options?.queueOwnerAssistantId ?? null,\n queuedClientId: options?.queuedClientId ?? null,\n // Set when a create_fact / label_facts tool runs this turn, so the Facts\n // table + label vocabulary refresh on completion (see handleComplete).\n factsTouched: false,\n threadDocumentId,\n // Set by the handlers: the server stored the question / reported its own failure.\n userMessageId: null,\n serverError: null,\n }\n\n // A follow-up sent for another conversation than the one on screen places itself under its own\n // parent, not under the screen's cursor.\n const handlers = createHandlers({\n streamState,\n parentMessageId: target ? (parentId ?? null) : parentMessageId,\n parentId,\n })\n // The server's word on the turn's workspace binds the conversation to it.\n const confirmTurnWorkspace = () => {\n if (streamState.workspaceId)\n confirmConversationWorkspace(\n queryClient,\n streamState.conversationId,\n streamState.workspaceId\n )\n }\n const onUserMessageInWorkspace = handlers.onUserMessage\n handlers.onUserMessage = (message) => {\n onUserMessageInWorkspace(message)\n confirmTurnWorkspace()\n }\n if (agentExtId) {\n const onUserMessage = handlers.onUserMessage\n handlers.onUserMessage = (message) => {\n if (message.conversation_ext_id) {\n session.setAgentChatOwner(message.conversation_ext_id, agentExtId)\n queryClient.invalidateQueries({ queryKey: arbiQueryKeys.agentChats(agentExtId) })\n }\n onUserMessage(message)\n }\n }\n\n const ownsActiveGeneration = () => {\n const activeGeneration = session.getState().generating[streamState.conversationId]\n return (\n activeGeneration?.abortController === abortController ||\n (!!streamState.assistantId &&\n activeGeneration?.streamingAssistantId === streamState.assistantId)\n )\n }\n\n try {\n performance.mark('arbi:send-message:start')\n await arbi.assistant.ask(request, {\n signal: abortController.signal,\n onMessageQueued: handlers.onMessageQueued,\n onUserMessage: handlers.onUserMessage,\n onStreamStart: handlers.onStreamStart,\n onToken: handlers.onToken,\n onMetadata: handlers.onMetadata,\n onError: handlers.onError,\n onComplete: handlers.onComplete,\n // `response.output_item.added` is deliberately NOT consumed: an item\n // opening told us only that SOME block had started, which is exactly the\n // guess this design gave up. Text arrives via onOutputTextDelta and\n // settles via onOutputItemDone.\n onOutputItemDone: handlers.onOutputItemDone,\n onOutputTextDelta: handlers.onOutputTextDelta,\n onUserInputRequest: handlers.onUserInputRequest,\n onProbeAnswer: handlers.onProbeAnswer,\n onDeferredInterjection: handlers.onDeferredInterjection,\n onAgentControlState: handlers.onAgentControlState,\n onArtifact: handlers.onArtifact,\n onMailDraft: handlers.onMailDraft,\n onElapsedTime: handlers.onElapsedTime,\n onContextUpdate: handlers.onContextUpdate,\n onQuotaExceeded: (quota) => chatHost?.onQuotaExceeded?.(quota),\n onWorkspace: (workspaceId) => {\n streamState.workspaceId = workspaceId\n confirmTurnWorkspace()\n },\n })\n // The stream is over: commit the text still waiting for a frame now, while the message is\n // the streamed one. Left buffered, it lands after the thread is re-read and is appended\n // to the answer the server stored.\n flushStreamingTokens(queryClient)\n\n // A steering response has no assistant stream of its own. The backend\n // accepted the queued user message into the active tool loop. Keep it\n // visibly queued until the parent stream emits its interjection step at\n // the actual tool boundary; that event settles the cache entry.\n if (queuedContinuation && !streamState.hasStreamStarted) {\n if (streamState.queuedFailed) failQueuedContinuation(queryClient, session, streamState)\n return\n }\n\n // The server refused the turn before storing anything (a parent that is gone, a model\n // that is down): say the send failed and offer Retry with the question, instead of letting\n // it vanish with nothing in the thread. Re-read the thread in case the server did store it.\n if (\n streamState.serverError &&\n !streamState.userMessageId &&\n !streamState.assistantId &&\n !queuedContinuation\n ) {\n if (ownsActiveGeneration()) {\n session.setConversationError(\n streamState.conversationId,\n streamState.serverError,\n content\n )\n void queryClient.invalidateQueries({\n queryKey: getThreadsQueryKey(streamState.conversationId),\n })\n // Undo the optimistic 'in_progress' set when the send started.\n void invalidateConversationLists(queryClient)\n }\n return\n }\n\n const ownsGeneration = ownsActiveGeneration()\n if (ownsGeneration) {\n session.setConversationGenerating(streamState.conversationId, false, null)\n }\n\n // The user's own generation just consumed AI usage \u2014 refresh the\n // \"\u00A3 used today\" indicator now instead of polling for it.\n queryClient.invalidateQueries({ queryKey: arbiQueryKeys.userUsageToday() })\n\n // Steering that reached the agent too late to steer this turn was\n // deferred by the backend rather than allowed to derail the answer.\n // Now that the turn is done, submit it as a new turn \u2014 threaded off the\n // assistant message that just finished, exactly as if the user had sent\n // it a moment later. Sent as one turn so a burst reads as a single\n // follow-up. Fire-and-forget via the ref so we don't block this turn's\n // completion bookkeeping (and to sidestep the self-reference).\n if (streamState.deferredInterjections.length > 0) {\n const followUp = streamState.deferredInterjections.join('\\n\\n')\n const threadParent = streamState.assistantId\n if (threadParent) {\n session.clearQueuedMessages(threadParent)\n }\n // In the conversation and workspace of the turn it steered, captured now: the view (or\n // the surface's workspace) may have moved on while the turn ran.\n void sendMessageRef.current?.(\n followUp,\n threadParent,\n streamState.workspaceId && streamState.conversationId !== 'new'\n ? {\n target: {\n workspaceId: streamState.workspaceId,\n conversationId: streamState.conversationId,\n },\n }\n : undefined\n )\n }\n\n // The conversations list cache is patched to 'in_progress' on stream\n // start (see the stream handlers). Patch it back to 'completed' here\n // so the Status column / sidebar reflect reality without a refetch.\n if (ownsGeneration) {\n const lastStatus = streamState.serverError ? 'failed' : 'completed'\n patchConversation(queryClient, streamState.conversationId, {\n last_message_status: lastStatus,\n })\n }\n\n // A new conversation changes the workspace's conversation_count \u2014 refresh\n // the workspaces list so onboarding's \"Ask ARBI\" step ticks reactively.\n if (isNewConversation && ownsGeneration) invalidateUserWorkspaces()\n\n if (ownsGeneration) {\n clearLoadingState(queryClient, streamState.conversationId)\n }\n } catch (error) {\n flushStreamingTokens(queryClient)\n const isAbort = error instanceof Error && error.name === 'AbortError'\n // The server stored the turn and only the connection dropped: the run is not over.\n const isDroppedConnection = !streamState.serverError && !isAbort && !streamState.isStopped()\n if (queuedContinuation && !streamState.hasStreamStarted) {\n // The server holds this follow-up in its queue already, and runs it in turn.\n if (isDroppedConnection && streamState.queuedUserMessageId) {\n if (streamState.queuedClientId) {\n session.removeQueuedMessage(streamState.queuedClientId)\n }\n void queryClient.invalidateQueries({\n queryKey: getThreadsQueryKey(streamState.conversationId),\n })\n return\n }\n failQueuedContinuation(queryClient, session, streamState)\n return\n }\n\n if (streamState.isStopped() || isAbort) {\n console.debug('Message sending was aborted by user')\n // The stopped stream leaves its assistant message loading with no stream behind it, and\n // a queued follow-up is not the owner so nothing else clears it. Clear it and re-read the\n // thread: a follow-up the server still runs is attached again, a stopped one shows its end.\n clearLoadingState(queryClient, streamState.conversationId)\n void queryClient.invalidateQueries({\n queryKey: getThreadsQueryKey(streamState.conversationId),\n })\n if (ownsActiveGeneration()) {\n session.setConversationGenerating(streamState.conversationId, false, null)\n patchConversation(queryClient, streamState.conversationId, {\n last_message_status: 'completed',\n })\n }\n return\n }\n\n console.error('Message sending failed:', error)\n if (!ownsActiveGeneration()) return\n\n // A dropped connection after the server stored the question does not end the turn: the\n // run goes on there. Failing it here offered Retry, which deleted the question under the\n // live answer (its answer then landed in the thread with no question) and, if the resend\n // failed too, left the view on the deleted turn. Re-read the thread instead: a running\n // answer is attached again, a failed one shows Retry, a finished one shows its answer.\n // Generation is held until the thread is re-read (or a few seconds pass, offline), so no\n // \"Response Incomplete\" alert offers to delete the question meanwhile.\n if (isDroppedConnection && streamState.userMessageId) {\n let timer: ReturnType<typeof setTimeout> | undefined\n await Promise.race([\n queryClient.invalidateQueries({\n queryKey: getThreadsQueryKey(streamState.conversationId),\n }),\n new Promise((resolve) => {\n timer = setTimeout(resolve, RECONCILE_TIMEOUT_MS)\n }),\n ])\n clearTimeout(timer)\n clearLoadingState(queryClient, streamState.conversationId)\n if (ownsActiveGeneration()) {\n session.setConversationGenerating(streamState.conversationId, false, null)\n }\n void invalidateConversationLists(queryClient)\n return\n }\n\n patchConversation(queryClient, streamState.conversationId, {\n last_message_status: 'failed',\n })\n\n // Open the upgrade modal if either (a) the error string carries\n // LiteLLM's ExceededBudget payload directly, or (b) the project's\n // cached AI usage is used up. Either path beats showing only\n // the conversation's generic error.\n const quota =\n quotaFromStreamError(error) ??\n cachedQuotaUsedUp(queryClient, session.getState().workspaceId)\n if (quota) chatHost?.onQuotaExceeded?.(quota)\n\n session.setConversationError(\n streamState.conversationId,\n error instanceof Error ? error.message : String(error),\n content\n )\n }\n },\n [\n selectedWorkspaceId,\n parentMessageId,\n queryClient,\n session,\n createHandlers,\n chatHost,\n arbi,\n invalidateUserWorkspaces,\n routeMessage,\n postComment,\n ]\n )\n\n // Keep the ref pointing at the latest sendMessage so the deferred-interjection\n // resubmit inside sendMessage always calls the current closure.\n sendMessageRef.current = sendMessage\n\n const handleCreateBranch = useCallback(\n async (parentMessageExtId: string | null, newContent: string) => {\n if (!newContent.trim() || !selectedWorkspaceId) return\n await sendMessage(newContent, parentMessageExtId, { branch: true })\n },\n [selectedWorkspaceId, sendMessage]\n )\n\n const handleSendMessage = useCallback(\n async (options?: SendMessageOptions) => {\n const currentMessage = selectDraft(session.getState()).trim()\n // The draft a shared thread starts with (`@<assistant> `) is not a question yet.\n if (!selectedWorkspaceId || !hasMessageBody(currentMessage, assistantLabels(assistantName)))\n return\n\n // A send consumes the suggested follow-up, whether or not it was used \u2014\n // the next turn's metadata brings a fresh one.\n session.setSuggestedQuery(null)\n\n // Chat runs against the server-side AI \u2014 there's nothing to queue offline.\n // Don't fire a mutation that would just pause and hang; tell the user.\n if (refuseChatWhenOffline()) return\n\n // While a turn is active, only true out-of-band controls use /interject.\n // Ordinary text still goes through the typed query endpoint, whose\n // arbi.message_queued event persists and renders it before the response gate\n // opens. The backend then either steers it into the next tool boundary or\n // starts a normal follow-up turn after the current final answer completes.\n if (isGenerating) {\n const convKey = session.getState().conversationId || 'new'\n const genState = session.getState().generating[convKey]\n const streamingAssistantId = genState?.streamingAssistantId\n\n // In a thread, a message that does not @mention the assistant is a comment between\n // people: post it straight away instead of queueing it into the running turn.\n const route = routeMessage(currentMessage)\n if (route.isComment && streamingAssistantId) {\n session.setDraft(draftAfterSend(queryClient, session, route.isComment, assistantName))\n await postComment(currentMessage, streamingAssistantId, route.mentions)\n return\n }\n\n if (streamingAssistantId) {\n const command = currentMessage.toLowerCase()\n const isPause = command === AGENT_CONTROL_COMMANDS.pause\n const isResume = command === AGENT_CONTROL_COMMANDS.resume\n const isReport =\n command === AGENT_CONTROL_COMMANDS.report ||\n command.startsWith(`${AGENT_CONTROL_COMMANDS.report} `)\n\n session.setDraft(draftAfterSend(queryClient, session, route.isComment, assistantName))\n\n if (isPause) {\n session.setAgentControlState(streamingAssistantId, 'pausing')\n } else if (isResume) {\n session.setAgentControlState(streamingAssistantId, 'resuming')\n } else if (isReport) {\n session.setAgentProbePending(streamingAssistantId, true)\n }\n\n if (isPause || isResume || isReport) {\n await arbi.assistant.interject(streamingAssistantId, currentMessage)\n } else {\n const queuedClientId = `queued-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`\n session.addQueuedMessage(streamingAssistantId, {\n clientId: queuedClientId,\n content: currentMessage,\n serverId: null,\n acknowledged: false,\n createdAt: Date.now(),\n })\n await sendMessage(currentMessage, streamingAssistantId, {\n ...options,\n queuedContinuation: true,\n queueOwnerAssistantId: streamingAssistantId,\n queuedClientId,\n })\n }\n return\n }\n // Keep the draft intact during the brief pre-stream window.\n return\n }\n\n const parentId = session.getState().cursor\n\n session.setDraft(\n draftAfterSend(queryClient, session, routeMessage(currentMessage).isComment, assistantName)\n )\n\n await sendMessage(currentMessage, parentId, options)\n },\n [\n selectedWorkspaceId,\n isGenerating,\n session,\n sendMessage,\n arbi,\n routeMessage,\n postComment,\n queryClient,\n assistantName,\n ]\n )\n\n const handleAbortGeneration = useCallback(async () => {\n // A second press (a double-click) must not stop the run again: it would cancel twice, and\n // once the local abort has landed it would clear the stopped-stream marker, so the stream's\n // error would read as a failed answer.\n const stream = session.getState().generating[conversationKey]\n if (stopInFlightRef.current || stream?.isGenerating === false) return\n // A tap right after a Stop must not stop the follow-up the queue started in the meantime.\n if (Date.now() - lastStopAtRef.current < STOP_REPEAT_WINDOW_MS) return\n lastStopAtRef.current = Date.now()\n stopInFlightRef.current = true\n try {\n // Stop the run on the server first; closing the stream alone leaves it running and\n // its answer would arrive later.\n const controller = stream?.abortController ?? null\n stoppedStreamRef.current = controller\n let streamKey = conversationKey\n let responseId = stream?.streamingAssistantId ?? null\n // Pressed before the stream reported its id: the run already exists on the server, so wait\n // for the id instead of only closing the stream. Further presses meanwhile do nothing.\n if (!responseId && controller) {\n const reported = await waitForResponseId(session, controller)\n if (reported) {\n streamKey = reported.conversationKey\n responseId = reported.responseId\n }\n }\n // In a sub-agent's branch this is the run that delegated it.\n const threads =\n queryClient.getQueryData<ThreadsResponse>(getThreadsQueryKey(streamKey))?.threads ?? []\n const runId = responseId ? getRunToCancel(threads, responseId) : null\n if (runId) {\n try {\n await arbi.assistant.cancel(runId)\n } catch {\n // Still stop here (the run may already have ended); say the server may keep going.\n notify.warning(\n \"Stopped here, but the server didn't confirm: the answer may still appear later.\"\n )\n }\n }\n // The cancel is awaited, and the server starts the next queued follow-up as soon as it lands:\n // that follow-up may already own this conversation's generation state and attach. Only close\n // the stream this Stop belongs to, or the next answer goes silent (\"Working\", no stop square).\n const current = session.getState().generating[streamKey]\n const isStoppedStream = controller\n ? current?.abortController === controller\n : !current?.streamingAssistantId || current.streamingAssistantId === responseId\n if (isStoppedStream) session.abortConversationGeneration(streamKey)\n // A reattached run has no local controller: close its stream too, or it keeps painting. The\n // attach follows this conversation's streaming message (in a sub-agent branch, the branch's\n // reply), not the delegating run that was cancelled, so match it by that id.\n if (responseId) disconnectAttachedRun(streamKey, responseId)\n } finally {\n stopInFlightRef.current = false\n }\n }, [session, arbi, conversationKey, queryClient])\n\n const handleRetryOrphanedMessage = useCallback(\n async (messageContent: string, parentId: string | null, messageExtId: string) => {\n // One retry at a time: the alert's Retry and the composer's retry (or a double click) must\n // not delete and resend twice. Released however the retry ends.\n if (retryInFlightRef.current) return\n // Offline the delete would be paused and the resend refused, leaving neither done.\n if (refuseChatWhenOffline()) return\n retryInFlightRef.current = true\n try {\n // A failed parent (a follow-up the restart sweep failed behind a failed answer) is\n // refused by the server, so the resend goes to the nearest message above it instead.\n const replyTo = currentConversationId\n ? resolveReplyTarget(queryClient, currentConversationId, parentId)\n : parentId\n if (!replyTo && currentConversationId) {\n try {\n await arbi.conversations.delete(currentConversationId)\n removeConversationFromLists(queryClient, currentConversationId)\n session.startNewConversation()\n } catch (error) {\n console.error('Failed to delete conversation with orphaned first message:', error)\n }\n } else if (replyTo) {\n try {\n await arbi.conversations.deleteMessage(messageExtId)\n // The message and its replies are gone from whichever thread held them.\n void queryClient.invalidateQueries(allConversationThreads)\n } catch (error) {\n console.error('Failed to delete orphaned message:', error)\n }\n }\n\n await sendMessage(messageContent, replyTo)\n } finally {\n retryInFlightRef.current = false\n }\n },\n [currentConversationId, arbi, session, sendMessage, queryClient]\n )\n\n const handleRetrySend = useCallback(async () => {\n if (isGenerating || !lastFailedMessage || retryInFlightRef.current) return\n if (refuseChatWhenOffline()) return\n retryInFlightRef.current = true\n try {\n session.clearConversationError(conversationKey)\n // sendMessage replies to the nearest message the server accepts: the view may still point at\n // a turn a Retry deleted, or at a failed answer.\n const parentId = session.getState().cursor\n await sendMessage(lastFailedMessage, parentId)\n } finally {\n retryInFlightRef.current = false\n }\n }, [isGenerating, lastFailedMessage, session, conversationKey, sendMessage])\n\n const clearErrorState = useCallback(() => {\n session.clearConversationError(conversationKey)\n }, [session, conversationKey])\n\n // Clear error state when starting a new conversation\n useEffect(() => {\n if (currentConversationId === null) {\n session.clearConversationError('new')\n }\n }, [currentConversationId, session])\n\n return {\n handleCreateBranch,\n handleSendMessage,\n handleAbortGeneration,\n handleRetrySend,\n handleRetryOrphanedMessage,\n clearErrorState,\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * The chat engine's actions on a conversation: send (a turn, or a comment to the people in a\n * thread), branch, retry, stop, and the run-control commands typed while a turn runs. A turn goes\n * through `arbi.assistant.ask`, its frames through the stream handlers, and its state lives in the\n * session (`ArbiSessionState`) and the conversation cache.\n */\n\nimport { flushDocumentForTurn } from './turn-flush'\nimport { pendingChatModelSave } from '../config/config-hooks'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { useQueryClient, type QueryClient } from '@tanstack/react-query'\nimport {\n ArbiApiError,\n STOP_REPEAT_WINDOW_MS,\n getRunToCancel,\n quotaFromStreamError,\n type ConversationResponse as Conversation,\n type ThreadsResponse,\n} from '@arbidocs/sdk/browser'\nimport type { components } from '@arbidocs/client'\nimport { notify } from '../shell'\nimport { arbiQueryKeys } from '../hooks'\nimport { useArbi } from '../useArbi'\nimport { useArbiHost } from '../host/config'\nimport { selectDraft, useArbiSessionStore } from '../session'\nimport type { ArbiSessionStore } from '../session/store'\nimport {\n invalidateConversationLists,\n isConversationShared,\n patchConversation,\n patchConversationList,\n removeConversationFromLists,\n} from '../conversation-list'\nimport {\n allConversationThreads,\n clearLoadingState,\n flushStreamingTokens,\n getThreadsQueryKey,\n updateUserMessageStateInCache,\n} from './conversation-cache'\nimport {\n assistantLabels,\n cachedAssistantMode,\n cachedMentionCandidates,\n isCommentToPeople,\n} from './conversation-mentions'\nimport { useAssistantName } from '../host/assistant-copy'\nimport { hasMessageBody, toMessageMentions, extractMentions } from './mentions'\nimport { takeDocumentForTurn } from './open-document'\nimport { agentChatOwnerOf, creatorOf } from './agent-chat-owner'\nimport { bindThreadDocument, boundDocumentOf } from './thread-binding'\nimport { resolveReplyTarget } from './reply-target'\nimport {\n confirmConversationWorkspace,\n conversationWorkspaceOf,\n watchConversationWorkspaces,\n} from './conversation-workspace'\nimport { useStreamHandlers, type StreamState } from './stream-handlers'\nimport {\n AGENT_CONTROL_COMMANDS,\n cachedQuotaUsedUp,\n disconnectAttachedRun,\n refuseChatWhenOffline,\n waitForResponseId,\n} from './turn-control'\n\ntype MessageInput = components['schemas']['MessageInput']\ntype MessageMention = components['schemas']['MessageMention']\n\n/** How long a dropped turn holds its generating state while the thread is re-read. */\nconst RECONCILE_TIMEOUT_MS = 5000\n\nexport interface SendMessageOptions {\n /** An explicit edit/fork, rather than appending a human message to the shared thread. */\n branch?: boolean\n queuedContinuation?: boolean\n queueOwnerAssistantId?: string\n queuedClientId?: string\n /**\n * Bypasses the normal single-pinned-document tools computation (the chat's documents and its\n * pin, see `buildTurnRequest`) for this one send.\n * The app's general \"pin a document\" feature is deliberately one-at-a-time\n * (`selectFullContextDocument`) \u2014 a pinned TEXT doc's full content goes\n * straight into the AGENT's chat prompt, so more than one at a time risks\n * blowing that context window. Generate image is a different resource\n * entirely: reference images never touch the agent's chat context \u2014 they go\n * to the separate ImageGen model in its own request \u2014 so multiple\n * references (e.g. \"combine these two logos\") don't compound against that\n * budget, even though they still cost real tokens against ImageGen's own\n * call. This lets that caller build its own tools object with every\n * selected reference doc instead of touching the single global pin.\n */\n toolsOverride?: MessageInput['tools']\n /**\n * Send in this conversation and workspace rather than the open ones: a follow-up captured when\n * its turn started (a deferred interjection), sent after the view may have moved on.\n */\n target?: TurnTarget\n}\n\n/** The conversation and workspace a turn belongs to, captured when it starts. */\nexport interface TurnTarget {\n workspaceId: string\n conversationId: string\n}\n\n/**\n * The draft a send leaves behind. A shared chat addresses ARBI again, except after a comment to\n * people: then the box is left empty, which a shared chat reads as a comment to the thread.\n */\nfunction draftAfterSend(\n queryClient: QueryClient,\n session: ArbiSessionStore,\n wasComment: boolean,\n assistantName: string\n): string {\n const shared = isConversationShared(queryClient, session.getState().conversationId)\n return shared && !wasComment ? `@${assistantName} ` : ''\n}\n\n/**\n * A queued follow-up the backend refused before its turn started: mark its bubble failed and\n * take it out of the queue, so it does not sit there as if it were still waiting.\n */\nfunction failQueuedContinuation(\n queryClient: QueryClient,\n session: ArbiSessionStore,\n streamState: {\n conversationId: string\n queuedUserMessageId: string | null\n queueOwnerAssistantId: string | null\n queuedClientId: string | null\n }\n): void {\n if (streamState.queuedUserMessageId) {\n updateUserMessageStateInCache(\n queryClient,\n streamState.conversationId,\n streamState.queuedUserMessageId,\n 'failed',\n false\n )\n }\n if (streamState.queuedClientId) {\n session.removeQueuedMessage(streamState.queuedClientId)\n }\n notify.error('Queued message failed to send')\n}\n\n/** The signed-in user's id, or null before the session is known. */\nfunction signedInUserId(arbi: { user: { identity: () => { externalId: string } | null } }) {\n try {\n return arbi.user.identity()?.externalId ?? null\n } catch {\n return null\n }\n}\n\n/** What the actions act on: the open workspace and conversation, as the session holds them. */\nexport interface ConversationActionsInput {\n selectedWorkspaceId: string | null\n /** The cursor when the view rendered (the stream handlers place a branch against it). */\n parentMessageId: string | null\n /** The open conversation's key (`new` for a new chat). */\n conversationKey: string\n isGenerating: boolean\n lastFailedMessage: string | null\n}\n\n/** The actions on the open conversation. */\nexport function useConversationActions({\n selectedWorkspaceId,\n parentMessageId,\n conversationKey,\n isGenerating,\n lastFailedMessage,\n}: ConversationActionsInput) {\n const queryClient = useQueryClient()\n const session = useArbiSessionStore()\n const currentConversationId = conversationKey === 'new' ? null : conversationKey\n const invalidateUserWorkspaces = useCallback(\n () => void queryClient.invalidateQueries({ queryKey: arbiQueryKeys.workspaces() }),\n [queryClient]\n )\n\n const arbi = useArbi()\n // Each conversation stays in the workspace it was opened in, though the surface switches.\n useState(() => watchConversationWorkspaces(queryClient, session))\n const createHandlers = useStreamHandlers()\n const chatHost = useArbiHost().chat\n const assistantName = useAssistantName()\n const mentionCandidates = useCallback(\n () =>\n cachedMentionCandidates(\n queryClient,\n session.getState().workspaceId,\n signedInUserId(arbi),\n assistantName\n ),\n [queryClient, session, arbi, assistantName]\n )\n // The local stream a Stop is closing. Stop cancels the run on the server before it aborts the\n // stream, so the connection can drop first with a network error that is not an abort.\n const stoppedStreamRef = useRef<AbortController | null>(null)\n // True while a Stop's server cancel is in flight, so a second press does not cancel again.\n const stopInFlightRef = useRef(false)\n // When the last Stop was pressed. A press within STOP_REPEAT_WINDOW_MS of it is a double tap.\n const lastStopAtRef = useRef(0)\n // True while a Retry deletes and resends, so a second press does not do it again.\n const retryInFlightRef = useRef(false)\n // Lets sendMessage resubmit a deferred interjection as a new turn without a\n // self-reference (it is a useCallback that would otherwise close over a stale\n // or not-yet-defined copy of itself). Kept current just below its definition.\n const sendMessageRef = useRef<\n | ((content: string, parentId?: string | null, options?: SendMessageOptions) => Promise<void>)\n | null\n >(null)\n\n /**\n * Shared posts, and posts in a `mentioned` thread, run ARBI only when addressed; posts in an\n * ordinary solo chat address it implicitly.\n */\n const routeMessage = useCallback(\n (content: string, conversationId?: string | null) => {\n const state = session.getState()\n const convId = conversationId === undefined ? state.conversationId : conversationId\n const { newConversationAssistantMode } = state\n const candidates = mentionCandidates()\n const mentioned = extractMentions(content, candidates)\n const assistantMode = cachedAssistantMode(queryClient, convId, newConversationAssistantMode)\n const isComment = isCommentToPeople(queryClient, convId, content, candidates)\n const mentions = toMessageMentions(mentioned) as MessageMention[]\n // A message not taken as a comment addresses ARBI. In an existing `mentioned` thread that is\n // only one that @mentions it; what is left is the first message of a thread not created\n // yet, whose turn creates it in `mentioned` mode.\n if (\n !isComment &&\n assistantMode === 'mentioned' &&\n !mentions.some((m) => m.type === 'assistant')\n ) {\n mentions.push({ type: 'assistant' })\n }\n return {\n mentions,\n assistantMode,\n isComment,\n }\n },\n [queryClient, session, mentionCandidates]\n )\n\n /** Post a comment (no assistant run) and land the thread on it. */\n const postComment = useCallback(\n async (\n content: string,\n parentId: string | null,\n mentions: MessageMention[],\n branch = false\n ) => {\n const convId = session.getState().conversationId\n try {\n const saved = await arbi.conversations.postMessage({\n content,\n mentions,\n // Only an explicit edit creates a branch. Ordinary comments append to the\n // server's latest message, so simultaneous sends stay visible together. Else\n // (nothing yet) start a thread. The mode only applies to a conversation it creates.\n ...(branch && parentId\n ? { previous_message_ext_id: parentId }\n : convId\n ? { conversation_ext_id: convId }\n : {}),\n assistant_mode: 'mentioned',\n })\n const conversationId = saved.conversation_ext_id\n const nowIso = new Date().toISOString()\n if (!convId) {\n // A comment starts the thread without a turn to carry its binding: bind it right after.\n const bindTo = session.getState().newChatDocumentId\n session.moveConversation('new', conversationId)\n if (bindTo) session.setNewChatDocumentId(null)\n patchConversationList(queryClient, session.getState().workspaceId, (old) => [\n {\n external_id: conversationId,\n title: content.slice(0, 40),\n created_by_ext_id: saved.created_by_ext_id,\n updated_by_ext_id: null,\n created_at: nowIso,\n updated_at: nowIso,\n // A mention of someone who cannot see it yet shares it server-side: refetched below.\n is_shared: false,\n message_count: 1,\n last_message_status: 'completed',\n assistant_mode: 'mentioned',\n } as Conversation,\n ...old,\n ])\n if (bindTo)\n bindThreadDocument(arbi, queryClient, conversationId, bindTo).catch(() =>\n notify.error('Could not attach the document to this thread')\n )\n invalidateUserWorkspaces()\n } else {\n patchConversation(queryClient, conversationId, (c) => ({\n ...c,\n message_count: c.message_count + 1,\n updated_at: nowIso,\n }))\n }\n // Mentioning someone shares the conversation on the server, which this cache cannot know.\n if (mentions.some((m) => m.type === 'user')) {\n await invalidateConversationLists(queryClient)\n }\n await queryClient.invalidateQueries({ queryKey: getThreadsQueryKey(conversationId) })\n session.setCursor(saved.external_id)\n } catch (error) {\n notify.error(\n (error instanceof ArbiApiError && error.detail) || 'Failed to post the message'\n )\n // Give the text back rather than lose it.\n if (!selectDraft(session.getState())) session.setDraft(content)\n }\n },\n [arbi, session, queryClient, invalidateUserWorkspaces]\n )\n\n const sendMessage = useCallback(\n async (content: string, requestedParentId?: string | null, options?: SendMessageOptions) => {\n // Never reply to a failed or deleted message: the server refuses it, so every Retry and every\n // new message would fail again. A queued follow-up names the running turn on purpose. With\n // nothing above to reply to, the parent stays: only a fresh conversation could take it.\n const target = options?.target\n const parentId =\n options?.queuedContinuation || !requestedParentId\n ? requestedParentId\n : (resolveReplyTarget(\n queryClient,\n (target ? target.conversationId : session.getState().conversationId) || 'new',\n requestedParentId\n ) ?? requestedParentId)\n const route = routeMessage(content, target?.conversationId)\n if (route.isComment && !options?.queuedContinuation && !target) {\n await postComment(content, parentId ?? null, route.mentions, options?.branch)\n return\n }\n // A model picked just now is still being saved: the turn runs on it, not on the config before.\n const modelSave = pendingChatModelSave(session)\n if (modelSave) await modelSave.catch(() => undefined)\n const state = session.getState()\n const convId = target ? target.conversationId : state.conversationId\n const convKey = convId || 'new'\n // The turn's workspace, captured now: the conversation's own (a docked or scoped chat keeps\n // its conversation across a switch), else the surface's for a new conversation. Named on the\n // request, so neither a switch nor the session's open workspace can move it.\n const turnWorkspaceId =\n target?.workspaceId ?? conversationWorkspaceOf(queryClient, convId) ?? selectedWorkspaceId\n const agentChat = state.newAgentChat\n // An existing conversation's agent: as recorded, else its creator when that is one of the\n // person's own agents (an agent chat runs under the agent's session; the record does not\n // survive a sign-out, or another device).\n let agentExtId = convId\n ? state.agentChatOwners[convId]\n : agentChat?.workspaceId === turnWorkspaceId\n ? agentChat.agentId\n : undefined\n // Only a conversation an agent created is looked up (an ordinary send never waits on it).\n if (convId && !agentExtId && creatorOf(queryClient, convId)?.startsWith('agt-')) {\n agentExtId = await agentChatOwnerOf(queryClient, () => arbi.agents.list(), convId)\n if (agentExtId) session.setAgentChatOwner(convId, agentExtId)\n }\n const queuedContinuation = options?.queuedContinuation === true\n // First message of a brand-new conversation bumps the workspace's\n // conversation_count \u2014 used to tick the \"Ask ARBI\" onboarding step.\n const isNewConversation = !convId\n\n const documentIds = (state.conversationDocuments[convKey] || []).map((doc) => doc.docId)\n // The chat's facts ride every message of it (snapshot semantics, like its documents).\n const factIds = [...(state.conversationFacts[convKey] || [])]\n\n const newConfigId = state.chatModel\n\n // Frontend is the single source of truth for which config a request\n // uses (Option B): send the same config the model selector resolves &\n // displays (see useConversationSettings) \u2014 explicit per-conversation\n // choice (`newConfigId`) wins, otherwise the user's persisted\n // `last_config`, so a selected config (e.g. one with ARTIFACTS_ENABLED)\n // actually drives the chat instead of being silently dropped to an\n // empty config. `newConfigId` alone is insufficient \u2014 it is transient\n // and cleared on every new conversation. Read `last_config` from the\n // shared settings cache by partial key match: openapi-react-query\n // suffixes the query key with request options and the settings hook is\n // token-gated, so the hook value can momentarily be undefined here even\n // though the data is cached app-wide.\n const cachedSettings = queryClient\n .getQueriesData<{ last_config?: string }>({ queryKey: arbiQueryKeys.userSettings() })\n .map(([, data]) => data)\n .find((data) => data?.last_config)\n const lastConfigId = cachedSettings?.last_config\n const resolvedConfigId = newConfigId || (agentExtId ? undefined : lastConfigId)\n\n // PA mode delegates the turn to the personal agent (`buildTurnRequest` prefixes `/pa ` so\n // the backend routes it straight to the delegation stream).\n const isPaMode = !agentExtId && state.paMode\n\n // Drafting mode: the thread's own document is named (a reference, never its content): the one\n // the thread is bound to (started from it, or attached in it), else one made in this thread\n // and open. A document merely open beside a chat is not: a new chat starts unbound. While an\n // earlier version of it is on screen the viewer returns to the live document. For a Word\n // document the server binds the live document: the assistant reads what it needs on demand\n // and edits it as tracked changes. For an html/react artifact it gets the source and a\n // targeted edit tool. PA-mode turns are delegated to a separate agent and never touch\n // artifacts.\n const threadDocumentId = boundDocumentOf(queryClient, { ...state, conversationId: convId })\n const activeArtifactExtId = isPaMode\n ? null\n : takeDocumentForTurn({\n boundDocumentId: threadDocumentId,\n conversationId: convId,\n workspaceId: turnWorkspaceId,\n })\n\n // An editor holding unsaved changes to that document saves them first: ARBI works on what\n // the person sees.\n const flushing = flushDocumentForTurn(activeArtifactExtId)\n if (flushing) await flushing\n\n // A workflow launch can leave structured tools for its one send; consume them here.\n const pendingTools = state.pendingTools\n if (pendingTools) session.setPendingTools(null)\n const toolsOverride = options?.toolsOverride ?? pendingTools ?? undefined\n\n const request = {\n question: content,\n workspaceId: turnWorkspaceId!,\n docIds: documentIds,\n pinnedDocumentId: state.pinnedDocumentId,\n factIds,\n tools: toolsOverride,\n personalAgent: isPaMode,\n activeArtifactExtId,\n // A new chat about a document is bound by its first turn, on the server (every device).\n ...(!convId && threadDocumentId ? { bindDocumentId: threadDocumentId } : {}),\n previousResponseId: parentId,\n agentId: agentExtId,\n model: resolvedConfigId,\n mentions: route.mentions,\n // A brand-new conversation starts in the mode picked in the composer.\n ...(!convId && route.assistantMode === 'mentioned'\n ? { assistantMode: 'mentioned' as const }\n : {}),\n }\n\n const abortController = new AbortController()\n if (!queuedContinuation) {\n session.setConversationGenerating(convKey, true, abortController)\n }\n\n // Optimistically mark this conversation in-progress the instant we start a\n // turn. Without this, Conversation's \"reconcile stale isGenerating\" effect\n // runs on the setConversationGenerating(true) above, sees the conversation\n // list's last_message_status still 'completed' from the PREVIOUS turn (the\n // stream_start patch below only lands ~50ms later), and immediately clears\n // isGenerating \u2014 which race-hid the live status bar on every follow-up\n // turn (first turns were exempt: the new conversation isn't in the list\n // yet). Patching here closes that window. No-op for a brand-new\n // conversation (convKey === 'new' isn't in the list).\n if (!queuedContinuation) {\n patchConversation(queryClient, convKey, { last_message_status: 'in_progress' })\n }\n\n // Mutable stream state \u2014 shared by all SSE handlers via closure\n const streamState: StreamState = {\n conversationId: convKey,\n workspaceId: turnWorkspaceId,\n assistantId: null,\n // 0 = nothing narrated yet, so nothing is owed a reading window.\n lastNarrationSettledAt: 0,\n // True once the model has pivoted to a tool call and not yet resumed\n // prose. Prose arriving while this is set opens a NEW narration, so the\n // one before it closes: paragraph break, and everything above becomes\n // superseded. Item boundaries alone cannot say this \u2014 the backend splits\n // a single sentence across several items.\n pivotedSinceProse: false,\n // Steering that landed too late to steer this turn; resubmitted as a new\n // turn once this one completes (see after the ask below).\n deferredInterjections: [],\n queuedContinuation,\n queuedUserMessageId: null,\n hasStreamStarted: false,\n queuedFailed: false,\n abortController,\n isStopped: () => stoppedStreamRef.current === abortController,\n queueOwnerAssistantId: options?.queueOwnerAssistantId ?? null,\n queuedClientId: options?.queuedClientId ?? null,\n // Set when a create_fact / label_facts tool runs this turn, so the Facts\n // table + label vocabulary refresh on completion (see handleComplete).\n factsTouched: false,\n threadDocumentId,\n // Set by the handlers: the server stored the question / reported its own failure.\n userMessageId: null,\n serverError: null,\n }\n\n // A follow-up sent for another conversation than the one on screen places itself under its own\n // parent, not under the screen's cursor.\n const handlers = createHandlers({\n streamState,\n parentMessageId: target ? (parentId ?? null) : parentMessageId,\n parentId,\n })\n // The server's word on the turn's workspace binds the conversation to it.\n const confirmTurnWorkspace = () => {\n if (streamState.workspaceId)\n confirmConversationWorkspace(\n queryClient,\n streamState.conversationId,\n streamState.workspaceId\n )\n }\n const onUserMessageInWorkspace = handlers.onUserMessage\n handlers.onUserMessage = (message) => {\n onUserMessageInWorkspace(message)\n confirmTurnWorkspace()\n }\n if (agentExtId) {\n const onUserMessage = handlers.onUserMessage\n handlers.onUserMessage = (message) => {\n if (message.conversation_ext_id) {\n session.setAgentChatOwner(message.conversation_ext_id, agentExtId)\n queryClient.invalidateQueries({ queryKey: arbiQueryKeys.agentChats(agentExtId) })\n }\n onUserMessage(message)\n }\n }\n\n const ownsActiveGeneration = () => {\n const activeGeneration = session.getState().generating[streamState.conversationId]\n return (\n activeGeneration?.abortController === abortController ||\n (!!streamState.assistantId &&\n activeGeneration?.streamingAssistantId === streamState.assistantId)\n )\n }\n\n try {\n performance.mark('arbi:send-message:start')\n await arbi.assistant.ask(request, {\n signal: abortController.signal,\n onMessageQueued: handlers.onMessageQueued,\n onUserMessage: handlers.onUserMessage,\n onStreamStart: handlers.onStreamStart,\n onToken: handlers.onToken,\n onMetadata: handlers.onMetadata,\n onError: handlers.onError,\n onComplete: handlers.onComplete,\n // `response.output_item.added` is deliberately NOT consumed: an item\n // opening told us only that SOME block had started, which is exactly the\n // guess this design gave up. Text arrives via onOutputTextDelta and\n // settles via onOutputItemDone.\n onOutputItemDone: handlers.onOutputItemDone,\n onOutputTextDelta: handlers.onOutputTextDelta,\n onUserInputRequest: handlers.onUserInputRequest,\n onProbeAnswer: handlers.onProbeAnswer,\n onDeferredInterjection: handlers.onDeferredInterjection,\n onAgentControlState: handlers.onAgentControlState,\n onArtifact: handlers.onArtifact,\n onMailDraft: handlers.onMailDraft,\n onElapsedTime: handlers.onElapsedTime,\n onContextUpdate: handlers.onContextUpdate,\n onQuotaExceeded: (quota) => chatHost?.onQuotaExceeded?.(quota),\n onWorkspace: (workspaceId) => {\n streamState.workspaceId = workspaceId\n confirmTurnWorkspace()\n },\n })\n // The stream is over: commit the text still waiting for a frame now, while the message is\n // the streamed one. Left buffered, it lands after the thread is re-read and is appended\n // to the answer the server stored.\n flushStreamingTokens(queryClient)\n\n // A steering response has no assistant stream of its own. The backend\n // accepted the queued user message into the active tool loop. Keep it\n // visibly queued until the parent stream emits its interjection step at\n // the actual tool boundary; that event settles the cache entry.\n if (queuedContinuation && !streamState.hasStreamStarted) {\n if (streamState.queuedFailed) failQueuedContinuation(queryClient, session, streamState)\n return\n }\n\n // The server refused the turn before storing anything (a parent that is gone, a model\n // that is down): say the send failed and offer Retry with the question, instead of letting\n // it vanish with nothing in the thread. Re-read the thread in case the server did store it.\n if (\n streamState.serverError &&\n !streamState.userMessageId &&\n !streamState.assistantId &&\n !queuedContinuation\n ) {\n if (ownsActiveGeneration()) {\n session.setConversationError(\n streamState.conversationId,\n streamState.serverError,\n content\n )\n void queryClient.invalidateQueries({\n queryKey: getThreadsQueryKey(streamState.conversationId),\n })\n // Undo the optimistic 'in_progress' set when the send started.\n void invalidateConversationLists(queryClient)\n }\n return\n }\n\n const ownsGeneration = ownsActiveGeneration()\n if (ownsGeneration) {\n session.setConversationGenerating(streamState.conversationId, false, null)\n }\n\n // The user's own generation just consumed AI usage \u2014 refresh the\n // \"\u00A3 used today\" indicator now instead of polling for it.\n queryClient.invalidateQueries({ queryKey: arbiQueryKeys.userUsageToday() })\n\n // Steering that reached the agent too late to steer this turn was\n // deferred by the backend rather than allowed to derail the answer.\n // Now that the turn is done, submit it as a new turn \u2014 threaded off the\n // assistant message that just finished, exactly as if the user had sent\n // it a moment later. Sent as one turn so a burst reads as a single\n // follow-up. Fire-and-forget via the ref so we don't block this turn's\n // completion bookkeeping (and to sidestep the self-reference).\n if (streamState.deferredInterjections.length > 0) {\n const followUp = streamState.deferredInterjections.join('\\n\\n')\n const threadParent = streamState.assistantId\n if (threadParent) {\n session.clearQueuedMessages(threadParent)\n }\n // In the conversation and workspace of the turn it steered, captured now: the view (or\n // the surface's workspace) may have moved on while the turn ran.\n void sendMessageRef.current?.(\n followUp,\n threadParent,\n streamState.workspaceId && streamState.conversationId !== 'new'\n ? {\n target: {\n workspaceId: streamState.workspaceId,\n conversationId: streamState.conversationId,\n },\n }\n : undefined\n )\n }\n\n // The conversations list cache is patched to 'in_progress' on stream\n // start (see the stream handlers). Patch it back to 'completed' here\n // so the Status column / sidebar reflect reality without a refetch.\n if (ownsGeneration) {\n const lastStatus = streamState.serverError ? 'failed' : 'completed'\n patchConversation(queryClient, streamState.conversationId, {\n last_message_status: lastStatus,\n })\n }\n\n // A new conversation changes the workspace's conversation_count \u2014 refresh\n // the workspaces list so onboarding's \"Ask ARBI\" step ticks reactively.\n if (isNewConversation && ownsGeneration) invalidateUserWorkspaces()\n\n if (ownsGeneration) {\n clearLoadingState(queryClient, streamState.conversationId)\n }\n } catch (error) {\n flushStreamingTokens(queryClient)\n const isAbort = error instanceof Error && error.name === 'AbortError'\n // The server stored the turn and only the connection dropped: the run is not over.\n const isDroppedConnection = !streamState.serverError && !isAbort && !streamState.isStopped()\n if (queuedContinuation && !streamState.hasStreamStarted) {\n // The server holds this follow-up in its queue already, and runs it in turn.\n if (isDroppedConnection && streamState.queuedUserMessageId) {\n if (streamState.queuedClientId) {\n session.removeQueuedMessage(streamState.queuedClientId)\n }\n void queryClient.invalidateQueries({\n queryKey: getThreadsQueryKey(streamState.conversationId),\n })\n return\n }\n failQueuedContinuation(queryClient, session, streamState)\n return\n }\n\n if (streamState.isStopped() || isAbort) {\n console.debug('Message sending was aborted by user')\n // The stopped stream leaves its assistant message loading with no stream behind it, and\n // a queued follow-up is not the owner so nothing else clears it. Clear it and re-read the\n // thread: a follow-up the server still runs is attached again, a stopped one shows its end.\n clearLoadingState(queryClient, streamState.conversationId)\n void queryClient.invalidateQueries({\n queryKey: getThreadsQueryKey(streamState.conversationId),\n })\n if (ownsActiveGeneration()) {\n session.setConversationGenerating(streamState.conversationId, false, null)\n patchConversation(queryClient, streamState.conversationId, {\n last_message_status: 'completed',\n })\n }\n return\n }\n\n console.error('Message sending failed:', error)\n if (!ownsActiveGeneration()) return\n\n // A dropped connection after the server stored the question does not end the turn: the\n // run goes on there. Failing it here offered Retry, which deleted the question under the\n // live answer (its answer then landed in the thread with no question) and, if the resend\n // failed too, left the view on the deleted turn. Re-read the thread instead: a running\n // answer is attached again, a failed one shows Retry, a finished one shows its answer.\n // Generation is held until the thread is re-read (or a few seconds pass, offline), so no\n // \"Response Incomplete\" alert offers to delete the question meanwhile.\n if (isDroppedConnection && streamState.userMessageId) {\n let timer: ReturnType<typeof setTimeout> | undefined\n await Promise.race([\n queryClient.invalidateQueries({\n queryKey: getThreadsQueryKey(streamState.conversationId),\n }),\n new Promise((resolve) => {\n timer = setTimeout(resolve, RECONCILE_TIMEOUT_MS)\n }),\n ])\n clearTimeout(timer)\n clearLoadingState(queryClient, streamState.conversationId)\n if (ownsActiveGeneration()) {\n session.setConversationGenerating(streamState.conversationId, false, null)\n }\n void invalidateConversationLists(queryClient)\n return\n }\n\n patchConversation(queryClient, streamState.conversationId, {\n last_message_status: 'failed',\n })\n\n // Open the upgrade modal if either (a) the error string carries\n // LiteLLM's ExceededBudget payload directly, or (b) the project's\n // cached AI usage is used up. Either path beats showing only\n // the conversation's generic error.\n const quota =\n quotaFromStreamError(error) ??\n cachedQuotaUsedUp(queryClient, session.getState().workspaceId)\n if (quota) chatHost?.onQuotaExceeded?.(quota)\n\n session.setConversationError(\n streamState.conversationId,\n error instanceof Error ? error.message : String(error),\n content\n )\n }\n },\n [\n selectedWorkspaceId,\n parentMessageId,\n queryClient,\n session,\n createHandlers,\n chatHost,\n arbi,\n invalidateUserWorkspaces,\n routeMessage,\n postComment,\n ]\n )\n\n // Keep the ref pointing at the latest sendMessage so the deferred-interjection\n // resubmit inside sendMessage always calls the current closure.\n sendMessageRef.current = sendMessage\n\n const handleCreateBranch = useCallback(\n async (parentMessageExtId: string | null, newContent: string) => {\n if (!newContent.trim() || !selectedWorkspaceId) return\n await sendMessage(newContent, parentMessageExtId, { branch: true })\n },\n [selectedWorkspaceId, sendMessage]\n )\n\n const handleSendMessage = useCallback(\n async (options?: SendMessageOptions) => {\n const currentMessage = selectDraft(session.getState()).trim()\n // The draft a shared thread starts with (`@<assistant> `) is not a question yet.\n if (!selectedWorkspaceId || !hasMessageBody(currentMessage, assistantLabels(assistantName)))\n return\n\n // A send consumes the suggested follow-up, whether or not it was used \u2014\n // the next turn's metadata brings a fresh one.\n session.setSuggestedQuery(null)\n\n // Chat runs against the server-side AI \u2014 there's nothing to queue offline.\n // Don't fire a mutation that would just pause and hang; tell the user.\n if (refuseChatWhenOffline()) return\n\n // While a turn is active, only true out-of-band controls use /interject.\n // Ordinary text still goes through the typed query endpoint, whose\n // arbi.message_queued event persists and renders it before the response gate\n // opens. The backend then either steers it into the next tool boundary or\n // starts a normal follow-up turn after the current final answer completes.\n if (isGenerating) {\n const convKey = session.getState().conversationId || 'new'\n const genState = session.getState().generating[convKey]\n const streamingAssistantId = genState?.streamingAssistantId\n\n // In a thread, a message that does not @mention the assistant is a comment between\n // people: post it straight away instead of queueing it into the running turn.\n const route = routeMessage(currentMessage)\n if (route.isComment && streamingAssistantId) {\n session.setDraft(draftAfterSend(queryClient, session, route.isComment, assistantName))\n await postComment(currentMessage, streamingAssistantId, route.mentions)\n return\n }\n\n if (streamingAssistantId) {\n const command = currentMessage.toLowerCase()\n const isPause = command === AGENT_CONTROL_COMMANDS.pause\n const isResume = command === AGENT_CONTROL_COMMANDS.resume\n const isReport =\n command === AGENT_CONTROL_COMMANDS.report ||\n command.startsWith(`${AGENT_CONTROL_COMMANDS.report} `)\n\n session.setDraft(draftAfterSend(queryClient, session, route.isComment, assistantName))\n\n if (isPause) {\n session.setAgentControlState(streamingAssistantId, 'pausing')\n } else if (isResume) {\n session.setAgentControlState(streamingAssistantId, 'resuming')\n } else if (isReport) {\n session.setAgentProbePending(streamingAssistantId, true)\n }\n\n if (isPause || isResume || isReport) {\n await arbi.assistant.interject(streamingAssistantId, currentMessage)\n } else {\n const queuedClientId = `queued-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`\n session.addQueuedMessage(streamingAssistantId, {\n clientId: queuedClientId,\n content: currentMessage,\n serverId: null,\n acknowledged: false,\n createdAt: Date.now(),\n })\n await sendMessage(currentMessage, streamingAssistantId, {\n ...options,\n queuedContinuation: true,\n queueOwnerAssistantId: streamingAssistantId,\n queuedClientId,\n })\n }\n return\n }\n // Keep the draft intact during the brief pre-stream window.\n return\n }\n\n const parentId = session.getState().cursor\n\n session.setDraft(\n draftAfterSend(queryClient, session, routeMessage(currentMessage).isComment, assistantName)\n )\n\n await sendMessage(currentMessage, parentId, options)\n },\n [\n selectedWorkspaceId,\n isGenerating,\n session,\n sendMessage,\n arbi,\n routeMessage,\n postComment,\n queryClient,\n assistantName,\n ]\n )\n\n const handleAbortGeneration = useCallback(async () => {\n // A second press (a double-click) must not stop the run again: it would cancel twice, and\n // once the local abort has landed it would clear the stopped-stream marker, so the stream's\n // error would read as a failed answer.\n const stream = session.getState().generating[conversationKey]\n if (stopInFlightRef.current || stream?.isGenerating === false) return\n // A tap right after a Stop must not stop the follow-up the queue started in the meantime.\n if (Date.now() - lastStopAtRef.current < STOP_REPEAT_WINDOW_MS) return\n lastStopAtRef.current = Date.now()\n stopInFlightRef.current = true\n try {\n // Stop the run on the server first; closing the stream alone leaves it running and\n // its answer would arrive later.\n const controller = stream?.abortController ?? null\n stoppedStreamRef.current = controller\n let streamKey = conversationKey\n let responseId = stream?.streamingAssistantId ?? null\n // Pressed before the stream reported its id: the run already exists on the server, so wait\n // for the id instead of only closing the stream. Further presses meanwhile do nothing.\n if (!responseId && controller) {\n const reported = await waitForResponseId(session, controller)\n if (reported) {\n streamKey = reported.conversationKey\n responseId = reported.responseId\n }\n }\n // In a sub-agent's branch this is the run that delegated it.\n const threads =\n queryClient.getQueryData<ThreadsResponse>(getThreadsQueryKey(streamKey))?.threads ?? []\n const runId = responseId ? getRunToCancel(threads, responseId) : null\n if (runId) {\n try {\n await arbi.assistant.cancel(runId)\n } catch {\n // Still stop here (the run may already have ended); say the server may keep going.\n notify.warning(\n \"Stopped here, but the server didn't confirm: the answer may still appear later.\"\n )\n }\n }\n // The cancel is awaited, and the server starts the next queued follow-up as soon as it lands:\n // that follow-up may already own this conversation's generation state and attach. Only close\n // the stream this Stop belongs to, or the next answer goes silent (\"Working\", no stop square).\n const current = session.getState().generating[streamKey]\n const isStoppedStream = controller\n ? current?.abortController === controller\n : !current?.streamingAssistantId || current.streamingAssistantId === responseId\n if (isStoppedStream) session.abortConversationGeneration(streamKey)\n // A reattached run has no local controller: close its stream too, or it keeps painting. The\n // attach follows this conversation's streaming message (in a sub-agent branch, the branch's\n // reply), not the delegating run that was cancelled, so match it by that id.\n if (responseId) disconnectAttachedRun(streamKey, responseId)\n } finally {\n stopInFlightRef.current = false\n }\n }, [session, arbi, conversationKey, queryClient])\n\n const handleRetryOrphanedMessage = useCallback(\n async (messageContent: string, parentId: string | null, messageExtId: string) => {\n // One retry at a time: the alert's Retry and the composer's retry (or a double click) must\n // not delete and resend twice. Released however the retry ends.\n if (retryInFlightRef.current) return\n // Offline the delete would be paused and the resend refused, leaving neither done.\n if (refuseChatWhenOffline()) return\n retryInFlightRef.current = true\n try {\n // A failed parent (a follow-up the restart sweep failed behind a failed answer) is\n // refused by the server, so the resend goes to the nearest message above it instead.\n const replyTo = currentConversationId\n ? resolveReplyTarget(queryClient, currentConversationId, parentId)\n : parentId\n if (!replyTo && currentConversationId) {\n try {\n await arbi.conversations.delete(currentConversationId)\n removeConversationFromLists(queryClient, currentConversationId)\n session.startNewConversation()\n } catch (error) {\n console.error('Failed to delete conversation with orphaned first message:', error)\n }\n } else if (replyTo) {\n try {\n await arbi.conversations.deleteMessage(messageExtId)\n // The message and its replies are gone from whichever thread held them.\n void queryClient.invalidateQueries(allConversationThreads)\n } catch (error) {\n console.error('Failed to delete orphaned message:', error)\n }\n }\n\n await sendMessage(messageContent, replyTo)\n } finally {\n retryInFlightRef.current = false\n }\n },\n [currentConversationId, arbi, session, sendMessage, queryClient]\n )\n\n const handleRetrySend = useCallback(async () => {\n if (isGenerating || !lastFailedMessage || retryInFlightRef.current) return\n if (refuseChatWhenOffline()) return\n retryInFlightRef.current = true\n try {\n session.clearConversationError(conversationKey)\n // sendMessage replies to the nearest message the server accepts: the view may still point at\n // a turn a Retry deleted, or at a failed answer.\n const parentId = session.getState().cursor\n await sendMessage(lastFailedMessage, parentId)\n } finally {\n retryInFlightRef.current = false\n }\n }, [isGenerating, lastFailedMessage, session, conversationKey, sendMessage])\n\n const clearErrorState = useCallback(() => {\n session.clearConversationError(conversationKey)\n }, [session, conversationKey])\n\n // Clear error state when starting a new conversation\n useEffect(() => {\n if (currentConversationId === null) {\n session.clearConversationError('new')\n }\n }, [currentConversationId, session])\n\n return {\n handleCreateBranch,\n handleSendMessage,\n handleAbortGeneration,\n handleRetrySend,\n handleRetryOrphanedMessage,\n clearErrorState,\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -3,7 +3,7 @@
3
3
  var chunkT5FZICXN_cjs = require('./chunk-T5FZICXN.cjs');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
5
 
6
- var pdfViewerChunk = chunkT5FZICXN_cjs.lazyChunk(() => import('./PdfViewerImpl-QUSDLNNM.cjs'));
6
+ var pdfViewerChunk = chunkT5FZICXN_cjs.lazyChunk(() => import('./PdfViewerImpl-ZVJZ4KNW.cjs'));
7
7
  var PdfViewer = chunkT5FZICXN_cjs.lazyComponent(
8
8
  pdfViewerChunk,
9
9
  (m) => m.PdfViewer,
@@ -19,5 +19,5 @@ var PdfViewer = chunkT5FZICXN_cjs.lazyComponent(
19
19
 
20
20
  exports.PdfViewer = PdfViewer;
21
21
  exports.pdfViewerChunk = pdfViewerChunk;
22
- //# sourceMappingURL=chunk-QP3WQSAA.cjs.map
23
- //# sourceMappingURL=chunk-QP3WQSAA.cjs.map
22
+ //# sourceMappingURL=chunk-4B2GW3AY.cjs.map
23
+ //# sourceMappingURL=chunk-4B2GW3AY.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/viewers/PdfViewer.tsx"],"names":["lazyChunk","lazyComponent","jsx"],"mappings":";;;;;AAUO,IAAM,cAAA,GAAiBA,2BAAA,CAAU,MAAM,OAAO,8BAAiB,CAAC;AAEhE,IAAM,SAAA,GAA2CC,+BAAA;AAAA,EACtD,cAAA;AAAA,EACA,CAAC,MAAM,CAAA,CAAE,SAAA;AAAA,EACT,sBACEC,cAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAU,yCAAA;AAAA,MACV,WAAA,EAAU,aAAA;AAAA,MACV,WAAA,EAAU;AAAA;AAAA;AAGhB","file":"chunk-QP3WQSAA.cjs","sourcesContent":["/**\n * `PdfViewer`: a PDF rendered with pdf.js. The PDF engine is large, so it lives in its own chunk,\n * loaded the first time a PDF renders (`pdfViewerChunk`); meanwhile a blank page area.\n */\nimport type { ComponentType } from 'react'\nimport { lazyChunk, lazyComponent } from '../lazy-chunk'\nimport type { PdfViewerProps } from './PdfViewerImpl'\n\nexport type { PdfViewerAssets, PdfViewerProps } from './PdfViewerImpl'\n\nexport const pdfViewerChunk = lazyChunk(() => import('./PdfViewerImpl'))\n\nexport const PdfViewer: ComponentType<PdfViewerProps> = lazyComponent(\n pdfViewerChunk,\n (m) => m.PdfViewer,\n () => (\n <div\n className=\"h-full w-full animate-pulse bg-muted/40\"\n data-slot=\"pdf-loading\"\n aria-busy=\"true\"\n />\n )\n)\n"]}
1
+ {"version":3,"sources":["../src/viewers/PdfViewer.tsx"],"names":["lazyChunk","lazyComponent","jsx"],"mappings":";;;;;AAUO,IAAM,cAAA,GAAiBA,2BAAA,CAAU,MAAM,OAAO,8BAAiB,CAAC;AAEhE,IAAM,SAAA,GAA2CC,+BAAA;AAAA,EACtD,cAAA;AAAA,EACA,CAAC,MAAM,CAAA,CAAE,SAAA;AAAA,EACT,sBACEC,cAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAU,yCAAA;AAAA,MACV,WAAA,EAAU,aAAA;AAAA,MACV,WAAA,EAAU;AAAA;AAAA;AAGhB","file":"chunk-4B2GW3AY.cjs","sourcesContent":["/**\n * `PdfViewer`: a PDF rendered with pdf.js. The PDF engine is large, so it lives in its own chunk,\n * loaded the first time a PDF renders (`pdfViewerChunk`); meanwhile a blank page area.\n */\nimport type { ComponentType } from 'react'\nimport { lazyChunk, lazyComponent } from '../lazy-chunk'\nimport type { PdfViewerProps } from './PdfViewerImpl'\n\nexport type { PdfViewerAssets, PdfViewerProps } from './PdfViewerImpl'\n\nexport const pdfViewerChunk = lazyChunk(() => import('./PdfViewerImpl'))\n\nexport const PdfViewer: ComponentType<PdfViewerProps> = lazyComponent(\n pdfViewerChunk,\n (m) => m.PdfViewer,\n () => (\n <div\n className=\"h-full w-full animate-pulse bg-muted/40\"\n data-slot=\"pdf-loading\"\n aria-busy=\"true\"\n />\n )\n)\n"]}
@@ -715,7 +715,8 @@ var VIEWER_TABS = [
715
715
  "docx",
716
716
  "xlsx",
717
717
  "versions",
718
- "preview"
718
+ "preview",
719
+ "slides"
719
720
  ];
720
721
  var DEFAULT_DOCUMENT_VIEWER_STATE = {
721
722
  tab: "pdf",
@@ -725,7 +726,8 @@ var DEFAULT_DOCUMENT_VIEWER_STATE = {
725
726
  phonePdfChosen: false,
726
727
  commentTarget: null,
727
728
  unsavedChanges: false,
728
- detached: false
729
+ detached: false,
730
+ presenting: false
729
731
  };
730
732
  var NO_PASSAGE_CITATION = {
731
733
  active: null,
@@ -747,7 +749,8 @@ var DRAFT_TITLES = {
747
749
  text: "New Doc",
748
750
  audio: "New Audio",
749
751
  photo: "New Photo",
750
- video: "New Video"
752
+ video: "New Video",
753
+ presentation: "New Presentation"
751
754
  };
752
755
  function nextViewingDocument(current, input) {
753
756
  if (!input) return null;
@@ -850,7 +853,7 @@ function createArbiDocumentSessionStore({
850
853
  draftDocuments: { ...state.draftDocuments, [id]: draft },
851
854
  viewingDocument: { id, page: null, passageId: null },
852
855
  // A text draft is written in the editor; a capture's recorder replaces the tabs.
853
- documentViewer: kind === "text" ? { ...state.documentViewer, tab: "edit" } : state.documentViewer
856
+ documentViewer: kind === "text" ? { ...state.documentViewer, tab: "edit" } : kind === "presentation" ? { ...state.documentViewer, tab: "slides" } : state.documentViewer
854
857
  });
855
858
  showViewer?.();
856
859
  return id;
@@ -881,7 +884,8 @@ function createArbiDocumentSessionStore({
881
884
  tabRequest: null,
882
885
  commentTarget: null,
883
886
  unsavedChanges: false,
884
- detached: false
887
+ detached: false,
888
+ presenting: false
885
889
  }
886
890
  }),
887
891
  reset: () => update({
@@ -2134,5 +2138,5 @@ exports.useTransientView = useTransientView;
2134
2138
  exports.useUserUsageToday = useUserUsageToday;
2135
2139
  exports.useWorkspaceActivity = useWorkspaceActivity;
2136
2140
  exports.useWorkspaces = useWorkspaces;
2137
- //# sourceMappingURL=chunk-YMB6XUR2.cjs.map
2138
- //# sourceMappingURL=chunk-YMB6XUR2.cjs.map
2141
+ //# sourceMappingURL=chunk-7L7QRIQ2.cjs.map
2142
+ //# sourceMappingURL=chunk-7L7QRIQ2.cjs.map