Tour Integration
Connect AI chat to active tour state: context-aware assistance that knows the current step, tour progress, and user actions
Tour integration is the differentiator for @tour-kit/ai. A generic chat widget answers "how do I save a file?" — the integrated assistant answers "you are on step 3 of the Welcome tour, the Save button is the blue icon in the top-right, here's why you need to click it now." This page covers the wiring, the three highest-value use cases, and the failure modes when tour state and AI context get out of sync.
What useTourAssistant gives you on top of useAiChat
useTourAssistant extends useAiChat with three additions:
tourContext— a structured snapshot of the active tour (id, name, current step index, total steps, completed tours, checklist progress). Re-assembled on every render.askAboutStep()— sends a pre-built prompt asking the assistant to explain the current step. No-op when there is no active step.askForHelp(topic?)— sends a help prompt with the current tour context attached, optionally scoped to a topic.
Everything from useAiChat (messages, sendMessage, status, open, close, toggle) is still available on the same return value — useTourAssistant is a superset.
Wiring it up (3-line change from useAiChat)
import { useTourAssistant } from '@tour-kit/ai'
function HelpButton() {
const { askAboutStep, tourContext } = useTourAssistant()
const stepLabel = tourContext.activeStep?.title
return (
<button onClick={askAboutStep} disabled={!tourContext.activeStep}>
{stepLabel ? `Explain "${stepLabel}"` : 'Pick a tour first'}
</button>
)
}This works because the @tour-kit/react TourProvider exposes the active tour via context, and useTourAssistant reads from it automatically — no prop drilling.
The three use cases that justify tour integration
1. "Explain this step" — confused users mid-tour
function StepExplainer() {
const { askAboutStep, tourContext, messages } = useTourAssistant()
if (!tourContext.activeTour) return null
return (
<aside>
<p>
Step {tourContext.activeTour.currentStep + 1} of {tourContext.activeTour.totalSteps}
{tourContext.activeStep ? ` — ${tourContext.activeStep.title}` : null}
</p>
<button type="button" onClick={askAboutStep}>
I don't understand this step
</button>
<ul>
{messages.map((m) => (
<li key={m.id}>{m.content}</li>
))}
</ul>
</aside>
)
}The prompt that askAboutStep sends includes the step's id, title, and content, so the model can give a grounded answer without the user having to retype anything.
2. "What does this button do?" — context-aware UI explainer
Combine useTourAssistant with a tooltip or right-click handler so any UI element can be "explained" with the current tour as context.
function ExplainableButton({ label, action, children }: Props) {
const { sendMessage, tourContext, open } = useTourAssistant()
const tourId = tourContext.activeTour?.id ?? 'no-tour'
return (
<button
onClick={action}
onContextMenu={(e) => {
e.preventDefault()
open()
sendMessage({
text: `In the context of the ${tourId} tour, what does the "${label}" button do?`,
})
}}
>
{children}
</button>
)
}Right-clicking the button opens the chat and pre-populates a context-aware question.
3. "Skip to step N" — assistant as a navigation surface
Combine with useTour from @tour-kit/react to let the assistant drive tour navigation in response to user requests:
import { useTour } from '@tour-kit/react'
import { useTourAssistant } from '@tour-kit/ai'
function SkipToStep() {
const { sendMessage, tourContext } = useTourAssistant()
const { goToStep } = useTour(tourContext.activeTour?.id ?? '')
return (
<button
onClick={() =>
sendMessage({
text: 'Skip me to the most important step in this tour.',
})
}
>
Take me to the important part
</button>
)
}(For the model's response to actually drive goToStep, you need tool calling — see the AI SDK tool-calling docs.)
How tour context flows end-to-end
┌──────────────────────────┐
│ TourProvider │ active tour state
│ (@tour-kit/react) │ (id, currentStep, ...)
└──────────────────────────┘
│
▼
┌──────────────────────────┐
│ useTourAssistant │ reads via context,
│ │ builds TourAssistantContext
└──────────────────────────┘
│
▼ every sendMessage()
┌──────────────────────────┐
│ AiChatProvider │ serializes tourContext into the
│ config.tourContext=true │ POST body alongside the messages
└──────────────────────────┘
│
▼
┌──────────────────────────┐
│ /api/chat route handler │ injects tourContext into the
│ createChatRouteHandler │ system prompt
└──────────────────────────┘If you forget to set tourContext: true on the provider, the hook works fine for typing but the server never sees the tour state — the most common failure mode.
Tour context shape
interface TourAssistantContext {
activeTour: {
id: string
name: string
currentStep: number // zero-indexed
totalSteps: number
} | null
activeStep: {
id: string
title: string
content: string
} | null
completedTours: string[]
checklistProgress: { completed: number; total: number } | null
}activeTour and activeStep are null when no tour is running. Always guard before using.
Manual assembly for tests
You don't need a TourProvider to build a TourAssistantContext — assembleTourContext accepts a tour-state object directly. Useful in tests or in custom hosts:
import { assembleTourContext } from '@tour-kit/ai'
const ctx = assembleTourContext({
isActive: true,
tourId: 'onboarding',
tour: { id: 'onboarding', name: 'Onboarding Tour', steps: [] },
currentStepIndex: 2,
totalSteps: 5,
currentStep: { id: 'step-3', title: 'Add your team', content: 'Invite team members.' },
completedTours: [],
})Returns null-shaped context when isActive is false or tourState is missing.
Combining with CAG vs RAG
Tour integration works with both retrieval strategies — pick CAG or RAG based on documentation size (see CAG Guide and RAG Guide). The client config is identical in both cases:
<AiChatProvider config={{ endpoint: '/api/chat', tourContext: true }}>
<YourApp />
</AiChatProvider>The tour context is sent as structured JSON alongside the messages, so it composes with either retrieval strategy on the server.
Failure modes
tourContext: truenot set on provider. Hook returns valid data, but the server never sees it. Symptom: assistant answers generically, ignoring tour state.- Step content too large. If a step's
contentis itself a long-form React tree, the serialized version sent to the model can blow past the context window. Either trim the content sent to the model or store the explainer text separately. - Stale context after a step jump.
useTourAssistantreads on render. If you advance the tour imperatively (e.g. inside asetTimeout), make sure the React tree re-renders before sending the next message, or pulltourContextfresh each time. - Multi-tour ambiguity. If two tours are active at once (rare but possible with overlapping providers),
tourContext.activeTourreflects the innermost provider. Scope explicitly with a separateuseTour(tourId)call if you need a specific one.
Next steps
- Hooks — full
useTourAssistantsignature and the rest of the public hook surface - CAG Guide — server-side wiring for small docs
- RAG Guide — server-side wiring for large docs
- API Reference —
TourAssistantContext,assembleTourContext, and provider config
Ship onboarding, not config.
npm i @tour-kit/core is MIT and free. The Pro packages work unlicensed too — a one-time $99 license removes the production watermark when you ship.
MIT-licensed — no signup, no credit card. Pay once, only when you ship.