@arbidocs/react

中文文档
0.5.133发布于 2026/11/07MITTypeScriptESM + CJS近 30 天下载 5.1万

React hooks and provider for the ARBI SDK

原始使用文档
文档目录13 节
  1. Quickstart
  2. 1. Set up the host
  3. 2. Build a streaming chat
  4. Hooks
  5. Artifact editing ( @arbidocs/react/artifacts )
  6. PDF document tools ( @arbidocs/react/document-tools )
  7. Streaming callbacks
  8. The query client
  9. Offline mail and calendar
  10. The live connection
  11. Cache invalidation
  12. Peer dependencies
  13. Tasks, agents, skills, contacts and AI usage

@arbidocs/react

React hooks and provider for building ARBI-powered apps. Includes @arbidocs/sdk and @tanstack/react-query — one install gets you everything.

Quickstart

npm install @arbidocs/react

1. Set up the host

import { QueryClientProvider } from '@tanstack/react-query'
import { ArbiHost, createArbiQueryClient } from '@arbidocs/react'
import { Arbi } from '@arbidocs/sdk/browser'

const { queryClient } = createArbiQueryClient()
const arbi = new Arbi({ url: 'https://www.arbidocs.com' })

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <ArbiHost config={{ arbi }}>
        <Chat />
      </ArbiHost>
    </QueryClientProvider>
  )
}

<ArbiHost config> is the one place a host hands its configuration to the hooks: arbi, and optionally session, documents, calendar, mail, account and dictate (each with a default). @arbidocs/blocks re-exports it with the blocks domains too; a blocks site uses that one (see its README).

assistantName is what people call the assistant when they address it in a chat: the @<name> in the mention menu, the draft a shared thread starts with, and the name a message is routed to the assistant on (useAssistantName()). It defaults to "ARBI". Under another name, a typed @ARBI still reaches the assistant (assistantLabels), and mentions stored under an earlier name still show as chips.

2. Build a streaming chat

import { useState, useRef } from 'react'
import { useArbi, useDocuments, useAssistantQuery } from '@arbidocs/react'

function Chat() {
  const arbi = useArbi()
  const [workspaceId, setWorkspaceId] = useState<string>()

  if (!workspaceId) {
    return (
      <button
        onClick={async () => {
          await arbi.login('[email protected]', 'password')
          const ws = await arbi.workspaces.list()
          await arbi.selectWorkspace(ws[0].external_id)
          setWorkspaceId(ws[0].external_id)
        }}
      >
        Connect
      </button>
    )
  }

  return <ChatWindow workspaceId={workspaceId} />
}

function ChatWindow({ workspaceId }: { workspaceId: string }) {
  const { data: docs } = useDocuments(workspaceId)
  const mutation = useAssistantQuery()
  const [input, setInput] = useState('')
  const [response, setResponse] = useState('')
  const lastId = useRef<string | null>(null)

  return (
    <div>
      <div>{response}</div>
      <form
        onSubmit={(e) => {
          e.preventDefault()
          setResponse('')
          mutation.mutate({
            question: input,
            docIds: docs?.map((d) => d.external_id) ?? [],
            previousResponseId: lastId.current,
            onToken: (t) => setResponse((r) => r + t),
            onStreamStart: ({ assistant_message_ext_id }) => {
              lastId.current = assistant_message_ext_id
            },
          })
          setInput('')
        }}
      >
        <input value={input} onChange={(e) => setInput(e.target.value)} />
        <button type="submit" disabled={mutation.isPending}>
          Send
        </button>
      </form>
    </div>
  )
}

That's it — three hooks and you have a streaming RAG chat.

Hooks

HookReturnsAuto-enabled when
useArbi()Arbi instanceAlways
useWorkspaces()User's workspacesLogged in
useCurrentWorkspace()The selected workspace (workspaceId, workspaceStatus, its list entry) and my role / isMember in itLogged in
useActiveConfigId()The configuration new work runs with (the chosen one, else the replied-to message's, else the last)Always
useShellWorkspaces(options)Same, over a list and select you supply (no provider needed)Always
useDocuments(workspaceId)Workspace documentsWorkspace selected
useConversations(workspaceId)Workspace conversationsWorkspace selected
useTags(workspaceId)Workspace tagsWorkspace selected
useTags(workspaceId, { kind, parentId, ids, include: ['links'], linksLimit })Tags filtered, with each one's documents, facts and threads (a table's rows)Workspace selected
useTagKindSchema(kind) / useUpdateTagKindSchema()A kind's fields in the workspace (a table's columns); set themWorkspace selected
useSetTagValues()Merge-patch a tag's values for its kind's fieldsCall .mutate()
useFactsSearch({ workspaceId })The facts Smart Search: the query box, the hits of the last search (hybrid), search() and clear(), kept for the session under FACTS_SEARCH_VIEWWorkspace selected
useAssistantQuery()Streaming mutationCall .mutate()
useHealth()Backend statusPass { enabled: true }
useProfile() / useUpdateProfile()The user's name, picture and email; save name or pictureLogged in
useRequestEmailChange() / useConfirmEmailChange()Email change by verification codeCall .mutate()
usePasswordChange()Change the master password (one request: the password only wraps the account key)Call .mutate()
useNotificationPreferences(keys)Email notification switches, optimistic saveLogged in
useSessions() / useTerminateSession() / useLogoutAllSessions()Active sessions, end one, sign out everywhereLogged in
useDeleteAccount()Close the account (soft delete)Call .mutate()
useModelConnections() / useDirectModelUsage()The main agent's model connections (box and personal) and the direct calls of the last 30 daysLogged in
useSaveModelConnection() / useRemoveModelConnection() / useUnlockModelConnections()Save a personal connection (its key sealed to the identity, then unlocked on this session), remove one, unlock the personal keysCall .mutate()
useFeature(name) / useFeatureSwitch(name)Whether a feature is on for the user (<ArbiHost config={{ features }}>; unlisted: on), and how to switch itAlways

