@campfire-interactive/ai-chat
v0.4.2
Published
The platform AI chat panel for Campfire Suite apps: a docked, page-aware assistant opened from the header, answering from every app the user is granted, with typed answer blocks and confirm-to-write cards.
Keywords
Readme
@campfire-interactive/ai-chat
Private and unvetted until the pm exercise. Do not adopt it in an app yet; see CLAUDE.md.
The platform AI chat: an icon in the header opens a docked panel beside the
page. The chat knows which page is open, offers chips that fit it, answers with
typed blocks (prose, tables, charts, KPI tiles, links, buttons, questions,
citations), and proposes small changes as cards the user confirms. It talks to
the hub's /api/v1/assistant/* API directly with the user's token.
A live playground with a scripted hub is the showcase's AI chat page
(npm run showcase, then #ai-chat).
Entries
| Entry | What it holds |
|---|---|
| . | The wire-contract types and createAssistantClient (chatStream, confirmWrite, listActions). No UI. |
| /host | AiChatProvider, AiChatSurface, AiChatButton, useAiChat. |
Peers: react / react-dom 18 or 19, lucide-react (>= 0.460), recharts
2.15+ or 3. No CSS import: the stylesheet (cfi-ac-*, in
@layer components) is injected by the bundle. Charts load lazily.
Wiring a host (pm)
import { AiChatProvider, AiChatSurface, AiChatButton } from '@campfire-interactive/ai-chat/host';
// App.tsx: above <Routes>, beside <PmHelp>.
<AiChatProvider
app="pm"
hubUrl={aiEnabled ? runtime.aiHubBaseUrl : undefined} // undefined = chat off
getToken={getAccessToken}
uploadFile={uploadToDms} // pm's dms-client upload, as WorkAttachments does; returns { documentId }
pageContext={usePmPageContext()} // rebuilt on every navigation
starters={PM_STARTERS} // { 'program.risks': ['Which risks are overdue?'], … }
navigate={(link) => (link.app === 'pm' ? navigate(link.path) : (window.location.href = getAppUrl(link.app) + link.path))}
>
<Routes>…</Routes>
</AiChatProvider>
// AppShell.tsx
<ShellHeader rightActions={<AiChatButton />} … />
<div className="flex flex-1 overflow-hidden">
<SideNav … />
<main className="min-w-0 flex-1 overflow-auto">…</main>
<CampfireHelpSurfaces />
<AiChatSurface />
</div>AiChatButton and AiChatSurface render nothing when the provider has no
hubUrl, so a host gates only the provider.
Behavior worth knowing
- Session. The thread lives in React state; navigation and closing the panel keep it. A reload ends it. "Start over" clears the thread, the dropped files and the session id.
- History. Each request sends the last 8 turns as text. Blocks are reduced
to one-line traces; a confirmed write appends
Confirmed: <summary> (succeeded|failed)to that answer. - Starters are keyed by
pageContext.page('*'is the fallback).{placeholders}fill from the page's entities bytype, then fromfilters, then{title}. A fully filled chip sends; otherwise it loads into the composer. They show in the empty state, and again above the composer once the page differs from where the last question was asked. - Files are kept in memory under a
fileRefand offered on every request until attached.uploadFileruns only when the user confirms an attach card, immediately beforeconfirmWrite({ token, documentId }). - Writes. Cancel, or any new message, discards every pending write. Nothing is stored, so nothing needs cleaning up.
- Panel open state and width (360 to 900 px) are remembered per browser in
localStorageundercfi.ai-chat.<app>.*.
