08-portals-and-i18nwave: W5filled13 citations

Ask Phoenix — Public Knowledge-Base Chat Assistant

Audiences: investor, client, partner, developer

Ask Phoenix — Public Knowledge-Base Chat Assistant

Ask Phoenix is the public-facing chat surface that lets anyone — investor, client, partner, developer — query the Dudoxx HMS knowledge base in natural English / German / French and get cited answers grounded in the 12-layer technical corpus. Available as a floating side panel on every /docs/[layer]/[topic] page and as a full-page chat at /docs/ask.

Business Purpose

The HMS knowledge base contains ~100 topic pages spread over 12 architectural layers. Static documentation works for users who already know what to look for — but most prospects, clients, and partners do not. Ask Phoenix lowers the discovery cost from "browse 12 sections" to "type a question". It produces answers with clickable citation chips that deep-link to the exact KB topic + heading the claim came from — so the chat surface accelerates discovery without replacing the structured docs.

Three concrete use cases:

  1. Investor due diligence: "How does FHIR partitioning work?" returns a short grounded answer with chips into [02-data-and-fhir/fhir-partitions-tenancy].
  2. Partner onboarding: "What components must I change to add a new TUCAN tool?" returns multi-step guidance with chips into ai-tucan layer topics.
  3. Sales conversations: "Why does ddx-web never talk to HAPI FHIR directly?" returns the security rationale with chips into [03-security-and-tenants/...].

Audiences

  • Investor: Ask Phoenix is the public demo of the Dudoxx KB. Try it at https://<host>/en/docs/ask. Every answer cites its sources — the assistant cannot fabricate. Refusal-class questions ("What is OpenAI's pricing for GPT-5?", "What is Dudoxx's revenue?") are politely declined, not hallucinated.
  • Client / partner: Self-service onboarding. Skim a KB topic, click the Phoenix launcher in the page chrome, ask a follow-up question. The panel preserves chat state across topic navigations within the same session.
  • Developer: Ask Phoenix shares the same Mastra agent runtime (streamPhoenix in ddx-web/src/mastra/agents/phoenix.ts:84) as the internal TUCAN system. To extend the assistant's knowledge, ingest new topic chunks into the dudoxx_kb_techdocs Qdrant collection; the chat surface picks them up immediately — no front-end change needed.

Architecture

Rendering diagram…

Public KB surfaces

RouteRenders
/[locale]/docs/[layer]/[topic]topic reader page — KbShellPhoenixLauncher button in page chrome · AskPhoenixPanel (createPortal floating drawer)
/[locale]/docs/askfull-page chat surface — PhoenixFullPageChat (centered chat column)

/api/phoenix/chat (POST) — validate request · streamPhoenix({ messages, env })Response (SSE)

Mastra agent: Phoenix

SettingValue
modeldudoxx-gemma, temp 0.3, enable_thinking: false
toolskb-plan (private), kb-search (cited)
stopWhensteps.length >= 8

UI surfaces

AskPhoenixPanel (ddx-web/src/components/kb/AskPhoenixPanel.tsx:37)

  • Floating side panel mounted via createPortal.
  • Triggered by KbShellPhoenixLauncher button in the topic reader chrome.
  • Shows "Planning…" chip when the agent is in the hidden kb-plan step (PhoenixToolPart short-circuits tool-kb-plan to a chip-only render).
  • Shows expandable "Searching…" chip for each kb-search call.
  • Renders the final answer via PhoenixAnswerMarkdownRenderer (same chrome as topic pages — code/mermaid/admonitions look identical).
  • "Open full page" link routes to /docs/ask preserving the conversation state via search params.

PhoenixFullPageChat (ddx-web/src/components/kb/PhoenixFullPageChat.tsx:34)

  • Centered chat column (max-w-prose) for distraction-free reading.
  • Hero header instead of drawer chrome.
  • Routed at /[locale]/docs/ask via ddx-web/src/app/[locale]/docs/ask/page.tsx:27.
  • Same Mastra agent backend as the side panel — only the layout differs.

