From 03d3b72f9b1059343fe3b7217d39952c1fc1a728 Mon Sep 17 00:00:00 2001 From: Hermes Date: Thu, 9 Apr 2026 21:21:59 -0500 Subject: [PATCH] Session verbosity: compact/normal/full toggle - Compact: text only, truncated to 300 chars, no tool calls - Normal: full text + collapsed tool calls - Full: everything expanded with tool results - Eye icon dropdown in session header --- app/history/[id]/page.tsx | 68 ++++++++++++++++++++++++++++++------ components/MessageBubble.tsx | 37 ++++++++++++++------ 2 files changed, 84 insertions(+), 21 deletions(-) diff --git a/app/history/[id]/page.tsx b/app/history/[id]/page.tsx index 73e931e..e292de6 100644 --- a/app/history/[id]/page.tsx +++ b/app/history/[id]/page.tsx @@ -1,13 +1,21 @@ "use client"; import { use, useState, useRef } from "react"; -import { ArrowLeft, Send, Loader2 } from "lucide-react"; +import { ArrowLeft, Send, Loader2, Eye } from "lucide-react"; import Link from "next/link"; import { useSession, useMessages } from "@/lib/hooks"; import { timeAgo, formatTokens, platformBadgeClass } from "@/lib/utils"; import { MessageBubble } from "@/components/MessageBubble"; import { useQueryClient } from "@tanstack/react-query"; +export type Verbosity = "compact" | "normal" | "full"; + +const VERBOSITY_OPTIONS: { id: Verbosity; label: string }[] = [ + { id: "compact", label: "Compact" }, + { id: "normal", label: "Normal" }, + { id: "full", label: "Full" }, +]; + export default function SessionReplayPage({ params }: { params: Promise<{ id: string }> }) { const { id } = use(params); const qc = useQueryClient(); @@ -16,14 +24,26 @@ export default function SessionReplayPage({ params }: { params: Promise<{ id: st const [input, setInput] = useState(""); const [sending, setSending] = useState(false); const [pendingMsg, setPendingMsg] = useState(null); + const [verbosity, setVerbosity] = useState("normal"); + const [showVerbosity, setShowVerbosity] = useState(false); const inputRef = useRef(null); const session = sessionData?.session; const messages = messagesData?.items || []; - const visibleMessages = messages - .filter((m) => m.role === "user" || m.role === "assistant") - .reverse(); + // Filter based on verbosity + let visibleMessages = messages.filter((m) => { + if (verbosity === "compact") return m.role === "user" || (m.role === "assistant" && m.content); + return m.role === "user" || m.role === "assistant"; + }).reverse(); + + // In compact mode, skip assistant messages that are only tool calls (no text) + if (verbosity === "compact") { + visibleMessages = visibleMessages.filter((m) => { + if (m.role === "assistant" && !m.content?.trim()) return false; + return true; + }); + } const toolResults = new Map(); for (const m of messages) { @@ -41,7 +61,6 @@ export default function SessionReplayPage({ params }: { params: Promise<{ id: st setInput(""); setSending(true); setPendingMsg(msg); - try { await fetch("/api/sessions/" + id + "/chat", { method: "POST", @@ -49,7 +68,6 @@ export default function SessionReplayPage({ params }: { params: Promise<{ id: st body: JSON.stringify({ message: msg }), }); } catch {} - setPendingMsg(null); setSending(false); qc.invalidateQueries({ queryKey: ["messages", id] }); @@ -59,7 +77,7 @@ export default function SessionReplayPage({ params }: { params: Promise<{ id: st return (
{/* Header */} -
+
@@ -78,6 +96,36 @@ export default function SessionReplayPage({ params }: { params: Promise<{ id: st
)}
+ {/* Verbosity toggle */} +
+ + {showVerbosity && ( +
+ {VERBOSITY_OPTIONS.map((opt) => ( + + ))} +
+ )} +
{/* Message input */} @@ -107,7 +155,7 @@ export default function SessionReplayPage({ params }: { params: Promise<{ id: st - {/* Messages - newest first */} + {/* Messages */} {isLoading ? (
{[...Array(4)].map((_, i) => ( @@ -119,7 +167,6 @@ export default function SessionReplayPage({ params }: { params: Promise<{ id: st
) : (
- {/* Pending thinking indicator */} {sending && (
@@ -131,7 +178,6 @@ export default function SessionReplayPage({ params }: { params: Promise<{ id: st
)} - {/* Pending user message */} {pendingMsg && (
@@ -141,7 +187,7 @@ export default function SessionReplayPage({ params }: { params: Promise<{ id: st )} {visibleMessages.map((m) => ( - + ))} {visibleMessages.length === 0 && !sending && (

diff --git a/components/MessageBubble.tsx b/components/MessageBubble.tsx index c40d772..d00af10 100644 --- a/components/MessageBubble.tsx +++ b/components/MessageBubble.tsx @@ -4,13 +4,16 @@ import { useState } from "react"; import { Wrench, ChevronDown, ChevronRight } from "lucide-react"; import type { Message, ToolCall } from "@/lib/types"; +type Verbosity = "compact" | "normal" | "full"; + interface Props { message: Message; toolResults: Map; + verbosity?: Verbosity; } -function ToolCallBlock({ tc, result }: { tc: ToolCall; result?: { name: string; content: string } }) { - const [open, setOpen] = useState(false); +function ToolCallBlock({ tc, result, showResult }: { tc: ToolCall; result?: { name: string; content: string }; showResult: boolean }) { + const [open, setOpen] = useState(showResult); let args: Record = {}; try { args = JSON.parse(tc.function.arguments); } catch {} @@ -30,9 +33,9 @@ function ToolCallBlock({ tc, result }: { tc: ToolCall; result?: { name: string;

             {JSON.stringify(args, null, 2)}
           
- {result && ( + {showResult && result && (
-              {result.content.slice(0, 500)}{result.content.length > 500 ? "..." : ""}
+              {result.content.slice(0, 1000)}{result.content.length > 1000 ? "..." : ""}
             
)}
@@ -41,7 +44,7 @@ function ToolCallBlock({ tc, result }: { tc: ToolCall; result?: { name: string; ); } -export function MessageBubble({ message, toolResults }: Props) { +export function MessageBubble({ message, toolResults, verbosity = "normal" }: Props) { const isUser = message.role === "user"; const content = message.content || ""; @@ -52,6 +55,11 @@ export function MessageBubble({ message, toolResults }: Props) { if (!content && toolCalls.length === 0) return null; + // Compact: no tool calls at all + const showTools = verbosity !== "compact" && toolCalls.length > 0; + // Full: expand tool calls and show results + const expandTools = verbosity === "full"; + return (
{content && (

- {content} + {verbosity === "compact" && content.length > 300 + ? content.slice(0, 300) + "..." + : content}

)} - {toolCalls.length > 0 && ( + {showTools && (
- {toolCalls.length <= 3 ? ( + {expandTools ? ( + // Full: show all tool calls expanded with results toolCalls.map((tc) => ( - + + )) + ) : toolCalls.length <= 3 ? ( + // Normal: collapsed tool calls, no results + toolCalls.map((tc) => ( + )) ) : ( + // Normal with many tools: collapsed summary )}
@@ -96,7 +113,7 @@ function CollapsedTools({ toolCalls, toolResults }: { toolCalls: ToolCall[]; too {open ? : } {open && toolCalls.map((tc) => ( - + ))}
);