写卡攻略本AI玩家 · 创作者
技术手册

技术手册 · SDK

React 集成

使用 CharxProvider、公开 Hooks 和 Thread,管理消息状态与订阅生命周期。

React 入口是 @ai_wanjia/sdk/react。以下示例按仓库 SDK 1.0.10 编译核对,适用于连接真实宿主的 React 页面。React starter 自己实现了 mock 选择与界面状态;其 mock 不会自动注入到 CharxProvider。

Provider 与最小聊天页 ​

CharxProvider 负责初始化 SDK,握手完成后渲染子组件。fallback 显示等待状态,errorFallback 接收初始化错误。它没有 sdk 或 mock 注入参数。

以下代码可以作为 React 浏览器项目的 App.tsx。普通浏览器标签页没有宿主握手时会显示连接失败;先通过本地调试界面加载它。

tsx
import { useState } from 'react'
import { CharxProvider, Thread, useChat } from '@ai_wanjia/sdk/react'

function ChatPage() {
  const chat = useChat()
  const [text, setText] = useState('')
  const [actionError, setActionError] = useState<string | null>(null)
  const busy = chat.status === 'sending' || chat.status === 'streaming'
  const run = (action: () => Promise<void>) => {
    setActionError(null)
    void action().catch((error: unknown) => {
      setActionError(error instanceof Error ? error.message : String(error))
    })
  }

  return (
    <main>
      <Thread.MessageList
        messages={chat.messages}
        isStreaming={chat.status === 'streaming'}
        onLoadMore={chat.hasMore ? () => run(chat.loadMore) : undefined}
      />
      <form onSubmit={(event) => {
        event.preventDefault()
        const message = text.trim()
        if (!message || busy) return
        chat.send(message)
        setText('')
      }}>
        <input aria-label="消息" value={text} onChange={(event) => setText(event.target.value)} />
        <button type="submit" disabled={busy || !text.trim()}>发送</button>
        <button type="button" disabled={!busy} onClick={() => run(chat.stop)}>停止</button>
      </form>
      {chat.error && <p role="alert">{chat.error.message}</p>}
      {actionError && <p role="alert">{actionError}</p>}
    </main>
  )
}

export default function App() {
  return (
    <CharxProvider
      fallback={<p>正在连接平台…</p>}
      errorFallback={(error) => <p role="alert">连接失败:{error.message}</p>}
    >
      <ChatPage />
    </CharxProvider>
  )
}

Hooks ​

Hook返回值与边界
useCharx()当前 SDK 实例,必须位于 Provider 内
useChat()消息、请求状态、错误、发送与消息操作、历史分页
useVariable(key, defaultValue)[value, set];首次读取并订阅变量变化,set 返回 Promise
useVariables()完整变量表,首次读取并订阅变化
useCharacter()角色信息,加载前为 null
usePersona()人设或 null;null 也可能表示尚未加载
useStorage(key, defaultValue)[value, set, remove];读取并维护本组件状态,不提供跨组件 storage 订阅
useModels()模型列表、当前模型、加载/选择状态、错误、refresh() 和 select()

变量和 storage 的默认值用于显示缺值状态,不会自动持久化。useCharacter()、usePersona() 和变量、storage Hooks 没有独立的错误返回字段;若界面需要准确区分读取失败与空值,可使用 useCharx() 调用对应模块并自行处理错误。

Thread 与消息状态 ​

Thread 只包含 MessageList 和 MessageItem,不包含输入框或自动发送逻辑。MessageList 接收 messages、isStreaming、onLoadMore 和可选 renderMessage(message, isStreaming)。默认内容按文本展示;自定义展示可使用公开的 MdRenderer。

MdRenderer 是轻量渲染器:先转义 HTML,再处理粗体、斜体、行内代码和换行。它不提供完整 Markdown、原始 HTML 执行或任意组件解析。

useChat().status 是当前 Hook 的请求状态:idle、sending、streaming、error。它与单条消息可选的 generationStatus 不同。不要因为 Hook 回到 idle,就假定每条历史消息都已完成。

Hook 会订阅宿主消息新增、更新、编辑、删除和会话切换,刷新历史,并处理本地占位消息与宿主消息 ID 的合并。分页使用 loadMore() 和 hasMore。currentRequestId 和 currentAssistantMessageId 可用于关联当前生成,但可能为 null。

删除操作会回读宿主历史;识别到用户取消时不会把消息当作已删除。回溯成功后会刷新消息状态。swipeMessage() 仍是兼容占位,当前宿主可能拒绝,不能作为已支持的切换功能。

清理订阅 ​

SDK 自带 Hooks 会清理自己创建的订阅。自行调用 onMessageUpdated()、onChatChanged()、variable.watch() 或 model.onChanged() 时,需要在 Effect 清理函数中调用取消订阅函数。

tsx
import { useEffect } from 'react'
import { useCharx } from '@ai_wanjia/sdk/react'

export function MessageObserver() {
  const sdk = useCharx()
  useEffect(() => {
    const unsubscribe = sdk.chat.onMessageUpdated((message) => {
      console.log('宿主消息已更新', message.id)
    })
    return unsubscribe
  }, [sdk])
  return null
}

Provider 使用共享单例,当前实现不会在卸载时自动调用 destroy()。不要在任意子组件清理时销毁共享实例,否则会影响其他使用者。只有页面级所有者确认不再有调用方时,才调用 sdk.destroy() 释放底层监听并终止等待中的 RPC;销毁后需通过 getInstance() 获取新实例再初始化。