The Chat Widget#

Warning

Project Neo is currently in Technical Preview. Features described in this section may change before general availability.

Your bundle can embed the same conversational GenAI experience the core Squirro application ships: a full chat panel that streams answers with citations, footnotes, thinking steps, and interrupt handling, backed by the GenAI service of the project. This page covers embedding the widget, the data source and its authentication prerequisite, and how to theme, localize, and gate it.

The widget is exported from @squirro/neo-core/chat. The bundle scaffold also installs a squirro-chat skill under .claude/skills/ with the full API reference and patterns, which is a useful companion when building with Claude Code.

What It Is#

Two components work together:

  • ChatProvider

    Owns one instance-scoped bundle of chat state: the conversation, streaming, agents, and interrupts. Everything a chat surface needs is read from it.

  • ChatPanel

    The embeddable widget itself, made up of the conversation thread and the composer. It reads its state from the surrounding provider, so you can drop it in without passing props.

Note

One ChatProvider corresponds to one conversation. Two ChatPanel components under the same provider share a single conversation. To run two independent chats on a page, mount two providers.

The panel deliberately does not include a conversation-list sidebar or any URL syncing. Those belong to the surface that mounts the provider.

The Data Source and Authentication#

The chat widget talks to /service/genai through a data source built with createChatDataSource(). Authentication works the same way as for the rest of the Squirro API, described on the Squirro API page:

  • During local development

    The dev proxy injects a valid access token into every request.

  • In production

    The Squirro backend authenticates the request via session cookies on the same origin.

Called with no arguments, createChatDataSource() reuses the authentication and project-scoping bindings registered by the host, which are the same ones useSquirroApi() uses. No manual token handling is required, and the same code path works in both environments.

Embedding the Widget#

ChatProvider needs a dataSource, project-scoped queryKeys, and a getProjectId accessor. A standalone bundle panel owns neither a conversation-list sidebar nor a host rejoin service, so the cacheUtils and observers props default to built-in no-ops and you do not pass them at all.

import {
  ChatPanel,
  ChatProvider,
  createChatDataSource,
  createGenaiQueryKeys,
} from '@squirro/neo-core/chat';
import { useSquirroApi } from '@squirro/neo-core/api';
import { useCallback, useMemo } from 'react';

export default function ChatDashboard() {
  const api = useSquirroApi();
  const getProjectId = useCallback(() => api.projectId, [api]);
  const dataSource = useMemo(() => createChatDataSource(), []);
  const queryKeys = useMemo(() => createGenaiQueryKeys(getProjectId), [getProjectId]);

  return (
    <ChatProvider
      dataSource={dataSource}
      queryKeys={queryKeys}
      getProjectId={getProjectId}
    >
      <div className="h-full min-h-0">
        <ChatPanel welcomeContent={<p className="text-muted-foreground">Ask a question to start.</p>} />
      </div>
    </ChatProvider>
  );
}

Four points are worth calling out:

  • Memoize the data source and query keys

    ChatProvider rebuilds its store bundle when the identity of those props changes, so keep the references stable.

  • Size the container

    Give the panel a sized container with min-h-0 so its internal scroll area lays out correctly inside a flex dashboard card.

  • Query client

    Dashboards render inside the React Query provider of the host, so the GenAI hooks resolve their client automatically. You do not need to supply one.

  • No sidebar wiring

    cacheUtils and observers are optional and default to no-ops. Supply them only if you build your own conversation-list sidebar or rejoin service.

Theming, Strings, and Capabilities#

The widget uses the same theme tokens and typography as the rest of the application, so it picks up the active theme with no extra work. For more information, see the Styling and Components page.

Four optional ChatProvider props tailor the rest:

Prop

Type

Purpose

strings

Partial<ChatStrings>

Overrides user-facing text. Only the keys you pass change. The rest fall back to the English defaultChatStrings. Wire this to your bundle translations, described on the Translations page.

locale

string

BCP-47 tag, for example de, used for dates interpolated into those strings. Without it, a date follows the browser locale rather than the language of your interface. Omit the prop to keep the browser locale.

capabilities

ChatCapabilities

Feature gates. inlineActions turns on the selection actions offered on answer text, such as Explain and Translate. Flags you omit stay off. Turning inlineActions on requires the onExecuteSelectionStream callback.

callbacks

ChatCallbacks

Host hooks: onNavigateToItem, onNotify, onOpenSidePanel, onSubmitFeedback, and onExecuteSelectionStream.

For example, to localize a label and route document clicks into your own detail view:

<ChatProvider
  dataSource={dataSource}
  queryKeys={queryKeys}
  getProjectId={getProjectId}
  strings={{ codeBlockCopy: 'Copy code' }}
  callbacks={{
    onNotify: (kind, message) => toast[kind](message),
    onNavigateToItem: (itemId) => openItemDetail(itemId),
  }}
>
  <ChatPanel />
</ChatProvider>

Note

Closing a side panel is a separate top-level prop, onCloseSidePanel, and is not part of callbacks.

Next Steps#

Topic

Guide

Wiring the panel into a dashboard entry

The Bundle Manifest

Calling the Squirro API and how authentication is wired

Squirro API

UI components, icons, and theme tokens

Styling and Components

Overriding text per language

Translations

Live preview and prop table for the panel

neo-catalog.squirro.com

Full chat API reference

The squirro-chat skill under .claude/skills/ in your bundle