Skip to content

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

  1. Feature flag – enable chatkit_canvas_enabled for the environment (via LaunchDarkly, env var, or your preferred flag service). Keep the flag off in production until QA is complete.
  2. Authenticated Canvas session – the bubble uses the creator’s bearer access token to call /api/workflows/{id}/chatkit/session. Anonymous users are not supported.
  3. 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).
  4. Backend reachability – ensure the editor can reach the backend base URL returned by getBackendBaseUrl() (typically http://localhost:2025 in dev) so JWT refresh calls succeed.
  5. IdP login (local or hosted) – configure Canvas OIDC settings (VITE_ORCHEO_AUTH_ISSUER, VITE_ORCHEO_AUTH_CLIENT_ID, and optionally VITE_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.
  6. 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/session returns 503 Service Unavailable with 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 onClientTool calls so tools like orcheo.run_workflow can route through handleChatClientTool (use-workflow-chat.ts:192-244), which hits /api/chatkit/workflows/{workflowId}/trigger for background runs.
  • Telemetry: Calls to recordChatTelemetry already 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 onDismiss to 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, run make dev-server so 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 missing ORCHEO_AUTH_JWT_SECRET.
  • Bubble never opens – confirm attachChatHandlerToNode ran on the nodes and that chat.isChatOpen toggles when clicking the FAB or node CTA.
  • Workflow switch stale state – the hook resets sessions whenever the workflowId changes (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, update MINIMAP_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