Skip to Content
Card SDKReact-привязки

React-привязки

@neurosquad/card-sdk/react оборачивает карточку в провайдер и отдаёт её живое состояние через хуки. React 18.2+ или 19 — peer-зависимость; шаблон 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Карточка, которую вы подключили сами, — например, с мок-хоста. Без неё провайдер сам вызывает connect().
connectOptionsПараметры для connect().
fallbackПоказывается, пока идёт подключение.
errorFallback(error) => ReactNode, показывается, если подключиться не удалось. По умолчанию — текст ошибки.

Шаблон React сначала подключается, а потом передаёт card — так одна и та же точка входа работает и в приложении, и, с мок-хостом, в превью в браузере.

Хуки

ХукВозвращает
useCard()Card — для всего, у чего нет своего хука.
useCardContext()Живой HostContext; перерисовывает при любом изменении.
useSettings()[settings, setSettings] — settings.values, settings.secrets; сеттер меняет несекретные значения.
useStorage(key, initial, { scope? })[value, setValue, { loading, error }] — как useState, только с сохранением. Сеттер обновляет сразу и пишет в фоне; принимает и функцию. С 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)Переводчик, перерисовывающий при смене языка. Каталог объявляйте вне компонента.
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> ) }

Хуки, которые обращаются к приложению (useAgents, useStorage), не бросают исключения, а сообщают об ошибках в поле error — нехватка разрешения появится там как CardSdkError с PERMISSION_DENIED.

Локальная копия SDK

Если карточка подключает SDK по ссылке file:, npm создаёт символическую ссылку, и Vite может загрузить вторую копию React рядом с SDK — тогда хуки падают с «Invalid hook call». В vite.config.ts шаблона React уже есть resolve: { dedupe: ['react', 'react-dom'] }; в своей сборке добавьте его сами или укажите install-links=true в .npmrc карточки.