feat: add collapsible reasoning content to MessageBubble component, refactor message for response
This commit is contained in:
@@ -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 { useContext, useState, useMemo } from "react";
|
||||
import { ChatStoreMessage } from "@/types/chatstore";
|
||||
@@ -16,6 +16,11 @@ import {
|
||||
} from "@/components/ui/chat/chat-bubble";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import {
|
||||
ClipboardIcon,
|
||||
@@ -24,8 +29,10 @@ import {
|
||||
MessageSquarePlusIcon,
|
||||
AudioLinesIcon,
|
||||
LoaderCircleIcon,
|
||||
ChevronsUpDownIcon,
|
||||
} from "lucide-react";
|
||||
import { AppChatStoreContext, AppContext } from "@/pages/App";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
|
||||
|
||||
interface HideMessageProps {
|
||||
chat: ChatStoreMessage;
|
||||
@@ -283,75 +290,165 @@ export default function Message(props: { messageIndex: number }) {
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<ChatBubble
|
||||
variant={chat.role === "assistant" ? "received" : "sent"}
|
||||
className={chat.role !== "assistant" ? "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;
|
||||
{chat.role === "assistant" || chat.role === "received" ? (
|
||||
<div className="border-b border-border dark:border-border-dark pb-4">
|
||||
{chat.reasoning_content ? (
|
||||
<Collapsible className="mb-3 w-[450px]">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<h4 className="text-sm font-semibold text-gray-500">
|
||||
{chat.response_model_name}
|
||||
</h4>
|
||||
<CollapsibleTrigger asChild>
|
||||
<Button variant="ghost" size="sm">
|
||||
<LightBulbIcon className="h-3 w-3 text-gray-500" />
|
||||
<span className="sr-only">Toggle</span>
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
</div>
|
||||
</div>
|
||||
<CollapsibleContent className="ml-5 text-gray-500">
|
||||
{chat.reasoning_content}
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
) : null}
|
||||
<div>
|
||||
{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 max-w-full md:max-w-[75%]">
|
||||
{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>
|
||||
)}
|
||||
</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 });
|
||||
}}
|
||||
/>
|
||||
<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>
|
||||
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} />
|
||||
</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} />
|
||||
{chatStore.develop_mode && (
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user