Skip to content

Headless hooks ​

All five Pensieve data surfaces are available as plain React hooks that return raw data, loading state, and imperative actions. Use them when you want to build a custom UI on top of the Pensieve data layer without the bundled component chrome.

All hooks must be called inside a PensieveProvider.


usePensieveGraph ​

ts
import { usePensieveGraph } from "@pensieve-ai/react";
import type { UsePensieveGraphArgs, UsePensieveGraphResult } from "@pensieve-ai/react";

Loads one or more (database, graph) pairs in parallel via React Query, merges them into a unified node/edge set, and exposes expansion and search helpers.

ts
function usePensieveGraph(args?: UsePensieveGraphArgs): UsePensieveGraphResult

UsePensieveGraphArgs

FieldTypeDefaultDescription
graphsArray<{ database?: string; graph: string }>—Explicit list of graph coordinates.
discover"all-databases"—Discover all graphs across all databases (catalog fan-out). Overrides graphs.
realmstring—Passed to getOverview for realm-scoped fetches.
limitnumber800Max nodes per graph request.

UsePensieveGraphResult

FieldTypeDescription
nodesGraphNode[]Merged nodes from all loaded graphs, deduplicated by composite id.
edgesGraphRelationship[]Merged edges from all loaded graphs, deduplicated.
statsGraphStats | nullAggregate counts (total nodes/edges, per-label and per-type breakdowns).
namespaceCountsRecord<string, number>Node count per database/graph namespace key.
coordsGraphCoord[]Resolved (database, graph) coordinates being loaded.
progressGraphProgress{ total, settled, hasAny } — per-namespace fetch progress.
isLoadingbooleanTrue while discovery or any graph fetch is pending.
isErrorbooleanTrue when all fetches have settled and no data arrived.
errorunknownFirst error from any fetch, or null.
expandNode(compositeId: string) => Promise<void>Fetch and append the neighbours of a node identified by its composite id (database/graph::nodeId).
searchNodes(text: string) => Promise<SearchHits>Search nodes in the first loaded graph by text.
refetch() => voidInvalidate all graph query cache entries and refetch.

Custom graph UI example

tsx
import { usePensieveGraph } from "@pensieve-ai/react";

function GraphStats() {
  const { nodes, edges, isLoading, error } = usePensieveGraph({
    discover: "all-databases",
  });

  if (isLoading) return <p>Loading…</p>;
  if (error) return <p>Error: {String(error)}</p>;

  return (
    <ul>
      <li>{nodes.length} nodes</li>
      <li>{edges.length} edges</li>
    </ul>
  );
}

usePensieveQuery ​

ts
import { usePensieveQuery } from "@pensieve-ai/react";
import type { PensieveQueryArgs, UsePensieveQueryResult } from "@pensieve-ai/react";

Runs KQL or SQL queries via POST /v1/query (NDJSON streaming). Accumulates columns and rows as chunks arrive. An AbortController per execute() call allows mid-stream cancellation; unmount aborts automatically.

ts
function usePensieveQuery(): UsePensieveQueryResult

UsePensieveQueryResult

FieldTypeDescription
columnsColumn[]Columns from the last/current query (name + kind).
rowsRecord<string, unknown>[]Accumulated rows from the last/current query.
isRunningbooleanTrue while the stream is active.
errorunknownError from the last execute(), or null.
execute(args: PensieveQueryArgs) => Promise<void>Run a query. Aborts any previously running query. Resolves when the stream completes.
cancel() => voidAbort the current in-flight query. No-op if idle.

PensieveQueryArgs

FieldTypeDescription
databasestringDatabase to query.
querystringKQL or SQL query text.
language"kql" | "sql"Query language.
walMsnumber?Optional WAL durability hint.
memBytesnumber?Optional memory budget hint.

Custom query UI example

tsx
import { usePensieveQuery } from "@pensieve-ai/react";

function LiveQuery() {
  const { rows, isRunning, execute, cancel } = usePensieveQuery();

  return (
    <div>
      <button onClick={() => execute({ database: "prod", query: "errors | take 20", language: "kql" })}>
        Run
      </button>
      {isRunning && <button onClick={cancel}>Cancel</button>}
      <pre>{JSON.stringify(rows.slice(0, 5), null, 2)}</pre>
    </div>
  );
}

usePensieveDiscover ​

ts
import { usePensieveDiscover } from "@pensieve-ai/react";
import type { UsePensieveDiscoverResult } from "@pensieve-ai/react";

Streaming discover search (POST /v1/explore/search) that yields typed frames (plan, source_progress, rows, histogram, source_done, done, etc.). Use this to build a custom log/event browsing UI.

ts
function usePensieveDiscover(): UsePensieveDiscoverResult

UsePensieveDiscoverResult