The account hooks have SDK-free controllers for hosts without an arbi (useNameEditor, useEmailChange, usePasswordChangeForm, useDeleteAccountFlow, useAvatarCrop, toNotificationPreferences): they own the form state and take the save as a function. The blocks /connect entry composes the lot into a settings page (see the Settings page recipe in docs/design/blocks-package.md).

Features. A host lists the parts of the product the user can switch, and where each switch is kept: a boolean field of the user's settings (follows the user) or this device (createLocalFeatureStore({ key, migrate }), read synchronously from localStorage; migrate brings an earlier build's values in once). useFeature(name) reads one anywhere; a feature the host does not list is on, so a site that lists none offers everything.

const device = createLocalFeatureStore({ key: 'my-site-features' })
<ArbiHost config={{ features: {
  skills: { userSetting: 'show_skills', default: false },
  calls: { local: device, default: false },
} }}>

Selected workspace. The session's workspaceId and workspaceStatus live in a workspace store; useSwitchWorkspace moves them and useOpenSelectedWorkspace re-opens the selection after a reload. Give the session one kept per tab so a reload stays in the workspace (createArbiSessionStore({}, { workspace: createArbiWorkspaceStore({ storage: browserWorkspaceStorage() }) })); reset() it on sign-in so the next person starts on the workspace list. readWorkspaceLastOpened() orders a "recent workspaces" list.

Thread tabs. The open conversations are the layout's tabs.threads (in the order opened, at most MAX_THREAD_TABS), with tabs.activeThread the one on screen (threadTabId: its conversation, or new). Each tab keeps the composer it was left with (ArbiThreadTabView: its line, edit, model and scroll position; its draft is the chat's, per conversation), so selectThreadTab brings it back and openThreadTab opens a thread at its latest message. They are kept across reloads with the layout's persist, never with what was typed.

Chat. The open conversation and its composer, the turns running in each conversation, the context documents and facts, and the agent bar's queue and run control live in a chat store (createArbiChatStore({ layout, persist, draftStorage })), which moves with the layout's thread tabs and tells readers once per tab change. With a persist (createStoragePersist({ parse: parseSavedChat })) the open conversation and its line, the context, each agent chat's agent and the chosen model survive a reload; the drafts are kept apart, sealed to the person (createSealedDraftStorage), and only for open tabs. createArbiSessionStore uses one in memory. A host with state of its own composes its session from parts instead (composeArbiSessionStore({ layout, workspace, chat, documents })).

Documents. The document being viewed and the viewer's own state (its tab, a comparison, a tab asked for, the text size, a Word comment to open, unsaved edits, a popped-out window), the documents selected and those to select once listed, the document pinned to the chat, the drafts started on the client (startDraftDocument(kind), updateDraftDocument, setDraftDocumentKind, clearDraftDocument), the citation picking passages in the viewer (setPassageCitation) and the related documents live in a documents store (createArbiDocumentSessionStore({ persist, showViewer })). showViewer is how the host shows its viewer for a document the session just got (a new draft, showNewDocument for a chat's document). With a persist (load is synchronous, read from localStorage for example, and checked with parseSavedDocuments), the viewed document and its page, the pinned document, the viewer's tab and the text size come back on a reload before anything renders.

Conversation settings. useConversationSettings() is the open conversation's title and sharing (saveTitle, share, unshare: one PATCH of the conversation, which the server resolves from the conversation's workspace) and the model of the config its next turn runs on (resetToDefaults drops the picked one).

Artifact editing (@arbidocs/react/artifacts)

