Skip to Content
卡片 SDKReact 绑定

React 绑定

@neurosquad/card-sdk/react 用一个 provider 包裹卡片,并把它的实时状态以 hook 的形式提供出来。 React 18.2+ 或 19 是对等依赖(peer dependency);React 模板已经把一切配置好了。

import { createRoot } from 'react-dom/client' import { CardProvider, useCardContext, useStorage } from '@neurosquad/card-sdk/react' function App() { const { instance, workspace } = useCardContext() const [count, setCount] = useStorage('count', 0) return ( <button className="ns-btn ns-btn--primary" onClick={() => setCount(count + 1)}> {instance.displayName} in {workspace.name}: {count} </button> ) } createRoot(document.getElementById('root')!).render( <CardProvider fallback={<p>Connecting…</p>}> <App /> </CardProvider> )

CardProvider

属性含义
card你自己连接好的 card——例如来自模拟宿主。不提供时,provider 会调用 connect()。
connectOptions传给 connect() 的选项。
fallback连接期间渲染的内容。
errorFallback(error) => ReactNode,连接失败时渲染。默认显示错误消息。

React 模板会先完成连接,再传入 card——这样同一个入口既能在应用中运行,也能配合模拟宿主在浏览器中预览。

Hook

Hook返回
useCard()Card——用于没有专门 hook 的一切操作。
useCardContext()实时的 HostContext;任何变化都会重新渲染。
useSettings()[settings, setSettings]——settings.values、settings.secrets;setter 修改非密钥值。
useStorage(key, initial, { scope? })[value, setValue, { loading, error }]——和 useState 一样,但会持久化。setter 立即更新并在后台写入;也接受函数。scope: 'package' 会跟随卡片其他副本的写入。
useAgents(){ agents, loading, error, refresh }——随状态和变更事件保持最新。需要 agents.read。
useAgentStatus(agentId)某个智能体的状态,或 undefined。
usePort(input?){ data, message }——某个输入(或任意输入)最后收到的值。
useEmit(output)一个稳定的 (data) => Promise<number>,用于在某个输出上发送。
usePortRequest(input, handler)挂载期间应答某个请求型输入。
usePeers()相连的卡片及其端口,实时更新。
useTool(name, handler)挂载期间实现清单中的某个工具;总是调用最新的处理函数。
useCardEvent(event, handler)挂载期间监听任意事件;总是调用最新的处理函数。
useTheme()实时的 ThemeSnapshot(CSS 变量已经应用好了)。
useLanguage()实时的 { language, locale }。
useTranslator(catalog)一个翻译函数,切换语言时会重新渲染。请在组件外定义 catalog。
useVisibility()visible、offscreen、overview 或 hidden。
usePaused()没人能看到卡片正文时为 true——此时暂停动画和轮询。
useExpanded()卡片是否已展开。

更完整的示例

import type { Catalog } from '@neurosquad/card-sdk' import { useAgents, useCard, useEmit, usePaused, usePort, useTool, useTranslator } from '@neurosquad/card-sdk/react' import { useEffect } from 'react' const catalog: Catalog = { en: { waiting_one: '{{count}} agent waits for you', waiting_other: '{{count}} agents wait for you' }, ru: { waiting_one: '{{count}} агент ждёт вас', waiting_few: '{{count}} агента ждут вас', waiting_many: '{{count}} агентов ждут вас', waiting_other: '{{count}} агента ждут вас' }, zh: { waiting_other: '{{count}} 个智能体在等你' } } export function Waiting() { const card = useCard() const t = useTranslator(catalog) const paused = usePaused() const { agents } = useAgents() const waiting = agents.filter((a) => a.status === 'needs-input') const { data: note } = usePort<string>('notes') const emit = useEmit<string>('digest') // Keep the overview tile and attention in step with the data. useEffect(() => { void card.setOverview({ primary: t('waiting', { count: waiting.length }), icon: 'bell' }) void card.attention(waiting.length > 0 ? 'needs-input' : 'none').catch(() => undefined) }, [card, t, waiting.length]) useTool('list_waiting', () => waiting.map((a) => a.name)) return ( <div className="ns-stack"> <p className={paused ? '' : 'pulse'}>{t('waiting', { count: waiting.length })}</p> {note ? <pre className="ns-mono">{note}</pre> : null} <button className="ns-btn" onClick={() => void emit(waiting.map((a) => `- ${a.name}`).join('\n'))}> Send digest </button> </div> ) }

会调用应用的 hook(useAgents、useStorage)不会抛出异常,而是在它们的 error 字段中报告失败—— 缺少权限时,那里会出现一个带 PERMISSION_DENIED 的 CardSdkError。

使用本地 SDK 副本

如果卡片通过 file: 链接依赖 SDK,npm 会创建符号链接,Vite 可能在 SDK 旁边加载第二份 React——此时 hook 会报 “Invalid hook call”。 React 模板的 vite.config.ts 已经包含 resolve: { dedupe: ['react', 'react-dom'] };在你自己的配置中请加上它,或在卡片的 .npmrc 中设置 install-links=true。