PhoenixToolPart (ddx-web/src/components/kb/PhoenixToolPart.tsx:42)

  • Renders each tool-call step in the chat transcript.
  • case 'kb-plan' → chip-only render with t('kb.phoenix.planning') (i18n keys in src/locales/{en,de,fr}/kb.json: "Planning…" / "Plane…" / "Planification…"). Never expands kb-plan args into the visible message — that would leak the hidden plan and inflate assistant_text length (AC-10 invariant).
  • case 'kb-search' → expandable chip showing the query and top hits.

PhoenixAnswer (ddx-web/src/components/kb/PhoenixAnswer.tsx:67)

  • Markdown renderer for the assistant message.
  • Detects [layer/slug] citation chips and converts them into clickable <Link> elements pointing to /[locale]/docs/[layer]/[slug].
  • Reuses MarkdownRenderer from the topic-page surface so code blocks, admonitions, and tables share visual chrome.

Tech Stack & Choices

LayerChoiceRationale
Client streamingAI SDK v6 useChat + DefaultChatTransportAuto-handles SSE reconnect, message ordering, tool-call streaming
API routeNext.js 16 route handler (/api/phoenix/chat)Lives in ddx-web (port 6200). Browser never talks to ddx-api here — the Mastra agent runs in Next.js
Agent backendMastra agent in ddx-web (src/mastra/agents/phoenix.ts)Same Mastra runtime as TUCAN — one source of truth for AI orchestration
Vector storeQdrant dudoxx_kb_techdocsCurated technical-docs collection. Re-ingested from ddx-presentation/knowledge-base/ content
MarkdownMarkdownRenderer reused from topic-page surfaceVisual consistency between static docs and chat answers
i18nnext-intl with kb.* namespaceEN/DE/FR all carry kb.phoenix.planning, etc. Locales in lockstep
Citations[layer/slug] chip syntax detected client-sideLightweight — no per-token parsing required. The agent emits chips verbatim from kb-search results

Why a side panel AND a full-page chat?

Side panel preserves topic context (the user keeps reading the docs while asking follow-up questions). Full-page chat is the discovery entrypoint — linked from the landing page / sales materials, no topic context required. Both share the same Mastra agent and the same /api/phoenix/chat route, so there is one place to fix any Phoenix behaviour.

Data Flow

#Step
1User opens /en/docs/02-data-and-fhir/fhir-partitions-tenancy.
2Clicks KbShellPhoenixLauncherAskPhoenixPanel mounts via createPortal.
3User types "How does the multi-tenant data isolation work?"
4useChat POSTs to /api/phoenix/chat with messages array.
5Server-side: streamPhoenix → first step is the hidden kb-plan tool call: { question: '...', sub_questions: ['How is FHIR partitioning implemented?', 'How is data isolated between tenants in HAPI FHIR?'] }. PhoenixToolPart receives a tool-kb-plan stream event → renders chip 'Planning…' (or 'Plane…' / 'Planification…' depending on locale).
6Then 2 kb-search calls fire — one per sub_question. PhoenixToolPart renders 'Searching…' chips with expandable detail.
7Final answer streams in as text-delta chunks. PhoenixAnswer renders the markdown, converts [02-data-and-fhir/fhir-...] into clickable links to the actual topic pages.
8User clicks a citation chip → navigates to the cited topic page.
9The side panel persists; the user can continue the conversation about the new topic with full context.

Implicated Code

