@wisdomai/react
v0.0.10
Published
Wisdom AI React SDK
Keywords
Readme
@wisdomai/react
React SDK for Wisdom AI. Ships a React context provider, auth hook, and a typed GraphQL client:
<WisdomProvider>— fetches a short-lived JWT + the tenantbaseUrlfrom your backend's/auth-tokenendpoint and exposes them via context. Accepts an optionalthemeprop (see Theming) and an optionalgetAuthTokencallback to bypass the default endpoint (see Custom auth token source).useWisdomAuth()— hook returning{ jwt, baseUrl, client, isLoading, error, refresh }.useWisdomClient()— hook returning a ready-to-useWisdomClient(null until auth succeeds).useWisdomTheme()— hook returning the activeWisdomTheme(the prop passed to<WisdomProvider>, or the default).WisdomClient— typed GraphQL client. Currently exposesclient.dashboards.get(id).
Capabilities
The SDK renders dashboards authored on the Wisdom platform directly in your React app — live data, your theme, and interactive filtering. Render a full dashboard with <Dashboard>, or drop in individual widgets with <DashboardWidget>.
- Visualizations — all chart types, data tables (with server-side paging), single- and multi-value metric cards, and text.
- Filters — interactive string, numeric (integer and float), enum, and date filters, driven by row-click, the filter drawer, or programmatically via
setFilterValue(see Dashboard filters).
The layout, data, and filter specs come from the dashboard you built in Wisdom; the SDK renders them client-side and keeps them in sync with the platform.
Installation
npm install @wisdomai/react \
@mui/material@^7 @mui/x-date-pickers@^7 \
@emotion/react@^11 @emotion/styled@^11 \
highcharts@^12 highcharts-react-official@^3 \
graphql@^16 luxon@^3Keep the version pins — installing these peers unpinned resolves to their latest majors and fails ERESOLVE against the SDK's peer ranges. Your app must additionally provide react (>=18) and react-dom (>=18).
Using an AI coding agent? The package ships an
AGENTS.mdwith condensed, copy-ready setup steps — point your agent at it to scaffold the integration.
Quick start: authentication
The SDK never sees your long-lived Wisdom access token. Your backend mints a short-lived JWT from that access token and serves it; <WisdomProvider> fetches the JWT in the browser and refreshes it before it expires.
1. Serve a token endpoint from your backend
Expose POST /auth-token on the same origin as your app, returning { jwt, baseUrl }. Keep WISDOM_ACCESS_TOKEN server-side only.
Protect this route with your app's own authentication. It exchanges your server-side access token for browser credentials, so only signed-in users of your app should be able to call it — an open
/auth-tokenlets anyone mint a Wisdom JWT for your org. The examples below omit that guard for brevity; add your existing session/authz check before the exchange (and mint a per-user token withimpersonateUserwhen each user should see only their own data).
import { WisdomAI } from '@wisdomai/node';
import Fastify from 'fastify';
const wisdom = new WisdomAI({
accessToken: process.env.WISDOM_ACCESS_TOKEN!,
baseUrl: process.env.WISDOM_BASE_URL!,
});
const app = Fastify();
// Returns { jwt, baseUrl } — a short-lived token plus your tenant API URL.
app.post('/auth-token', () => wisdom.getAuthToken());
await app.listen({ port: 3000 });Not on Node? getAuthToken() is just a thin wrapper over the GraphQL exchangeAccessToken query, so any backend can do the same exchange — POST {baseUrl}/graphql with your access key, then pair the returned JWT with your tenant baseUrl:
// exchangeAccessToken(accessToken: String!): String! — returns a bare ~60-min JWT.
app.post('/auth-token', async () => {
const res = await fetch(`${process.env.WISDOM_BASE_URL}/graphql`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: 'query Exchange($t: String!) { exchangeAccessToken(accessToken: $t) }',
variables: { t: process.env.WISDOM_ACCESS_TOKEN },
}),
});
const { data } = await res.json();
return { jwt: data.exchangeAccessToken, baseUrl: process.env.WISDOM_BASE_URL };
});exchangeAccessToken issues an org-level JWT (the same identity for every caller). For multi-user production embeds where each end user should see only their own data, mint a per-user JWT with impersonateUser instead and return that as jwt — the provider contract ({ jwt, baseUrl }) is the same.
2. Wrap your app and read a dashboard
<WisdomProvider> calls POST /auth-token, exposes a ready-to-use client via useWisdomClient() (null until auth succeeds), and silently re-fetches the JWT shortly before it expires and when the tab regains focus.
import { WisdomProvider, useWisdomClient } from '@wisdomai/react';
import { useEffect, useState } from 'react';
function App() {
return (
<WisdomProvider>
<DashboardName id="dashboard-id-123" />
</WisdomProvider>
);
}
function DashboardName({ id }: { id: string }) {
const client = useWisdomClient();
const [name, setName] = useState<string | null>(null);
useEffect(() => {
if (!client) return;
client.dashboards.get(id).then((dashboard) => setName(dashboard.name));
}, [client, id]);
if (!client) return <p>Authenticating…</p>;
return <p>{name ?? 'Loading…'}</p>;
}If your app already holds a Wisdom JWT (e.g. the SDK is mounted inside an authenticated app), skip the endpoint entirely with the getAuthToken callback. For the exact endpoint contract and error handling, see Requirements on the partner backend.
Usage
import { WisdomProvider, useWisdomClient, WisdomDashboardNotFoundError } from '@wisdomai/react';
import { useEffect, useState } from 'react';
function App() {
return (
<WisdomProvider>
<DashboardView />
</WisdomProvider>
);
}
function DashboardView() {
const client = useWisdomClient();
const [name, setName] = useState<string | null>(null);
const [notFound, setNotFound] = useState(false);
useEffect(() => {
if (!client) return;
client.dashboards
.get('dashboard-id-123')
.then((dashboard) => setName(dashboard.name))
.catch((err) => {
if (err instanceof WisdomDashboardNotFoundError) setNotFound(true);
else throw err;
});
}, [client]);
if (!client) return <p>Authenticating…</p>;
if (notFound) return <p>Dashboard not found</p>;
return <p>{name ?? 'Loading…'}</p>;
}Requirements on the partner backend
WisdomProvider sends POST /auth-token to the same origin and expects a JSON response of shape { jwt: string, baseUrl: string }. This matches the sdk-playground Fastify server exactly. If your backend hosts the token endpoint at a different path, you'll need a dev proxy or a reverse proxy in front of your app.
baseUrlmust include the scheme. Use the full origin, e.g.https://canary.gowisdom.ai— not a bare host (canary.gowisdom.ai). The SDK derives its WebSocket endpoint frombaseUrl, and a value without anhttp(s)://prefix throwsSubscriptionTransport: unrecognized baseUrl protocol.
Custom auth token source
If your app already has access to a JWT (e.g., the SDK is mounted inside an app that holds the user's session), pass a getAuthToken callback to bypass the default POST /auth-token fetch. The callback must resolve to { jwt, baseUrl } (typed as WisdomAuthTokenResult), where baseUrl is the Wisdom tenant API URL (the same value the partner backend would otherwise return from /auth-token) — not the host app's origin, unless the host app reverse-proxies /graphql and the WS endpoints to Wisdom.
import { WisdomProvider, type WisdomAuthTokenResult } from '@wisdomai/react';
import { useCallback } from 'react';
import { getAuthToken } from '@/services/auth';
const WISDOM_BASE_URL = 'https://your-tenant.wisdom.ai';
function App() {
const resolveAuth = useCallback(async (): Promise<WisdomAuthTokenResult> => {
const jwt = await getAuthToken();
return { jwt, baseUrl: WISDOM_BASE_URL };
}, []);
return (
<WisdomProvider getAuthToken={resolveAuth}>
<DashboardView />
</WisdomProvider>
);
}When getAuthToken is set, the SDK does not call POST /auth-token — your callback is the sole token source. Throw from the callback to surface an auth error via useWisdomAuth().error. Wrap the callback in useCallback (or define it at module scope) so its identity is stable across renders; the provider re-fetches whenever the callback reference changes.
Theming
Pass a theme prop to <WisdomProvider> to control the colors, fonts, and chart palette used by SDK-rendered components (Dashboard, DashboardWidgets, DashboardFilters, WisdomChart). The theme is exposed to your own components via useWisdomTheme() so partner UI can stay visually consistent.
import { WisdomProvider, type WisdomTheme } from '@wisdomai/react';
const theme: WisdomTheme = {
primaryTextColor: '#ffffff',
secondaryTextColor: '#a0a0a0',
background: '#1a1a1a',
border: '#2a2a2a',
fontFamily: 'Inter, sans-serif',
chartColors: ['#4f46e5', '#10b981', '#f59e0b'],
};
<WisdomProvider theme={theme}>
<DashboardView />
</WisdomProvider>;fontFamily and chartColors are optional; everything else is required. If theme is omitted, a light default is used. The SDK does not ship light/dark presets — partner apps own their full color system.
You can pass theme as an inline object literal without causing chart re-renders on every parent render; the provider stabilizes its identity by content.
Dashboard filters
Interactive filtering (row-click, drawer, or programmatic) goes through setFilterValue(filterId, filter) from the dashboard context. Two parts of the contract are easy to get wrong:
filterId is the lookup key
setFilterValue matches the filterId against dashboard.filterSpecs[*].filterId exactly. A filterId that doesn't match any spec throws Filter not found on dashboard: <filterId>. The same filterId keys highlight/active-filter lookups, so it must be the spec's filterId verbatim — not the parameterName, displayName, or column name.
The columnRef vs column shape asymmetry
A FilterSpec exposes its column under spec.columnRefs[0] (a ColumnRef). But the serialized filter lhs reads the column from lhs.expression.flattened[0].column (a DataframeColumn) when building the GraphQL input. These are different shapes at different layers — the SDK maps between them in buildLhsFromSpec. If you hand-assemble an lhs, you must populate expression.flattened[0].column; leaving it empty throws a malformed lhs error naming the filterId (it does not silently render a blank chart).
Filtering a column the widgets don't query
A filter only changes a widget if that widget's underlying query references the filtered column (WidgetFilterStatus.WIDGET_FILTER_STATUS_ADDED). If you set a filter whose column no visible widget queries — common for chat-authored widgets whose SQL never selected the entity column — setFilterValue resolves successfully but nothing visibly changes. The SDK emits a console.warn in this case so the no-op is observable during development.
Errors
All errors thrown by the client extend WisdomError:
WisdomAuthError— JWT rejected or expired (HTTP 401/403, or GraphQLUNAUTHENTICATED/INVALID_TOKEN). Consumers should callrefresh()to re-fetch a token.WisdomDashboardNotFoundError—dashboards.get(id)could not return a dashboard because the ID does not exist or the caller lacks access (server emitsNOT_FOUNDorFORBIDDEN).WisdomError— everything else (network failure, 5xx, malformed response, uncategorized backend errors). Carries an optionalcauseand, for GraphQL-level failures, anerrorsarray.
Releasing
The SDK ships a frozen set of GraphQL operations to consumers who cannot re-run codegen, so a backend schema change that removes a field one of those operations uses will hard-fail every dashboard with HTTP 400. To guard against that, the published operation set is snapshotted to published-operations.graphql and the sdk-schema-compat CI gate validates it against the live app-server schema on every change.
When cutting a release, after bumping the version:
pnpm run sdk:snapshot-operations # from the javascript/ root — refreshes published-operations.graphqlCommit the refreshed snapshot alongside the version bump, then publish. Skipping this only weakens protection for newly-added operations; it never produces a false CI failure.