useArtifactSession({ workspaceId, docId }) loads an artifact, holds the edit buffer, saves it as a NEW immutable version (a child document via parent_ext_id), lists the version lineage and compares any two versions with track changes. It returns props for the @arbidocs/blocks/artifacts parts: view (for ArtifactEditor), editorProps and redlineProps. The pieces underneath are SDK-free and usable on their own: useArtifactEditSession (buffer, baseline, dirty, save, compare), useRedline (diff with a pluggable, worker-capable RedlineEngine), buildVersionChain and versionLabel. useArtifactText, useArtifactVersions and useSaveArtifactVersion are the SDK bindings.

useArtifactText(docId) returns clean markdown and a status (loading, parsing, ready, error): a fresh upload is parsed for a few seconds, during which the backend answers 404, 422 or 500, so it retries with a growing delay (bounded) and unescapes the backend's escaped markdown (unescapeMarkdown; only for artifacts stored from markdown). useStreamedArtifact({ artifacts, workspaceId, settled }) covers the stream, persist, edit life of an agent draft (phase: streaming, persisting, parsing, ready, error), cleaning the draft with cleanDraftMarkdown (unwrapCitationLinks, stripAgentPreamble, promoteBoldTitle) before it is stored.

PDF document tools (@arbidocs/react/document-tools)