FieldTypeDescription
framesFrame[]All frames received from the current/last search stream.
isStreamingbooleanTrue while the stream generator is running.
errorunknownError from the last search(), or null.
search(req: SearchRequest) => Promise<void>Start a new search. Aborts any running search first.
abort() => voidAbort the current in-flight stream. No-op if idle.

Custom discover UI example (from examples/embed-demo headless view)

tsx
import { usePensieveDiscover } from "@pensieve-ai/react";

function DiscoverHeadless() {
  const { frames, isStreaming, search, abort } = usePensieveDiscover();

  const rows = frames
    .filter((f) => f.type === "rows")
    .flatMap((f) => f.rows ?? []);

  return (
    <div>
      <button onClick={() => search({ query: "level == \"error\"", databases: ["prod"] })}>
        Search errors
      </button>
      {isStreaming && <button onClick={abort}>Stop</button>}
      <p>{rows.length} rows</p>
    </div>
  );
}

usePensieveDashboards ​

ts
import { usePensieveDashboards } from "@pensieve-ai/react";
import type { UsePensieveDashboardsResult } from "@pensieve-ai/react";

React Query wrapper over client.dashboards.listDashboards(). Exposes the dashboard list and a getDashboard(id) helper for on-demand detail loads. CRUD mutations (create, update, delete) are intentionally not in this hook — drive them directly via usePensieveClient().dashboards.*.

ts
function usePensieveDashboards(): UsePensieveDashboardsResult

UsePensieveDashboardsResult

FieldTypeDescription
dashboardsDashboard[]All dashboards for the current database.
isLoadingbooleanTrue while the list is loading.
errorunknownError from the list fetch, or null.
getDashboard(id: string) => Promise<DashboardWithPanels>Fetch a single dashboard with panels. Uses the React Query cache when available.
refetch() => voidInvalidate and refetch the dashboard list.

usePensieveAgent ​

ts
import { usePensieveAgent } from "@pensieve-ai/react";
import type { UsePensieveAgentArgs, UsePensieveAgentResult, AgentMessage, AgentStatus } from "@pensieve-ai/react";

Headless agent hook over POST /v1/agent/ask (SSE stream, Vercel AI UI Message Stream v1 protocol). Manages the message list, streaming state, and multi-turn session resume internally.

ts
function usePensieveAgent(args?: UsePensieveAgentArgs): UsePensieveAgentResult

UsePensieveAgentArgs

FieldTypeDescription
databasestring?Database sent with each ask request. Defaults to the transport's database.
includeThinkingboolean?Request extended thinking tokens (model-dependent).

UsePensieveAgentResult

FieldTypeDescription
messagesAgentMessage[]All messages in the conversation (user + assistant).
statusAgentStatus"idle" | "streaming" | "error"
send(text: string) => Promise<void>Send a user message and stream the assistant reply. Aborts any prior in-flight stream.
stop() => voidAbort the current stream. No-op if idle.

AgentMessage

ts
interface AgentMessage {
  id: string;
  role: "user" | "assistant";
  text: string;                  // completed text
  streamingText?: string;        // partially accumulated while streaming
}

Custom agent UI example

tsx
import { usePensieveAgent } from "@pensieve-ai/react";

function ChatWidget() {
  const { messages, status, send } = usePensieveAgent();
  const [input, setInput] = useState("");

  return (
    <div>
      {messages.map((m) => (
        <p key={m.id}><strong>{m.role}</strong>: {m.streamingText ?? m.text}</p>
      ))}
      <input value={input} onChange={(e) => setInput(e.target.value)} />
      <button disabled={status === "streaming"} onClick={() => { send(input); setInput(""); }}>
        Send
      </button>
    </div>
  );
}

usePensieveCapabilities ​

ts
import { usePensieveCapabilities } from "@pensieve-ai/react";

React Query wrapper over GET /v1/capabilities. Returns the server's feature flag set. Used internally by CapabilityGate (e.g. to guard PensieveAgentChat).

ts
function usePensieveCapabilities(): UseQueryResult<Capabilities>

Capabilities is a flat object with boolean fields (mode, data_sources, credentials, oauth, saved_views, users_admin, explore_live). staleTime is 5 minutes.


usePensieveClient ​

ts
import { usePensieveClient } from "@pensieve-ai/react";
import type { PensieveContextValue } from "@pensieve-ai/react";

Returns the PensieveClient from context. Use this to call the full client API (graph, query, discover, dashboards, data sources, etc.) from any component inside the provider.

tsx
import { usePensieveClient } from "@pensieve-ai/react";

function ListDatabases() {
  const client = usePensieveClient();

  useEffect(() => {
    client.catalog.fetchSchema().then((schema) => {
      console.log(schema.databases.map((d) => d.name));
    });
  }, [client]);
}

An open-source project · MIT licensed.