feat: enhance MessageBubble with custom Markdown rendering, update Navbar layout, and integrate Search component in App
This commit is contained in:
@@ -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 &&
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -32,112 +32,50 @@ 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-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 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>
|
||||
<div className="flex ml-auto gap-2 px-3">
|
||||
<Settings />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex ml-auto gap-2 px-3">
|
||||
<Settings />
|
||||
<Search />
|
||||
</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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
<ModeToggle />
|
||||
<div className="flex items-start gap-2">
|
||||
<ModeToggle />
|
||||
<Search />
|
||||
</div>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="destructive">{Tr("DEL")}</Button>
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
Reference in New Issue
Block a user