The data side of annotation, markup, freeze, e-signing, signature checking and production sets: DocumentToolsProvider (the host's API client, user and callbacks; deploymentDomain defaults to the host arbi's, useDeploymentDomain()), fetchDocumentPdf (a version's /view PDF), useDocumentMarkup, createMarkupStore (an AnnotationStore over the server markup, with 409 merge), fetchMarkupPdf, useDocumentSignatures, useSignDocument, useSignatureVerification and buildProduction / saveProductionFiles. The UI is @arbidocs/blocks/document-tools; see "Using the document tools in another frontend" in the @arbidocs/blocks README.

Streaming callbacks

Pass these to mutation.mutate() alongside question and docIds:

CallbackDescription
onToken(token)Each text chunk as it streams
onStreamStart({ assistant_message_ext_id })Stream started — save the ID for follow-up queries
onAgentStep(step)Agent tool usage (searching, reading, etc.)
onError(message)Error during streaming
onComplete()Stream finished

The query client

createArbiQueryClient() is the client every ARBI host shares (ARBI-frontend, and a blocks site through defaultHost): reads fresh for 5 minutes and kept 30, retried twice with backoff but never on a 4xx (except 408/429) or a read a sign-out aborted; writes never retried; a write refused while the account's deletion is pending, or dropped by the client's settings write breaker, tells the user. Sign-out empties it with queryClient.clear() (the same instance, so every hook keeps it).

createArbiQueryClient({ persist: true }) also returns persistOptions for <PersistQueryClientProvider> (@tanstack/react-query-persist-client): the document metadata, workspace list, settings, projects and entitlements are kept on this device for an offline start (never a document's content), with writes queued offline (registerArbiMutationDefaults). Call claimOfflineQueue(userExtId) at each sign-in, so a previous account's queued writes never replay.

ArbiWebSocketProvider keeps React Query's onlineManager in step with the browser's network, the live connection (a dropped socket counts once it has been up) and the person's "Work offline" choice (useWorkOffline, a transient session view, WORK_OFFLINE_VIEW). useReconnect takes the cheapest way back: it clears "Work offline", says there is no network, or reloads.

Offline mail and calendar

Mail is kept on the device, sealed at rest to the signed-in user's key, bound to that account and readable only by them: reading and searching recent mail offline (the last 7 days by default; per device, Off / 1 / 7 / 30 / 90 days, useOfflineMailSettings), an Outbox whose sends go out exactly once and in order on reconnect (each keeps its request_id), drafts written offline, and changes made offline (read, flagged, moved, Trash, deleted: queueMailChange, usePendingMailChanges), applied in order on reconnect. Nothing goes out under "Work offline". The default mail client (no host mail) already reads through offlineMailReads, and defaultHost in @arbidocs/blocks mounts the runtime and wipes the mail at sign-out. A host with its own client and session adds three pieces:

import { createMailClient, offlineMailReads, useOfflineMailRuntime, wipeOfflineMail } from '@arbidocs/react'

const client = createMailClient((path, init) => getArbi().mail.fetch(path, init), {
  ...offlineMailReads(getArbi), // reads answer from the device offline
})
useOfflineMailRuntime({ openOutbox }) // mounted once: sends the Outbox, applies changes, syncs
await wipeOfflineMail() // at sign-out

Conversations (useMailConversations, useMailThread) group by thread on the device exactly as the server does (groupMailConversations, mailConversationKey); useNativeThreadKeys keys older native mail into conversations in the background.

The mail kits use offlineMailSupport (@arbidocs/blocks/connect) as their device storage by default; the per-device settings are ConnectedMailOfflineSettings.

The calendar is kept the same way (its own sealed database): reads are answered from the device offline, and event writes made offline wait in an outbox that is replayed in order once back online.

import { calendarOfflineSupport, useCalendarOfflineRuntime, wipeCalendarOffline } from '@arbidocs/react'

<ArbiHost config={{ calendar: { offline: calendarOfflineSupport } }}>…</ArbiHost>
useCalendarOfflineRuntime() // mounted once: replays the outbox on reconnect
await wipeCalendarOffline() // at sign-out

The live connection

<ArbiWebSocketProvider> holds the one reconnecting socket and applies what every site applies:

  • tasks and presence;
  • chats and calls;
  • notification rows;
  • finished runs (response_complete): the conversation stops generating, the follow-up leaves the queue box, a background run's saved documents are read again, and a run failed for the AI usage goes to the chat host's onQuotaExceeded;
  • conversation, mail-draft and skill events.

A turn's messages reach the person's other tabs from the server (conversation.message_posted).

A host handles other message types with handlers, by type, before the useArbiWebSocket subscribers:

<ArbiWebSocketProvider enabled={signedIn} handlers={useRemoteControlHandlers()}>

Remote control. An agt-… assistant asking to drive the UI (remote_control) is consent-gated. Its state is a session transient view (REMOTE_CONTROL_VIEW, never saved; blocks' remote control resets it at every sign-in, sign-out, expiry and change of user): useRemoteControl(selector) reads it, remoteControlActions(session) changes it (reset() back to the start), and toRemoteControlRequest(message, isSequenceId) narrows the message to a runnable request. The handler, the consent prompt and the guided cursor are @arbidocs/blocks' (useRemoteControlHandlers, ConnectedRemoteControlConsent).

Cache invalidation

import { arbiQueryKeys } from '@arbidocs/react'
import { useQueryClient } from '@tanstack/react-query'

const queryClient = useQueryClient()
queryClient.invalidateQueries({ queryKey: arbiQueryKeys.documents(workspaceId) })

Peer dependencies

  • react >= 18

Tasks, agents, skills, contacts and AI usage

Hooks for an "AI workforce" and a messages screen. Writes invalidate through arbiQueryKeys, so useTasks, useAgents, useConfigs and useChats refresh by themselves.

const create = useCreateTask() // mutateAsync(CreateTaskRequest) -> TaskResponse
const run = useRunTask() // approves a run parked for access (temporary grant), then runs
const runs = useTaskRuns(taskId) // polls while a run is in flight
const save = useSaveAgentConfig() // { agentExtId, sections: { Agents: { PERSONA, ... } } }
const grant = useGrantAgentAccess() // { agentExtId, workspaceId, mode?: { kind: 'standing' | 'temporary' } }
const contacts = useContacts() // + useInviteContacts(), useCreateContact(), useUpdateContact(), useDeleteContact()

Form-to-request mappings (taskComposerToCreateRequest, composePersona, ...) are in @arbidocs/blocks/agents. AI usage is reported in £ (ex VAT), not dollars: use formatGbp(n) (£0.47, £12.40, whole pounds as £10) and the metered flag on useProjectUsage (false = this deployment does not record usage). The messenger (a side panel, or its list and thread parts on a messages page) is ConnectedMessenger in @arbidocs/blocks/connect. See docs/design/blocks-package.md ("Hooks and mappings a host should not rewrite").

useSessionPageTab(page, fallback) reads and writes a page's selected tab in the host session's views.tabs, preserving it across navigation and reload. The host supplies its default tab. Use useSessionView(key, fallback) for persisted page preferences and useTransientView for session-only filters or one-shot requests. All three use the same session layout as the kits.

版本与发布记录

@arbidocs/react 共发布 487 个版本,首次发布于 2026/02/11;过去 12 个月发布了 487 个版本。

版本发布日期距今
0.5.133latest2026/11/071 天前
0.5.1322026/11/061 天前
0.5.1312026/11/061 天前
0.5.1302026/11/052 天前
0.5.1292026/11/052 天前
0.5.1282026/11/052 天前

关于 @arbidocs/react 的常见问题

@arbidocs/react 是什么?

React hooks and provider for the ARBI SDK

如何安装 @arbidocs/react?

在项目目录运行 npm install @arbidocs/react,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=18)、react-dom(>=18)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@arbidocs/react 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@arbidocs/react 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@arbidocs/react 可以商用吗?

@arbidocs/react 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@arbidocs/react 的下载量有多少?

根据 npm 官方下载统计,@arbidocs/react 在 2026/10/07 – 2026/11/05 这 30 天内被下载 50,995 次,日均约 1,700 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@arbidocs/react 还在维护吗?

最新版本 0.5.133 发布于 2026/11/07(1 天前);至今共发布 487 个版本,其中过去 12 个月发布了 487 个;首次发布于 2026/02/11。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。