From cf2fd3ad9e824d8e9397e6b8f174869f22c2b7e0 Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Tue, 20 Jan 2026 06:30:33 +1000 Subject: [PATCH] feat: add conversation history and view mode toggle to scenario runs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Streamdown for markdown rendering - Add Message interface for conversation typing - Add viewMode state (chat/debug) for switching views - Add conversation history section in expanded run details - Show conversation in chat format (USER/ASSISTANT/TOOL messages) - Add view mode toggle to switch between chat and raw JSON views - Improve usage stats section to show — when data is missing --- .../[slug]/scenarios/[id]/page.tsx | 213 +++++++++++++++--- 1 file changed, 180 insertions(+), 33 deletions(-) diff --git a/apps/web/src/app/(profile)/[username]/collections/[slug]/scenarios/[id]/page.tsx b/apps/web/src/app/(profile)/[username]/collections/[slug]/scenarios/[id]/page.tsx index cbe73e9..122bb4c 100644 --- a/apps/web/src/app/(profile)/[username]/collections/[slug]/scenarios/[id]/page.tsx +++ b/apps/web/src/app/(profile)/[username]/collections/[slug]/scenarios/[id]/page.tsx @@ -6,6 +6,7 @@ import { Icon } from '@tpmjs/ui/Icon/Icon'; import Link from 'next/link'; import { notFound, useParams } from 'next/navigation'; import { useCallback, useEffect, useState } from 'react'; +import { Streamdown } from 'streamdown'; import { AppHeader } from '~/components/AppHeader'; interface ScenarioRun { @@ -32,7 +33,7 @@ interface ScenarioRun { }; output?: string; errorLog?: string; - conversation?: unknown[]; + conversation?: Message[]; } interface ScenarioDetail { @@ -61,6 +62,16 @@ interface ScenarioDetail { runCount: number; } +interface Message { + id: string; + role: 'USER' | 'ASSISTANT' | 'TOOL'; + content: string; + toolName?: string; + toolCallId?: string; + toolResult?: unknown; + createdAt?: string; +} + function StatusBadge({ status }: { status: string | null }) { if (!status) { return Not run; @@ -156,6 +167,7 @@ export default function CollectionScenarioDetailPage(): React.ReactElement { const [isRunning, setIsRunning] = useState(false); const [runError, setRunError] = useState(null); const [expandedRunId, setExpandedRunId] = useState(null); + const [viewMode, setViewMode] = useState<'chat' | 'debug'>('chat'); const fetchScenario = useCallback(async () => { try { @@ -249,13 +261,43 @@ export default function CollectionScenarioDetailPage(): React.ReactElement { {/* Header */}
-
-

- {scenario.name || 'Unnamed Scenario'} -

- {scenario.description && ( -

{scenario.description}

- )} +
+

+ {scenario.name || 'Unnamed Scenario'} +

+ {scenario.description && ( +

{scenario.description}

+ )} +
+ {scenario.isOwner && ( + + )} + {scenario.isOwner && ( + + )}
{scenario.isOwner && (
)} - {/* Usage Stats */} -
-

- Usage -

-
-
- Duration:{' '} - - {formatDuration(run.usage?.executionTimeMs || null)} - -
-
- Tokens:{' '} - - {run.usage?.totalTokens?.toLocaleString() || '—'} - -
-
- Retries:{' '} - {run.retryCount} -
-
-
-
+ {/* Usage Stats */} +
+

+ Usage +

+ {run.usage ? ( +
+
+ Duration:{' '} + + {formatDuration(run.usage.executionTimeMs || null)} + +
+
+ Tokens:{' '} + + {run.usage.totalTokens?.toLocaleString() || '—'} + +
+
+ Retries:{' '} + {run.retryCount} +
+
+ ) : ( +
+ No usage data available +
+ )} +
{/* Output (if owner) */} {run.output && ( @@ -453,6 +500,106 @@ export default function CollectionScenarioDetailPage(): React.ReactElement {
)} + + {/* Conversation History */} + {run.conversation && ( +
+
+

+ Conversation History +

+
+ + +
+
+ {viewMode === 'debug' ? ( +
+
+
+ Raw Messages ({run.conversation.length}) +
+ +
+
+                                    {JSON.stringify(run.conversation, null, 2)}
+                                  
+
+ ) : ( +
+ {run.conversation.map((msg) => ( +
+ {msg.role === 'USER' && ( +
+
+
+ {msg.content} +
+
+
+ )} + {msg.role === 'ASSISTANT' && ( +
+ {msg.content && ( +
+
+
+ {msg.content} +
+
+
+ )} +
+ )} + {msg.role === 'TOOL' && ( +
+
+
+
+ {msg.toolName || 'Unknown Tool'} +
+ {msg.toolResult && ( +
+
+                                                    {typeof msg.toolResult === 'string'
+                                                      ? msg.toolResult
+                                                      : JSON.stringify(msg.toolResult, null, 2)}
+                                                  
+
+ )} +
+
+
+ )} +
+ ))} +
+ )} +
+ )} )}