Mandatory ≥3 file:line citations.

  • ddx-web/src/app/[locale]/docs/ask/page.tsx:27PhoenixAskPage server component. Builds metadata via generateMetadata (line 16), renders <PhoenixFullPageChat /> client component.
  • ddx-web/src/components/kb/PhoenixFullPageChat.tsx:34export function PhoenixFullPageChat(). Wires useChat({ api: '/api/phoenix/chat', transport: new DefaultChatTransport() }), renders header + chat column + composer.
  • ddx-web/src/components/kb/AskPhoenixPanel.tsx:37export function AskPhoenixPanel(...). Side-panel variant using createPortal to mount outside the topic-page tree.
  • ddx-web/src/components/kb/PhoenixToolPart.tsx:42export function PhoenixToolPart({...}). The function-level branch on part.type === 'tool-kb-plan' returns a chip-only fragment; tool-kb-search renders an expandable detail. Critical for AC-10 (no sub-question leak into visible answer).
  • ddx-web/src/components/kb/PhoenixAnswer.tsx:67export function PhoenixAnswer({ text }). Markdown rendering with citation chip detection + deep-link conversion.
  • ddx-web/src/app/api/phoenix/chat/route.ts:21POST handler. Builds env, calls streamPhoenix, returns the streamed SSE response.
  • ddx-web/src/locales/en/kb.json (+ de, fr) — i18n key phoenix.planning = "Planning…" / "Plane…" / "Planification…". All three locales in lockstep.

Operational Notes

Public route exposure

/[locale]/docs/ask is a public route — no auth required. The Mastra agent runs server-side in ddx-web (Next.js 16 route handler), and the Qdrant query happens server-side too. The browser never talks to Qdrant or the LLM gateway directly. This means:

  • Anyone can use Ask Phoenix without signup.
  • Rate limiting must live in the /api/phoenix/chat route (currently inherits Next.js platform-level limits — consider explicit per-IP throttling if traffic grows).
  • No PII should ever appear in user questions (the route does not strip PII before passing to the LLM). Document this in the privacy note on the landing page.

Pitfalls

  1. Citation chips that don't deep-link: [layer/slug] chips MUST use the raw slug from the kb-search result (layer: 'infrastructure', slug: 'infra-postgres'). Inventing prefixes like [01-infrastructure/infra-postgres] breaks the markdown chip detection. The Phoenix system prompt forbids invented prefixes; verify by spot-checking the eval report.
  2. Locale fallback gotcha: if a user's browser is it-IT and the route lacks an Italian locale, next-intl falls back to en. The Mastra agent detects the language from the user's typed message regardless — so a user typing in French on an /en/docs/ask route gets a French answer. This is by design (the agent's output-language-strict rule).
  3. Tool-call chips can flash on slow networks: if the SSE stream stutters between the kb-plan event and the first kb-search event, users see "Planning…" alone for ~1 second. This is acceptable UX (clearly conveys "working") but consider a smoother transition if feedback flags it.
  4. No conversation persistence: each useChat instance keeps history in React state only. Refresh = new conversation. Persisting to backend would require an ai-chat-sessions-style record but is OUT of scope for the public surface.

Validation

bash
# Build sanity
cd ddx-web && pnpm build
# i18n keys present in all 3 locales
for L in en de fr; do
  jq -r '.phoenix.planning // "MISSING"' ddx-web/src/locales/$L/kb.json
done
# Type + lint Phoenix UI surface
pnpm tsc --noEmit
pnpm eslint src/components/kb/*.tsx src/app/[locale]/docs/ask/page.tsx

Common defects

  • "Planning…" chip never disappears → kb-plan tool returned ok:false on first call and the agent stopped. Check the Mastra agent prompt's rule 3 ("retry kb-plan exactly once on ok:false using suggestion").
  • Empty answer with 0 citations → falls into the Q6/Q8 anti-loop class. Verify the tool-layer guard is wired (see phoenix-agent-architecture Operational Notes).
  • Citation chip 404s on click → the slug in the chip doesn't match any KB topic page. Cause: a kb-search hit whose slug field is stale or the chunk was ingested without a matching markdown page in ddx-presentation/knowledge-base/. Re-run the inventory check.
    Ask Phoenix — Public Knowledge-Base Chat Assistant — Dudoxx Docs | Dudoxx