Skip to content

Commit 711c4f4

Browse files
pinkroosteraiclaude
andcommitted
feat(playground): add judging indicator via SSE progress event
Backend sends a 'judging' SSE event after streaming completes and before running the LLM judge. Frontend detects this and shows an "Evaluating output quality..." spinner with Loader2 icon below the response. The indicator disappears when scores arrive in the done event. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
1 parent 583f8eb commit 711c4f4

4 files changed

Lines changed: 21 additions & 0 deletions

File tree

src/backend/Clarive.Api/Endpoints/PlaygroundEndpoints.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ await sse.WriteErrorAsync(
104104
else
105105
{
106106
var testResult = result.Value;
107+
await sse.WriteChunkAsync(new TestStreamChunk(-1, "", "judging"), ct);
107108
var judgeResult = await playground.JudgePlaygroundRunAsync(
108109
tenantId, userId, entryId, testResult.RunId, ct);
109110
var finalResult = testResult with

src/frontend/src/components/playground/PlaygroundResultsArea.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ interface PlaygroundResultsAreaProps {
103103
// Judge
104104
pinnedJudgeScores: Evaluation | null;
105105
currentJudgeScores: Evaluation | null;
106+
isJudging: boolean;
106107
}
107108

108109
export default function PlaygroundResultsArea({
@@ -134,6 +135,7 @@ export default function PlaygroundResultsArea({
134135
handleCopy,
135136
pinnedJudgeScores,
136137
currentJudgeScores,
138+
isJudging,
137139
}: PlaygroundResultsAreaProps) {
138140
const judgeScores = pinnedJudgeScores ?? currentJudgeScores;
139141
return (
@@ -501,6 +503,14 @@ export default function PlaygroundResultsArea({
501503
</div>
502504
)}
503505

506+
{/* Judging indicator */}
507+
{isJudging && !judgeScores && (
508+
<div className="flex items-center gap-2 text-xs text-foreground-muted mt-4 pt-3 border-t border-border-subtle">
509+
<Loader2 className="size-3.5 animate-spin text-primary" />
510+
<span>Evaluating output quality...</span>
511+
</div>
512+
)}
513+
504514
{/* Judge output quality (non-comparison mode) */}
505515
{!pinnedRun && !isStreaming && hasResponses && judgeScores && (
506516
<JudgeScorePanel scores={judgeScores} />

src/frontend/src/hooks/usePlaygroundStreaming.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export function usePlaygroundStreaming({
7575
} | null>(null);
7676
const [lastRunId, setLastRunId] = useState<string | null>(null);
7777
const [lastJudgeScores, setLastJudgeScores] = useState<Evaluation | null>(null);
78+
const [isJudging, setIsJudging] = useState(false);
7879

7980
// ── Scroll refs ──
8081
const responseAreaRef = useRef<HTMLDivElement>(null);
@@ -149,6 +150,7 @@ export function usePlaygroundStreaming({
149150
setLastTokens(null);
150151
setLastRunId(null);
151152
setLastJudgeScores(null);
153+
setIsJudging(false);
152154
setElapsedSeconds(0);
153155
setApproxOutputTokens(0);
154156
streamedTextLengthRef.current = 0;
@@ -179,6 +181,10 @@ export function usePlaygroundStreaming({
179181
firstTokenRef.current = true;
180182
setFirstTokenReceived(true);
181183
}
184+
if (chunk.type === 'judging') {
185+
setIsJudging(true);
186+
return;
187+
}
182188
if (!chunk.text) return;
183189
if (chunk.type === 'reasoning') {
184190
setStreamedReasoning((prev) => ({
@@ -201,6 +207,7 @@ export function usePlaygroundStreaming({
201207
}
202208
setLastRunId(result.runId);
203209
setLastJudgeScores(result.judgeScores ?? null);
210+
setIsJudging(false);
204211

205212
queryClient.invalidateQueries({ queryKey: ['playground', 'runs', entryId] });
206213
} catch (err) {
@@ -264,6 +271,7 @@ export function usePlaygroundStreaming({
264271
lastTokens,
265272
lastRunId,
266273
lastJudgeScores,
274+
isJudging,
267275
hasResponses,
268276
currentPromptIndex,
269277
responseCount,

src/frontend/src/pages/PlaygroundPage.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ const PlaygroundPage = () => {
102102
approxOutputTokens,
103103
lastTokens,
104104
lastJudgeScores,
105+
isJudging,
105106
hasResponses,
106107
currentPromptIndex,
107108
responseCount,
@@ -316,6 +317,7 @@ const PlaygroundPage = () => {
316317
handleCopy={handleCopy}
317318
pinnedJudgeScores={pinnedRun?.judgeScores ?? null}
318319
currentJudgeScores={lastJudgeScores}
320+
isJudging={isJudging}
319321
/>
320322

321323
{/* ── History sidebar ── */}

0 commit comments

Comments
 (0)