feat: show turn count in realtime pretty view session header and output header

- Add purple 'N turns' tag to Session card header for at-a-glance turn count
- Add 'Turns: N' to the Output section header next to tokens/cost
- Extend SectionHeader to accept optional turnCount prop
- Add 3 new tests for turn count display (singular, plural, output header)

Co-authored-by: Ishaan Jaff <ishaan-jaff@users.noreply.github.com>
This commit is contained in:
Cursor Agent 2026-02-26 07:02:53 +00:00
parent 408e5e48bf
commit 1f0cb4705c
3 changed files with 64 additions and 3 deletions

View file

@ -187,6 +187,49 @@ describe("RealtimePrettyView", () => {
expect(screen.getByText("text")).toBeInTheDocument();
});
it("should display the turn count in session header", () => {
render(<RealtimePrettyView response={sampleRealtimeResponse} />);
expect(screen.getByText("2 turns")).toBeInTheDocument();
});
it("should display singular 'turn' for a single response event", () => {
const singleTurnResponse = {
results: [
{
type: "session.created",
session: {
id: "sess_1",
model: "gpt-4o-mini-realtime-preview",
voice: "alloy",
modalities: ["audio"],
},
},
{
type: "response.done",
response: {
id: "r1",
status: "completed",
output: [
{
id: "item1",
role: "assistant",
type: "message",
content: [{ type: "audio", transcript: "Hi!" }],
},
],
},
},
],
};
render(<RealtimePrettyView response={singleTurnResponse} />);
expect(screen.getByText("1 turn")).toBeInTheDocument();
});
it("should display the turn count in the output section header", () => {
render(<RealtimePrettyView response={sampleRealtimeResponse} />);
expect(screen.getByText("Turns: 2")).toBeInTheDocument();
});
it("should display the Output section header", () => {
render(<RealtimePrettyView response={sampleRealtimeResponse} />);
expect(screen.getByText("Output")).toBeInTheDocument();

View file

@ -107,7 +107,7 @@ export function RealtimePrettyView({ response, metrics }: RealtimePrettyViewProp
<div>
{/* Session Configuration Card */}
{sessionEvent?.session && (
<SessionCard session={sessionEvent.session} />
<SessionCard session={sessionEvent.session} turnCount={responseEvents.length} />
)}
{/* Conversation Turns */}
@ -138,7 +138,7 @@ export function RealtimePrettyView({ response, metrics }: RealtimePrettyViewProp
);
}
function SessionCard({ session }: { session: RealtimeSession }) {
function SessionCard({ session, turnCount }: { session: RealtimeSession; turnCount: number }) {
const [isCollapsed, setIsCollapsed] = useState(true);
return (
@ -184,6 +184,14 @@ function SessionCard({ session }: { session: RealtimeSession }) {
<Text type="secondary" style={{ fontSize: 12 }}>
{session.model}
</Text>
{turnCount > 0 && (
<Tag
color="purple"
style={{ margin: 0, fontWeight: 500 }}
>
{turnCount} {turnCount === 1 ? 'turn' : 'turns'}
</Tag>
)}
{session.voice && (
<Tag color="blue" style={{ margin: 0 }}>
<SoundOutlined /> {session.voice}
@ -298,6 +306,7 @@ function ConversationCard({
const [isCollapsed, setIsCollapsed] = useState(false);
const totalTokens = totalUsage?.total_tokens;
const turnCount = responses.length;
const handleCopy = () => {
const transcripts = responses
.flatMap((r) =>
@ -326,6 +335,7 @@ function ConversationCard({
onCopy={handleCopy}
isCollapsed={isCollapsed}
onToggleCollapse={() => setIsCollapsed(!isCollapsed)}
turnCount={turnCount}
/>
<div

View file

@ -19,9 +19,10 @@ interface SectionHeaderProps {
onCopy: () => void;
isCollapsed?: boolean;
onToggleCollapse?: () => void;
turnCount?: number;
}
export function SectionHeader({ type, tokens, cost, onCopy, isCollapsed, onToggleCollapse }: SectionHeaderProps) {
export function SectionHeader({ type, tokens, cost, onCopy, isCollapsed, onToggleCollapse, turnCount }: SectionHeaderProps) {
return (
<div
onClick={onToggleCollapse}
@ -81,6 +82,13 @@ export function SectionHeader({ type, tokens, cost, onCopy, isCollapsed, onToggl
Cost: ${cost.toFixed(6)}
</Text>
)}
{/* Turn count */}
{turnCount !== undefined && turnCount > 0 && (
<Text type="secondary" style={{ fontSize: 12 }}>
Turns: {turnCount}
</Text>
)}
</div>
{/* Copy Button */}