From 6f5f21d699a9e9a5760a84629fa3f3728cd7b862 Mon Sep 17 00:00:00 2001 From: yao <63141491+yaonyan@users.noreply.github.com> Date: Sun, 23 Nov 2025 00:19:31 +0800 Subject: [PATCH] refactor: embed full message rendering in InspectorBar with compact viewport - Replaced simplified message display with full Conversation component rendering - Embedded Message, MessageContent, MessageAvatar components directly in bar - Used renderMessagePart for complete message rendering - Added compact viewport (h-10) with overflow control to hide scrollbars - Removed legacy useTextBuffer, processMessage, extractToolName logic - Simplified state management by removing custom buffering and tool tracking - Net reduction of ~105 lines of code --- .../client/components/InspectorBar.tsx | 202 +++++------------- 1 file changed, 55 insertions(+), 147 deletions(-) diff --git a/packages/unplugin-dev-inspector/client/components/InspectorBar.tsx b/packages/unplugin-dev-inspector/client/components/InspectorBar.tsx index cf7a72f..f5739fc 100644 --- a/packages/unplugin-dev-inspector/client/components/InspectorBar.tsx +++ b/packages/unplugin-dev-inspector/client/components/InspectorBar.tsx @@ -1,12 +1,18 @@ import React, { useState, useRef, useEffect } from 'react'; import { cn } from '../lib/utils'; -import { Eye, Sparkles, ArrowRight, Terminal, CheckCircle2, XCircle, ChevronUp } from 'lucide-react'; -import { Shimmer } from '../../src/components/ai-elements/shimmer'; +import { Eye, Sparkles, ArrowRight, ChevronUp } from 'lucide-react'; import type { UIMessage } from 'ai'; -import { processMessage, extractToolName } from '../utils/messageProcessor'; import { FeedbackCart, type FeedbackItem } from './FeedbackCart'; import { MessageDetail } from './MessageDetail'; -import { useTextBuffer } from '../hooks/useTextBuffer'; +import { + Conversation, + ConversationContent, + ConversationScrollButton, +} from './ai-elements/conversation'; +import { Message, MessageAvatar, MessageContent } from './ai-elements/message'; +import { Loader } from './ai-elements/loader'; +import { renderMessagePart } from '../lib/messageRenderer'; +import { AVAILABLE_AGENTS, DEFAULT_AGENT } from '../constants/agents'; interface InspectorBarProps { isActive: boolean; @@ -34,109 +40,13 @@ export const InspectorBar = ({ console.log(messages); const [isExpanded, setIsExpanded] = useState(false); const [input, setInput] = useState(''); - const [toolCall, setToolCall] = useState(null); const [isPanelExpanded, setIsPanelExpanded] = useState(false); const [hideInputDuringWork, setHideInputDuringWork] = useState(false); const [isLocked, setIsLocked] = useState(false); const [allowHover, setAllowHover] = useState(true); - - // accumulatedText tracks the full message history for reference - const [accumulatedText, setAccumulatedText] = useState(''); - - // Use the text buffer hook to handle smooth text updates - const bufferedText = useTextBuffer(accumulatedText, isAgentWorking, 50); - - // Only show the new fragment of text, not the whole history - const [visibleFragment, setVisibleFragment] = useState(''); - const lastProcessedTextRef = useRef(''); - const prevVisibleFragmentRef = useRef(''); - - // Effect to calculate visible fragment from buffered text - useEffect(() => { - const currentFullText = bufferedText; - const lastFullText = lastProcessedTextRef.current; - - // 1. Handle Reset/Context Switch - if (currentFullText.length < lastFullText.length || !currentFullText.startsWith(lastFullText)) { - setVisibleFragment(currentFullText); - lastProcessedTextRef.current = currentFullText; - return; - } - - // 2. Handle Incremental Update - if (currentFullText.length > lastFullText.length) { - const newPart = currentFullText.slice(lastFullText.length).trim(); - // Only update if there is meaningful content - if (newPart) { - setVisibleFragment(newPart); - } - lastProcessedTextRef.current = currentFullText; - } - }, [bufferedText]); const inputRef = useRef(null); const containerRef = useRef(null); - const toolClearTimerRef = useRef(null); - const lastSeenToolNameRef = useRef(null); - const isToolActiveRef = useRef(false); - - // Main effect: Process messages - useEffect(() => { - if (messages.length === 0) { - setAccumulatedText(''); - setToolCall(null); - lastSeenToolNameRef.current = null; - isToolActiveRef.current = false; - setVisibleFragment(''); - lastProcessedTextRef.current = ''; - return; - } - - // KISS: Only process the LAST message (the one that's being updated) - const lastMessage = messages[messages.length - 1]; - if (lastMessage.role !== 'assistant') return; - - // Extract tool and text from the last message - const extractedTool = extractToolName(lastMessage); - const { displayText: messageText, toolCall: activeToolCall } = processMessage( - lastMessage, - extractedTool || lastSeenToolNameRef.current - ); - - // Update accumulated text - const currentText = messageText || ''; - setAccumulatedText(currentText); - - // Track tool name - if (extractedTool) { - lastSeenToolNameRef.current = extractedTool; - } - - // Update tool display - if (activeToolCall) { - // There's an active tool - show it - if (toolClearTimerRef.current) { - clearTimeout(toolClearTimerRef.current); - toolClearTimerRef.current = null; - } - setToolCall(activeToolCall); - isToolActiveRef.current = true; - } else { - isToolActiveRef.current = false; - } - }, [messages]); - - // Effect to clear tool when text updates - useEffect(() => { - if (visibleFragment !== prevVisibleFragmentRef.current) { - // If text has updated and no tool is currently active, clear the tool display - // This ensures we keep showing the tool name until the text actually appears - if (!isToolActiveRef.current) { - setToolCall(null); - } - prevVisibleFragmentRef.current = visibleFragment; - } - }, [visibleFragment]); // Auto-focus input when expanded useEffect(() => { @@ -151,7 +61,6 @@ export const InspectorBar = ({ // Unlock immediately, but keep showing the content setHideInputDuringWork(false); setIsLocked(false); - // Don't clear tool call here - let the message processing effect handle it with delay // Temporarily disable hover to show result setAllowHover(false); // Re-enable hover after 2 seconds @@ -166,16 +75,7 @@ export const InspectorBar = ({ e.preventDefault(); if (!input.trim()) return; - // Clear any pending timer - if (toolClearTimerRef.current) { - clearTimeout(toolClearTimerRef.current); - toolClearTimerRef.current = null; - } - - // Clear all states for new query - setToolCall(null); - setAccumulatedText(''); - lastSeenToolNameRef.current = null; + // Set states for new query setHideInputDuringWork(true); setIsLocked(true); @@ -247,46 +147,54 @@ export const InspectorBar = ({ )} + {/* Embed full MessageDetail rendering in the bar */} {showMessage && ( - <> -
- {isAgentWorking ? ( - <> -
- - - ) : isError ? ( - - ) : ( - - )} -
- -
- -
- {toolCall ? ( -
- - {toolCall} -
- ) : ( -
- {isAgentWorking && !visibleFragment ? ( - - Thinking... - - ) : ( - visibleFragment || 'Processing...' - )} -
- )} -
- +
+ + + {messages.length === 0 ? ( +
+ Processing... +
+ ) : ( + messages.map((message) => { + const currentAgent = AVAILABLE_AGENTS.find((a) => a.name === DEFAULT_AGENT) || AVAILABLE_AGENTS[0]; + return ( + + + {message.parts.map((part, index) => + renderMessagePart( + part, + message.id, + index, + status === "streaming", + message.metadata as Record | undefined + ) + )} + + {message.role === "assistant" && ( + + )} + + ); + }) + )} + {status === "submitted" && } +
+ +
+
)}
-