feat: add collapsible reasoning content to MessageBubble component, refactor message for response

This commit is contained in:
ecwu
2025-01-26 10:52:14 +00:00
parent c13bce63a9
commit 233397ba46

View File

@@ -1,4 +1,4 @@
import { XMarkIcon } from "@heroicons/react/24/outline"; import { LightBulbIcon, XMarkIcon } from "@heroicons/react/24/outline";
import Markdown from "react-markdown"; import Markdown from "react-markdown";
import { useContext, useState, useMemo } from "react"; import { useContext, useState, useMemo } from "react";
import { ChatStoreMessage } from "@/types/chatstore"; import { ChatStoreMessage } from "@/types/chatstore";
@@ -16,6 +16,11 @@ import {
} from "@/components/ui/chat/chat-bubble"; } from "@/components/ui/chat/chat-bubble";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { import {
ClipboardIcon, ClipboardIcon,
@@ -24,8 +29,10 @@ import {
MessageSquarePlusIcon, MessageSquarePlusIcon,
AudioLinesIcon, AudioLinesIcon,
LoaderCircleIcon, LoaderCircleIcon,
ChevronsUpDownIcon,
} from "lucide-react"; } from "lucide-react";
import { AppChatStoreContext, AppContext } from "@/pages/App"; import { AppChatStoreContext, AppContext } from "@/pages/App";
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
interface HideMessageProps { interface HideMessageProps {
chat: ChatStoreMessage; chat: ChatStoreMessage;
@@ -283,75 +290,165 @@ export default function Message(props: { messageIndex: number }) {
</span> </span>
</div> </div>
)} )}
<ChatBubble {chat.role === "assistant" || chat.role === "received" ? (
variant={chat.role === "assistant" ? "received" : "sent"} <div className="border-b border-border dark:border-border-dark pb-4">
className={chat.role !== "assistant" ? "flex-row-reverse" : ""} {chat.reasoning_content ? (
> <Collapsible className="mb-3 w-[450px]">
<ChatBubbleMessage isLoading={false}> <div className="flex items-center justify-between">
{chat.hide ? ( <div className="flex items-center gap-2">
<MessageHide chat={chat} /> <h4 className="text-sm font-semibold text-gray-500">
) : typeof chat.content !== "string" ? ( {chat.response_model_name}
<MessageDetail chat={chat} renderMarkdown={renderMarkdown} /> </h4>
) : chat.tool_calls ? ( <CollapsibleTrigger asChild>
<MessageToolCall chat={chat} copyToClipboard={copyToClipboard} /> <Button variant="ghost" size="sm">
) : chat.role === "tool" ? ( <LightBulbIcon className="h-3 w-3 text-gray-500" />
<MessageToolResp chat={chat} copyToClipboard={copyToClipboard} /> <span className="sr-only">Toggle</span>
) : renderMarkdown ? ( </Button>
<Markdown>{getMessageText(chat)}</Markdown> </CollapsibleTrigger>
) : ( </div>
<div className="message-content"> </div>
{chat.content && <CollapsibleContent className="ml-5 text-gray-500">
(chat.logprobs && renderColor {chat.reasoning_content}
? chat.logprobs.content </CollapsibleContent>
.filter((c) => c.token) </Collapsible>
.map((c) => ( ) : null}
<div <div>
style={{ {chat.hide ? (
backgroundColor: logprobToColor(c.logprob), <MessageHide chat={chat} />
display: "inline", ) : typeof chat.content !== "string" ? (
}} <MessageDetail chat={chat} renderMarkdown={renderMarkdown} />
> ) : chat.tool_calls ? (
{c.token} <MessageToolCall chat={chat} copyToClipboard={copyToClipboard} />
</div> ) : chat.role === "tool" ? (
)) <MessageToolResp chat={chat} copyToClipboard={copyToClipboard} />
: getMessageText(chat))} ) : renderMarkdown ? (
</div> <Markdown>{getMessageText(chat)}</Markdown>
)} ) : (
</ChatBubbleMessage> <div className="message-content max-w-full md:max-w-[75%]">
<ChatBubbleActionWrapper> {chat.content &&
<ChatBubbleAction (chat.logprobs && renderColor
icon={ ? chat.logprobs.content
chat.hide ? ( .filter((c) => c.token)
<MessageSquarePlusIcon className="size-4" /> .map((c) => (
) : ( <div
<MessageSquareOffIcon className="size-4" /> style={{
) backgroundColor: logprobToColor(c.logprob),
} display: "inline",
onClick={() => { }}
chatStore.history[messageIndex].hide = >
!chatStore.history[messageIndex].hide; {c.token}
chatStore.totalTokens = 0; </div>
for (const i of chatStore.history ))
.filter(({ hide }) => !hide) : getMessageText(chat))}
.slice(chatStore.postBeginIndex) </div>
.map(({ token }) => token)) { )}
chatStore.totalTokens += i; </div>
<div className="flex md:opacity-0 hover:opacity-100 transition-opacity">
<ChatBubbleAction
icon={
chat.hide ? (
<MessageSquarePlusIcon className="size-4" />
) : (
<MessageSquareOffIcon className="size-4" />
)
} }
setChatStore({ ...chatStore }); onClick={() => {
}} chatStore.history[messageIndex].hide =
/> !chatStore.history[messageIndex].hide;
<ChatBubbleAction chatStore.totalTokens = 0;
icon={<PencilIcon className="size-4" />} for (const i of chatStore.history
onClick={() => setShowEdit(true)} .filter(({ hide }) => !hide)
/> .slice(chatStore.postBeginIndex)
<ChatBubbleAction .map(({ token }) => token)) {
icon={<ClipboardIcon className="size-4" />} chatStore.totalTokens += i;
onClick={() => copyToClipboard(getMessageText(chat))} }
/> setChatStore({ ...chatStore });
{chatStore.tts_api && chatStore.tts_key && <TTSButton chat={chat} />} }}
<TTSPlay chat={chat} /> />
</ChatBubbleActionWrapper> <ChatBubbleAction
</ChatBubble> icon={<PencilIcon className="size-4" />}
onClick={() => setShowEdit(true)}
/>
<ChatBubbleAction
icon={<ClipboardIcon className="size-4" />}
onClick={() => copyToClipboard(getMessageText(chat))}
/>
{chatStore.tts_api && chatStore.tts_key && (
<TTSButton chat={chat} />
)}
<TTSPlay chat={chat} />
</div>
</div>
) : (
<ChatBubble variant="sent" className="flex-row-reverse">
<ChatBubbleMessage isLoading={false}>
{chat.hide ? (
<MessageHide chat={chat} />
) : typeof chat.content !== "string" ? (
<MessageDetail chat={chat} renderMarkdown={renderMarkdown} />
) : chat.tool_calls ? (
<MessageToolCall chat={chat} copyToClipboard={copyToClipboard} />
) : chat.role === "tool" ? (
<MessageToolResp chat={chat} copyToClipboard={copyToClipboard} />
) : renderMarkdown ? (
<Markdown>{getMessageText(chat)}</Markdown>
) : (
<div className="message-content">
{chat.content &&
(chat.logprobs && renderColor
? chat.logprobs.content
.filter((c) => c.token)
.map((c) => (
<div
style={{
backgroundColor: logprobToColor(c.logprob),
display: "inline",
}}
>
{c.token}
</div>
))
: getMessageText(chat))}
</div>
)}
</ChatBubbleMessage>
<ChatBubbleActionWrapper>
<ChatBubbleAction
icon={
chat.hide ? (
<MessageSquarePlusIcon className="size-4" />
) : (
<MessageSquareOffIcon className="size-4" />
)
}
onClick={() => {
chatStore.history[messageIndex].hide =
!chatStore.history[messageIndex].hide;
chatStore.totalTokens = 0;
for (const i of chatStore.history
.filter(({ hide }) => !hide)
.slice(chatStore.postBeginIndex)
.map(({ token }) => token)) {
chatStore.totalTokens += i;
}
setChatStore({ ...chatStore });
}}
/>
<ChatBubbleAction
icon={<PencilIcon className="size-4" />}
onClick={() => setShowEdit(true)}
/>
<ChatBubbleAction
icon={<ClipboardIcon className="size-4" />}
onClick={() => copyToClipboard(getMessageText(chat))}
/>
{chatStore.tts_api && chatStore.tts_key && (
<TTSButton chat={chat} />
)}
<TTSPlay chat={chat} />
</ChatBubbleActionWrapper>
</ChatBubble>
)}
<EditMessage showEdit={showEdit} setShowEdit={setShowEdit} chat={chat} /> <EditMessage showEdit={showEdit} setShowEdit={setShowEdit} chat={chat} />
{chatStore.develop_mode && ( {chatStore.develop_mode && (
<div <div