Skip to content

Commit f5b27f9

Browse files
committed
fix(web): parse scorecard into rail instead of displaying as chat text
Strip helprs-scorecard JSON blocks from assistant message display. Extract scorecard in real-time from the SSE stream and from stored events during replay. The scorecard renders in the right rail with dimension bars instead of showing raw JSON in the transcript.
1 parent 7276901 commit f5b27f9

2 files changed

Lines changed: 50 additions & 1 deletion

File tree

apps/web/src/features/session/ContainerSession.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import ConversationOutput from './ConversationOutput'
2323
import SessionRail from './SessionRail'
2424
import type { ScorecardResponse } from './containerApi'
2525
import {
26+
extractScorecardFromText,
2627
parseStoredMessage,
2728
parseStreamMessage,
2829
} from './containerTypes'
@@ -142,6 +143,18 @@ export default function ContainerSession({
142143
try {
143144
const resp = await getSessionEvents(sessionId)
144145
for (const evt of resp.events) {
146+
// Extract scorecard from stored assistant events
147+
if (evt.data.type === 'assistant') {
148+
const content = (evt.data.message as { content?: Array<{ type: string; text?: string }> })?.content
149+
if (content) {
150+
for (const block of content) {
151+
if (block.type === 'text' && block.text) {
152+
const sc = extractScorecardFromText(block.text)
153+
if (sc) setScorecard(sc as unknown as ScorecardResponse)
154+
}
155+
}
156+
}
157+
}
145158
const parsed = parseStoredMessage(evt.data)
146159
if (parsed) {
147160
appendMessage(parsed)
@@ -270,6 +283,21 @@ export default function ContainerSession({
270283
// ignore
271284
}
272285

286+
// Check for scorecard in raw assistant text before it gets stripped
287+
try {
288+
const rawEvt = JSON.parse(event.data as string) as { type?: string; message?: { content?: Array<{ type: string; text?: string }> } }
289+
if (rawEvt.type === 'assistant' && rawEvt.message?.content) {
290+
for (const block of rawEvt.message.content) {
291+
if (block.type === 'text' && block.text) {
292+
const sc = extractScorecardFromText(block.text)
293+
if (sc) setScorecard(sc as unknown as ScorecardResponse)
294+
}
295+
}
296+
}
297+
} catch {
298+
// ignore — best-effort scorecard extraction
299+
}
300+
273301
const parsed = parseStreamMessage(event.data as string)
274302
if (parsed) {
275303
appendMessage(parsed)

apps/web/src/features/session/containerTypes.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,12 @@ export function parseStreamMessage(raw: string): Omit<StreamMessage, 'id' | 'tim
181181
// Only extract text blocks — tool_use and thinking are internal
182182
// activity that we don't display to users.
183183
if (block.type === 'text' && typeof block.text === 'string') {
184-
blocks.push({ type: 'text', text: block.text })
184+
// Strip helprs-scorecard JSON blocks from display — they're
185+
// parsed separately and shown in the session rail.
186+
const cleaned = block.text.replace(/```helprs-scorecard\s*\n[\s\S]*?\n```/g, '').trim()
187+
if (cleaned) {
188+
blocks.push({ type: 'text', text: cleaned })
189+
}
185190
}
186191
}
187192

@@ -246,3 +251,19 @@ export function parseStreamMessage(raw: string): Omit<StreamMessage, 'id' | 'tim
246251
export function parseStoredMessage(data: Record<string, unknown>): Omit<StreamMessage, 'id' | 'timestamp'> | null {
247252
return parseStreamMessage(JSON.stringify(data))
248253
}
254+
255+
/**
256+
* Extract a helprs-scorecard JSON block from assistant text.
257+
* Returns the parsed object or null if not found / invalid.
258+
*/
259+
export function extractScorecardFromText(raw: string): Record<string, unknown> | null {
260+
const match = raw.match(/```helprs-scorecard\s*\n([\s\S]*?)\n```/)
261+
if (!match) return null
262+
try {
263+
const parsed = JSON.parse(match[1]) as Record<string, unknown>
264+
if (parsed.dimensions && parsed.skill) return parsed
265+
return null
266+
} catch {
267+
return null
268+
}
269+
}

0 commit comments

Comments
 (0)