Canvas Chat Bubble Guide¶
Use this guide to wire the ChatKit-powered chat bubble directly inside the Canvas workflow editor. Unlike the public embedding guide, this flow keeps every request authenticated with short-lived workflow JWTs so creators can test in context without leaving Canvas.
When to use this guide¶
- You are building or modifying the Canvas editor (
apps/canvas/src/features) and want an in-editor chat experience tied to the workflow on screen. - You can call the Canvas backend (FastAPI) to mint workflow-scoped ChatKit
sessions via
/api/workflows/{id}/chatkit/session. - You need to keep the UX consistent with the floating bubble pattern used across workflow nodes rather than embedding a standalone public page.
Prerequisites¶
- Feature flag – enable
chatkit_canvas_enabledfor the environment (via LaunchDarkly, env var, or your preferred flag service). Keep the flag off in production until QA is complete. - Authenticated Canvas session – the bubble uses the creator’s bearer
access token to call
/api/workflows/{id}/chatkit/session. Anonymous users are not supported. - Saved workflow ID – session issuance fails unless the workflow has been
saved and has a stable
workflowId. The hook surfaces the error “Save the workflow before opening ChatKit.” when no ID exists (use-workflow-chat.ts:68-77). - Backend reachability – ensure the editor can reach the backend base URL
returned by
getBackendBaseUrl()(typicallyhttp://localhost:2025in dev) so JWT refresh calls succeed. - IdP login (local or hosted) – configure Canvas OIDC settings
(
VITE_ORCHEO_AUTH_ISSUER,VITE_ORCHEO_AUTH_CLIENT_ID, and optionallyVITE_ORCHEO_AUTH_AUDIENCE) plus backend JWT validation (ORCHEO_AUTH_JWKS_URL,ORCHEO_AUTH_AUDIENCE,ORCHEO_AUTH_ISSUER). The Canvas login screen now redirects to the IdP instead of using the dev-login shim. - ChatKit signing key – set
ORCHEO_CHATKIT_TOKEN_SIGNING_KEY(HS or RSA private key material) so the backend can mint workflow-scoped ChatKit session JWTs. Without this key,/api/workflows/{id}/chatkit/sessionreturns503 Service Unavailablewith the hint “Set CHATKIT_TOKEN_SIGNING_KEY to enable ChatKit session issuance.”
Component map¶
| Path | Responsibility |
|---|---|
apps/canvas/src/features/workflow/pages/workflow-canvas/hooks/use-workflow-chat.ts |
Manages chat state, issues sessions, and exposes handlers for node interactions. |
apps/canvas/src/features/chatkit/components/canvas-chat-bubble.tsx |
Floating FAB + modal that lazy-loads ChatKit and renders loading/error states. |
apps/canvas/src/features/chatkit/lib/workflow-session.ts |
Thin client for /api/workflows/{id}/chatkit/session (fetch + TTL parsing). |
apps/canvas/src/features/shared/components/chat-interface-options.ts |
Normalizes UseChatKitOptions, header text, greetings, and handler composition. |
apps/canvas/src/features/workflow/pages/workflow-canvas/components/workflow-canvas-layout.tsx |
Mounts CanvasChatBubble and forwards chat props gathered from the hook. |
Implementation steps¶
1. Initialize workflow chat state¶
Call useWorkflowChat from the Canvas workflow controller to obtain everything
the bubble needs: open/close handlers, telemetry hooks, JWT refresh logic, and
node bindings. See use-workflow-canvas-core.ts:131-189 for the reference
implementation.
const chat = useWorkflowChat({
nodesRef: history.nodesRef,
setNodes: history.setNodes,
workflowId,
backendBaseUrl: getBackendBaseUrl(),
userName: user.name,
});
The hook:
- Refreshes JWT-backed ChatKit sessions with automatic buffering before expiry
(SESSION_REFRESH_BUFFER_MS, use-workflow-chat.ts:25-66).
- Records telemetry for open/close/session events (use-workflow-chat.ts:129-190).
- Keeps per-node status in sync so the canvas reflects running/success states.
2. Attach chat triggers to workflow nodes¶
useWorkflowChat exposes attachChatHandlerToNode, which injects onOpenChat
into every chatTrigger node (use-workflow-chat.ts:247-258). Run your node
list through this helper before rendering so clicking a chat-capable node opens
the bubble.
const convertPersistedNodesToCanvas = useCallback(
(persisted: PersistedWorkflowNode[]) =>
persisted
.map((node) => toCanvasNodeBase(node))
.map(chat.attachChatHandlerToNode),
[chat.attachChatHandlerToNode],
);
3. Render the Canvas chat bubble¶
Mount CanvasChatBubble near the root of the layout and pass the values returned
from the hook (workflow-canvas-layout.tsx:120-190).
{chat && (
<CanvasChatBubble
title={chat.chatTitle}
user={chat.user}
ai={chat.ai}
workflowId={chat.workflowId}
sessionPayload={{
workflowId: chat.workflowId,
workflowLabel: chat.chatTitle,
chatNodeId: chat.activeChatNodeId,
}}
backendBaseUrl={chat.backendBaseUrl}
getClientSecret={chat.getClientSecret}
sessionStatus={chat.sessionStatus}
sessionError={chat.sessionError}
onRetry={chat.refreshSession}
onResponseStart={chat.handleChatResponseStart}
onResponseEnd={chat.handleChatResponseEnd}
onClientTool={chat.handleChatClientTool}
onDismiss={chat.handleCloseChat}
onOpen={() => chat.setIsChatOpen(true)}
isExternallyOpen={chat.isChatOpen}
/>
)}
CanvasChatBubble handles:
- Floating action button + modal chrome, including minimap avoidance logic and
skeleton placeholders (canvas-chat-bubble.tsx:70-320).
- Lazy-loading ChatKitSurface only after the user opens the panel to keep the
editor light.
- Displaying loader/error states while JWTs are being created or retried.
4. Supply ChatKit options and JWT refresh logic¶
Inside the bubble, useChatInterfaceOptions injects everything required by the
ChatKit SDK (canvas-chat-bubble.tsx:179-209,
chat-interface-options.ts:100-173):
- getClientSecret is wired to the hook’s refreshSession, which calls
requestWorkflowChatSession on the backend (workflow-session.ts:35-68).
- The helper auto-builds the header, optional greeting, composer placeholder,
and event handlers. You can merge additional chatkitOptions (e.g., disabling
history or customizing the composer) before they reach ChatKitSurface.
5. Handle advanced interactions (client tools, telemetry, dismissal)¶
- Client tools: The bubble forwards
onClientToolcalls so tools likeorcheo.run_workflowcan route throughhandleChatClientTool(use-workflow-chat.ts:192-244), which hits/api/chatkit/workflows/{workflowId}/triggerfor background runs. - Telemetry: Calls to
recordChatTelemetryalready exist in the hook and bubble (canvas-chat-bubble.tsx:147-168,use-workflow-chat.ts:129-150). Extend the helper to add new event names rather than inlining analytics. - Dismissal state: Use
onDismissto keep other UI (e.g., tutorial panels) in sync and prevent orphaned open states.
Testing & troubleshooting¶
- JWT failures (401/403) – make sure the Canvas session cookie is present
when calling
/api/workflows/{id}/chatkit/session. In dev, runmake dev-serverso both backend and frontend share the same origin. ChatKit session response missing client secret.– indicates backend configuration drift; inspect the FastAPI logs for signature errors or missingORCHEO_AUTH_JWT_SECRET.- Bubble never opens – confirm
attachChatHandlerToNoderan on the nodes and thatchat.isChatOpentoggles when clicking the FAB or node CTA. - Workflow switch stale state – the hook resets sessions whenever the
workflowIdchanges (use-workflow-chat.ts:50-56). If you manage IDs manually, update the hook inputs accordingly. - Minimap overlap – the bubble automatically offsets itself relative to the
minimap (
canvas-chat-bubble.tsx:26-145). If you customize the minimap DOM, updateMINIMAP_SELECTOR.
References¶
- Component source:
apps/canvas/src/features/chatkit/components/canvas-chat-bubble.tsx - Workflow chat hook:
apps/canvas/src/features/workflow/pages/workflow-canvas/hooks/use-workflow-chat.ts - Session helper:
apps/canvas/src/features/chatkit/lib/workflow-session.ts - Shared ChatKit options:
apps/canvas/src/features/shared/components/chat-interface-options.ts - Public embedding reference:
docs/chatkit_integration/webpage_embedding_guide.md