diff --git a/contrib/chat-plugin/src/app/ChatHistory.tsx b/contrib/chat-plugin/src/app/ChatHistory.tsx index 2d20bb0e..59e18201 100644 --- a/contrib/chat-plugin/src/app/ChatHistory.tsx +++ b/contrib/chat-plugin/src/app/ChatHistory.tsx @@ -105,42 +105,51 @@ const CustomMarkdown: React.FC<{ content: string }> = ({ content }) => { } // Message component -const Message: React.FC<{ message: ChatMessage, expand?: boolean }> = ({ message, expand = true }) => { - const [expanded, setExpanded] = useState(expand); - const [showButton, setShowButton] = useState(true); +const Message: React.FC<{ message: ChatMessage }> = ({ message }) => { + const [expanded, setExpanded] = useState(true); const contentRef = useRef(null); // Reference to the content div - useEffect(() => { - setExpanded(expand); // Set expanded based on prop - }, [expand]); + const handleToggle = () => { + setExpanded((prev) => !prev); + }; + + const hasExpandedRef = useRef(false); useEffect(() => { - // Check if the content height is less than a threshold (8 in this case) - const contentHeight = contentRef.current?.scrollHeight; - if (contentHeight && contentHeight <= 32) { // Assuming 32px is roughly equivalent to 2 lines of text - setShowButton(false); - } else { - setShowButton(true); + if (message.message.length > 0 && !hasExpandedRef.current) { + setExpanded(false); + hasExpandedRef.current = true; } - }, [message]); // Depend on message so it re-checks when message changes - - const toggleExpanded = () => { - setExpanded(!expanded); - }; + }, [message.message.length]); return (
- {showButton && ( - + + {/* Reasoning part with fold/unfold */} + {message.reasoning && ( +
+
+ { + e.preventDefault(); + handleToggle(); + }} + > + Reasoning + +
+ {message.reasoning} +
+
+
)}
{message.message} @@ -186,7 +195,7 @@ const MessageGroup: React.FC<{ index: number, group: MessageGroup, isLast: boole
{group.messages.map((message, index) => ( // - + ))}
diff --git a/contrib/chat-plugin/src/libs/api.ts b/contrib/chat-plugin/src/libs/api.ts index 0ef91392..fb25984a 100644 --- a/contrib/chat-plugin/src/libs/api.ts +++ b/contrib/chat-plugin/src/libs/api.ts @@ -215,10 +215,11 @@ export async function chatStreamRequest(abortSignal?: AbortSignal) { })), }; - const newMsg = { + let newMsg = { role: "assistant" as const, message: "", timestamp: new Date(), + reasoning: "", }; // Add a new message to the chat store to show loading state useChatStore.getState().addChatMessage(newMsg); @@ -234,7 +235,6 @@ export async function chatStreamRequest(abortSignal?: AbortSignal) { const reader = response.body!.getReader(); const decoder = new TextDecoder(); let buffer = ""; - let fullResponse = ""; try { while (true) { const { value, done } = await reader.read(); @@ -265,16 +265,23 @@ export async function chatStreamRequest(abortSignal?: AbortSignal) { const parsed = JSON.parse(data); // Handle different response formats (e.g., chat completions vs completions) - const content = parsed.choices?.[0]?.delta?.content || + let contentDelta = parsed.choices?.[0]?.delta?.content || parsed.choices?.[0]?.text || ""; + const reasonDelta = parsed.choices?.[0]?.delta?.reasoning_content || ""; - if (content) { - fullResponse += content; + if (contentDelta) { + if (newMsg.message.length === 0) { + contentDelta = contentDelta.trim(); + } + newMsg.message += contentDelta; + } + if (reasonDelta) { + newMsg.reasoning += reasonDelta; + } + if (contentDelta || reasonDelta) { // Update the last message in the chat store - useChatStore.getState().updateLastChatMessage(fullResponse); - // You could emit progress here if needed - // onProgress?.(content, fullResponse); + useChatStore.getState().updateLastChatMessage(newMsg); } } catch (e) { toast.error("Failed to parse SSE data:" + e + " \nLine:" + data); @@ -287,7 +294,7 @@ export async function chatStreamRequest(abortSignal?: AbortSignal) { } } } catch (error) { - console.error("Stream reading error:" + error); + console.error("Stream reading error:" + error); throw error; } finally { // Ensure reader is released diff --git a/contrib/chat-plugin/src/libs/state.ts b/contrib/chat-plugin/src/libs/state.ts index 994911cd..29d84730 100644 --- a/contrib/chat-plugin/src/libs/state.ts +++ b/contrib/chat-plugin/src/libs/state.ts @@ -1,5 +1,4 @@ import { create } from "zustand"; -import { cn } from "./utils"; export type Status = "loading" | "online" | "offline" | "unknown"; @@ -7,6 +6,7 @@ export type ChatMessage = { role: string; message: string; timestamp: Date; + reasoning?: string; // Optional field for reasoning support }; export type Job = { @@ -43,7 +43,7 @@ interface State { setAllModelsInCurrentJob: (models: string[]) => void; setCurrentModel: (model: string | null) => void; addChatMessage: (chat: ChatMessage) => void; - updateLastChatMessage: (lastMessageContent: string) => void; + updateLastChatMessage: (lastChat: ChatMessage) => void; cleanChat: () => void; } @@ -72,11 +72,12 @@ export const useChatStore = create((set) => ({ setCurrentModel: (model) => set({ currentModel: model }), addChatMessage: (log) => set((state) => ({ chatMsgs: [...state.chatMsgs, log] })), - updateLastChatMessage: (lastMessageContent) => set((state) => { + updateLastChatMessage: (lastChat) => set((state) => { const chatMsgs = [...state.chatMsgs]; if (chatMsgs.length > 0) { - chatMsgs[chatMsgs.length - 1].message = lastMessageContent; - chatMsgs[chatMsgs.length - 1].timestamp = new Date(); + chatMsgs[chatMsgs.length - 1] = lastChat; + } else { + chatMsgs.push(lastChat); } return { chatMsgs }; }),