feat: enhance MessageBubble with custom Markdown rendering, update Navbar layout, and integrate Search component in App

This commit is contained in:
ecwu
2025-01-26 21:15:34 +00:00
parent 233397ba46
commit 55e8186479
5 changed files with 75 additions and 102 deletions

View File

@@ -322,7 +322,35 @@ export default function Message(props: { messageIndex: number }) {
) : chat.role === "tool" ? (
<MessageToolResp chat={chat} copyToClipboard={copyToClipboard} />
) : renderMarkdown ? (
<Markdown>{getMessageText(chat)}</Markdown>
<div className="message-content max-w-full md:max-w-[75%]">
<Markdown
break={true}
components={{
code: ({ children }) => (
<code className="bg-muted px-1 py-0.5 rounded">
{children}
</code>
),
pre: ({ children }) => (
<pre className="bg-muted p-4 rounded-lg overflow-auto">
{children}
</pre>
),
a: ({ href, children }) => (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
{children}
</a>
),
}}
>
{getMessageText(chat)}
</Markdown>
</div>
) : (
<div className="message-content max-w-full md:max-w-[75%]">
{chat.content &&

View File

@@ -22,7 +22,10 @@ export function ModeToggle() {
const { setTheme } = useTheme();
return (
<Select onValueChange={(value) => setTheme(value as Theme)}>
<Select
onValueChange={(value) => setTheme(value as Theme)}
defaultValue={useTheme().theme}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select theme" />
</SelectTrigger>

View File

@@ -32,113 +32,51 @@ import { getTotalCost } from "@/utils/totalCost";
import { Tr } from "@/translate";
import { useContext } from "react";
import Search from "@/components/Search";
import Settings from "@/components/Settings";
import APIListMenu from "./ListAPI";
const Navbar: React.FC = () => {
const { chatStore, setChatStore } = useContext(AppChatStoreContext);
return (
<header className="flex sticky top-0 bg-background h-14 shrink-0 items-center border-b z-50">
<div className="flex flex-col w-full">
<div className="flex flex-1 items-center gap-2">
<div className="flex items-center gap-2 px-3">
<SidebarTrigger />
<Separator orientation="vertical" className="mr-2 h-4" />
<h1 className="text-lg font-bold">{chatStore.model}</h1>
<div className="flex justify-between items-center gap-2">
<div>
<div className="dropdown lg:hidden flex items-center gap-2">
<Badge variant="outline">
{chatStore.totalTokens.toString()}
</Badge>
<Popover>
<PopoverTrigger>
<EllipsisIcon />
</PopoverTrigger>
<PopoverContent>
<p>
Tokens: {chatStore.totalTokens}/{chatStore.maxTokens}
</p>
<p>
Cut(s): {chatStore.postBeginIndex}/
{chatStore.history.filter(({ hide }) => !hide).length}
</p>
<p>
Cost: ${chatStore.cost?.toFixed(4)} / $
{getTotalCost().toFixed(2)}
</p>
</PopoverContent>
</Popover>
</div>
<div className="hidden lg:inline-grid">
<Menubar>
<MenubarMenu>
<MenubarTrigger>
<WholeWordIcon className="w-4 h-4 mr-2" />{" "}
{chatStore.totalTokens}
<CircleDollarSignIcon className="w-4 h-4 mx-2" />
{chatStore.cost?.toFixed(4)}
</MenubarTrigger>
<MenubarContent>
<MenubarItem>
<RulerIcon className="w-4 h-4 mr-2" />
Max Length: {chatStore.maxTokens}
</MenubarItem>
<MenubarItem>
<ReceiptIcon className="w-4 h-4 mr-2" />
Price:{" "}
{models[chatStore.model]?.price?.prompt *
1000 *
1000}$ / 1M input tokens
</MenubarItem>
<MenubarItem>
<WalletIcon className="w-4 h-4 mr-2" />
Total: {getTotalCost().toFixed(
2
)}$
</MenubarItem>
<MenubarItem>
<ArrowUpDownIcon className="w-4 h-4 mr-2" />
{chatStore.streamMode ? (
<>
<span>{Tr("STREAM")}</span>·
<span style={{ color: "gray" }}>{Tr("FETCH")}</span>
</>
) : (
<>
<span style={{ color: "gray" }}>
{Tr("STREAM")}
</span>
·<span>{Tr("FETCH")}</span>
</>
)}
</MenubarItem>
<MenubarItem>
<ScissorsIcon className="w-4 h-4 mr-2" />
{
chatStore.postBeginIndex
} / {chatStore.history.length}
</MenubarItem>
<MenubarSeparator />
<MenubarItem disabled>
Switch to Model (TODO):
</MenubarItem>
<MenubarCheckboxItem checked>gpt-4o</MenubarCheckboxItem>
<MenubarCheckboxItem>gpt-o1</MenubarCheckboxItem>
<MenubarCheckboxItem>gpt-o1-mini</MenubarCheckboxItem>
<MenubarCheckboxItem>gpt-o3</MenubarCheckboxItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
</div>
</div>
</div>
</div>
<div className="flex ml-auto gap-2 px-3">
<Settings />
<Search />
</div>
</div>
<Popover>
<PopoverTrigger className="absolute left-1/2 bottom-0 transform -translate-x-1/2 translate-y-1/2">
<div className="rounded-full bg-primary/10 hover:bg-primary/20 p-1 cursor-pointer">
<EllipsisIcon className="w-4 h-4" />
</div>
</PopoverTrigger>
<PopoverContent className="w-screen">
<div className="flex justify-between items-center px-4 py-2 border-b">
<div className="flex items-center gap-2">
<ReceiptIcon className="w-4 h-4" />
<span className="text-sm font-medium">
Generated Tokens: {chatStore.totalTokens.toString()}
</span>
</div>
<div className="flex items-center gap-2">
<WalletIcon className="w-4 h-4" />
<span className="text-sm font-medium">
Cost: ${getTotalCost().toFixed(2)}
</span>
</div>
</div>
<APIListMenu />
</PopoverContent>
</Popover>
</div>
</header>
);
};

View File

@@ -89,6 +89,8 @@ import {
import { useToast } from "@/hooks/use-toast";
import { ModeToggle } from "@/components/ModeToggle";
import Search from "@/components/Search";
import Navbar from "@/components/navbar";
export function App() {
@@ -352,7 +354,10 @@ export function App() {
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<div className="flex items-start gap-2">
<ModeToggle />
<Search />
</div>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive">{Tr("DEL")}</Button>

View File

@@ -224,7 +224,7 @@ export default function ChatBOX() {
token: data.usage?.completion_tokens_details
? data.usage.completion_tokens -
data.usage.completion_tokens_details.reasoning_tokens
: (data.usage.completion_tokens ?? calculate_token_length(msg.content)),
: data.usage.completion_tokens ?? calculate_token_length(msg.content),
example: false,
audio: null,
logprobs: data.choices[0]?.logprobs,
@@ -414,7 +414,6 @@ export default function ChatBOX() {
return (
<>
<APIListMenu />
<div className="grow flex flex-col p-2 w-full">
<ChatMessageList>
{chatStore.history.length === 0 && (