From 82d2e37a27e4c669b208fac02d707e808da5f81b Mon Sep 17 00:00:00 2001 From: Nicolas Varrot Date: Fri, 13 Feb 2026 01:41:04 +0000 Subject: [PATCH] feat: raw JSON viewer toggle on each message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a {⁠} button (visible on hover) that toggles a collapsible panel showing the full raw gateway JSON payload for any message. Includes copy-to-clipboard, word-wrap, and i18n (EN/FR). Useful for debugging and understanding the gateway protocol. Closes feedback #52 --- src/components/ChatMessage.tsx | 49 +++++++++++++++++++++++++++++++++- src/lib/i18n.ts | 4 +++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/components/ChatMessage.tsx b/src/components/ChatMessage.tsx index 6238730..49f3515 100644 --- a/src/components/ChatMessage.tsx +++ b/src/components/ChatMessage.tsx @@ -12,7 +12,7 @@ import { CodeBlock } from './CodeBlock'; import { ToolCall } from './ToolCall'; import { ImageBlock } from './ImageBlock'; import { buildImageSrc } from '../lib/image'; -import { Bot, User, Wrench, Copy, Check, RefreshCw, Zap, Info, Webhook } from 'lucide-react'; +import { Bot, User, Wrench, Copy, Check, RefreshCw, Zap, Info, Webhook, Braces } from 'lucide-react'; import { t, getLocale } from '../lib/i18n'; import { useLocale } from '../hooks/useLocale'; import { stripWebhookScaffolding, hasWebhookScaffolding } from '../lib/systemEvent'; @@ -293,6 +293,48 @@ function MetadataViewer({ metadata }: { metadata?: Record }) { ); } +function RawJsonToggle({ isOpen, onToggle }: { isOpen: boolean; onToggle: () => void }) { + return ( + + ); +} + +function RawJsonPanel({ message }: { message: ChatMessageType }) { + const [copied, setCopied] = useState(false); + const json = JSON.stringify(message, null, 2); + const handleCopy = useCallback(() => { + navigator.clipboard.writeText(json).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }); + }, [json]); + + return ( +
+
+ Raw JSON + +
+
+        {json}
+      
+
+ ); +} + /** Extract plain text from message blocks for clipboard copy */ function getPlainText(message: ChatMessageType): string { if (message.blocks.length > 0) { @@ -324,6 +366,7 @@ function SystemEventMessage({ message }: { message: ChatMessageType }) { export function ChatMessageComponent({ message: rawMessage, onRetry, agentAvatarUrl }: { message: ChatMessageType; onRetry?: (text: string) => void; agentAvatarUrl?: string }) { useLocale(); // re-render on locale change + const [showRawJson, setShowRawJson] = useState(false); // Strip webhook/hook scaffolding from user messages before rendering const message = useMemo(() => { @@ -396,6 +439,7 @@ export function ChatMessageComponent({ message: rawMessage, onRetry, agentAvatar )}
+ setShowRawJson(o => !o)} />
{/* Retry button (user messages only) */} {isUser && onRetry && ( @@ -437,6 +481,9 @@ export function ChatMessageComponent({ message: rawMessage, onRetry, agentAvatar {/* Tool calls & thinking (inline) */} {!isUser && } + + {/* Raw JSON viewer */} + {showRawJson && } {(message.timestamp || wasWebhookMessage) && (
diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts index 608a3c5..0942a47 100644 --- a/src/lib/i18n.ts +++ b/src/lib/i18n.ts @@ -75,6 +75,8 @@ const en = { 'message.copied': 'Copied!', 'message.retry': 'Resend message', 'message.metadata': 'Message details', + 'message.rawJson': 'Raw JSON', + 'message.hideRawJson': 'Hide raw JSON', // Timestamps 'time.yesterday': 'Yesterday', @@ -179,6 +181,8 @@ const fr: Record = { 'message.copied': 'Copié !', 'message.retry': 'Renvoyer le message', 'message.metadata': 'Détails du message', + 'message.rawJson': 'JSON brut', + 'message.hideRawJson': 'Masquer le JSON brut', 'time.yesterday': 'Hier', 'time.today': "Aujourd'hui",