Boss Fight: Real-Time Dashboard
The Scenario
Section titled “The Scenario”You’re building a Real-Time Dashboard — a streaming system that delivers an analysis to a user in real time. The stream transports structured status updates alongside the analysis text, uses Message Metadata for session tracking, smooths the output, and catches errors gracefully.
Your dashboard should feel like this:
[Status: searching] Recherchiere Daten...[Fortschritt: 1/3] Marktdaten geladen.
Die Analyse zeigt folgende Trends: Im ersten Quartal stiegder Umsatz um 12%. Der wichtigste Treiber war...
[Fortschritt: 2/3] Wettbewerbsanalyse abgeschlossen.
Im Vergleich zum Wettbewerb liegt das Unternehmen bei...
[Fortschritt: 3/3] Prognose berechnet.[Status: done] Analyse abgeschlossen.[Session: user-42 | Dauer: 4.2s | Tokens: 847]This project connects all four building blocks:
Requirements
Section titled “Requirements”-
Custom Data Parts (Challenge 7.1) — Send structured status updates in the stream: “searching”, “analyzing”, “done”. Send at least 3 progress updates with the current step and total count (e.g.,
{ step: 1, total: 3, label: "Market data loaded" }). -
Message Metadata (Challenge 7.2) — Every user message carries metadata:
userId,timestamp,sessionId. The assistant response carries metadata:modelId,generatedAt,tokenCount. Log the user metadata server-side BEFORE the LLM call starts. -
Stream Transforms (Challenge 7.3) — Use
smoothStream()for smooth text output. Write a custom transform that masks confidential data (e.g., replaces email addresses with “[REDACTED]”). -
Error Handling (Challenge 7.4) — Implement
onErrorinstreamTextfor server-side logging. ImplementonErrorintoUIMessageStreamResponsefor user-friendly error messages. Wrap the stream consumer in try/catch as a last fallback. -
Session Statistics — At the end of the stream: calculate the duration (from metadata timestamps) and send a final statistic as a Data Part:
{ type: 'stats', duration, tokenCount, userId }.
Starter Code
Section titled “Starter Code”import { createDataStream, smoothStream, streamText } from 'ai';import { anthropic } from '@ai-sdk/anthropic';
// TODO: Message mit Metadata erstellen// TODO: Custom Transform fuer Daten-Maskierung// TODO: createDataStream mit Status-Updates// TODO: streamText mit smoothStream + Custom Transform// TODO: Error Handling (onError + try/catch)// TODO: Session-Statistik am EndeEvaluation Criteria
Section titled “Evaluation Criteria”Your Boss Fight is passed when:
- At least 3 Custom Data Parts with progress updates are sent in the stream
- User messages carry metadata (userId, timestamp, sessionId) that the LLM does not see
- The assistant response carries metadata (modelId, generatedAt)
-
smoothStream()is configured as a transform - A custom transform masks at least one data pattern (email, phone number, etc.)
-
onErroris implemented in bothstreamTextandtoUIMessageStreamResponse - A try/catch around the stream consumer catches unexpected errors
- At the end, a session statistic is sent as a Data Part (duration + tokens)
Hint 1: createDataStream Structure
createDataStream expects an execute function that receives the dataStream controller as a parameter. In this function you call dataStream.writeData() for Custom Data Parts and result.mergeIntoDataStream(dataStream) for the LLM stream. The order determines when Data Parts appear in the stream.
Hint 2: Timing for Progress Updates
You can use setTimeout or setInterval to send progress updates with a delay. Or use the onStepFinish or onChunk callbacks from streamText to send Data Parts based on stream progress. Important: end intervals in onFinish, otherwise the stream never completes.
Hint 3: Combining Custom Transform + smoothStream
experimental_transform accepts an array. smoothStream() should come first (smooths the raw stream), then your custom transform (works with the smoothed chunks). Remember: your transform must modify text-delta chunks and pass all other types through unchanged.
Hint 4: Calculating Session Duration
Store Date.now() at the beginning of the execute callback. In onFinish of streamText you calculate the difference. Send the result as a Data Part: dataStream.writeData({ type: 'stats', durationMs: Date.now() - startTime, ... }).