refactor: update Settings and Navbar components for improved state management and UI integration

This commit is contained in:
ecwu
2025-01-03 01:17:54 +08:00
parent 6fe1012270
commit 30583a421d
4 changed files with 112 additions and 103 deletions

View File

@@ -475,7 +475,7 @@ const Choice = (props: {
); );
}; };
export default (props: { setShow: Dispatch<boolean> }) => { export default (props: {}) => {
const ctx = useContext(AppContext); const ctx = useContext(AppContext);
if (ctx === null) return <></>; if (ctx === null) return <></>;
@@ -497,13 +497,14 @@ export default (props: { setShow: Dispatch<boolean> }) => {
const [totalCost, setTotalCost] = useState(getTotalCost()); const [totalCost, setTotalCost] = useState(getTotalCost());
// @ts-ignore // @ts-ignore
const { langCode, setLangCode } = useContext(langCodeContext); const { langCode, setLangCode } = useContext(langCodeContext);
const [open, setOpen] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
themeChange(false); themeChange(false);
const handleKeyPress = (event: any) => { const handleKeyPress = (event: any) => {
if (event.keyCode === 27) { if (event.keyCode === 27) {
// keyCode for ESC key is 27 // keyCode for ESC key is 27
props.setShow(false); setOpen(false);
} }
}; };
@@ -514,7 +515,7 @@ export default (props: { setShow: Dispatch<boolean> }) => {
}; };
}, []); // The empty dependency array ensures that the effect runs only once }, []); // The empty dependency array ensures that the effect runs only once
return ( return (
<Sheet> <Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild> <SheetTrigger asChild>
<Button variant="outline" className="flex-grow"> <Button variant="outline" className="flex-grow">
{Tr("Settings")} {Tr("Settings")}

View File

@@ -26,6 +26,8 @@ import {
WalletIcon, WalletIcon,
ArrowUpDownIcon, ArrowUpDownIcon,
ScissorsIcon, ScissorsIcon,
SearchIcon,
CogIcon,
} from "lucide-react"; } from "lucide-react";
import { AppContext } from "@/pages/App"; import { AppContext } from "@/pages/App";
import { models } from "@/types/models"; import { models } from "@/types/models";
@@ -33,6 +35,9 @@ import { getTotalCost } from "@/utils/totalCost";
import { Tr } from "@/translate"; import { Tr } from "@/translate";
import { useContext } from "react"; import { useContext } from "react";
import { Button } from "./ui/button";
import Search from "@/search";
import Settings from "./Settings";
const Navbar: React.FC = () => { const Navbar: React.FC = () => {
const ctx = useContext(AppContext); const ctx = useContext(AppContext);
@@ -40,90 +45,100 @@ const Navbar: React.FC = () => {
const { chatStore, setChatStore } = ctx; const { chatStore, setChatStore } = ctx;
return ( return (
<header className="flex sticky top-0 bg-background h-16 shrink-0 items-center gap-2 border-b z-50"> <header className="flex sticky top-0 bg-background h-14 shrink-0 items-center border-b z-50">
<div className="flex items-center gap-2 px-3"> <div className="flex flex-1 items-center gap-2">
<SidebarTrigger /> <div className="flex items-center gap-2 px-3">
<Separator orientation="vertical" className="mr-2 h-4" /> <SidebarTrigger />
<h1 className="text-lg font-bold">{chatStore.model}</h1> <Separator orientation="vertical" className="mr-2 h-4" />
<div className="flex justify-between items-center gap-2"> <h1 className="text-lg font-bold">{chatStore.model}</h1>
<div> <div className="flex justify-between items-center gap-2">
<div className="dropdown lg:hidden flex items-center gap-2"> <div>
<Badge variant="outline"> <div className="dropdown lg:hidden flex items-center gap-2">
{chatStore.totalTokens.toString()} <Badge variant="outline">
</Badge> {chatStore.totalTokens.toString()}
<Popover> </Badge>
<PopoverTrigger> <Popover>
<EllipsisIcon /> <PopoverTrigger>
</PopoverTrigger> <EllipsisIcon />
<PopoverContent> </PopoverTrigger>
<p> <PopoverContent>
Tokens: {chatStore.totalTokens}/{chatStore.maxTokens} <p>
</p> Tokens: {chatStore.totalTokens}/{chatStore.maxTokens}
<p> </p>
Cut(s): {chatStore.postBeginIndex}/ <p>
{chatStore.history.filter(({ hide }) => !hide).length} Cut(s): {chatStore.postBeginIndex}/
</p> {chatStore.history.filter(({ hide }) => !hide).length}
<p> </p>
Cost: ${chatStore.cost?.toFixed(4)} / $ <p>
{getTotalCost().toFixed(2)} Cost: ${chatStore.cost?.toFixed(4)} / $
</p> {getTotalCost().toFixed(2)}
</PopoverContent> </p>
</Popover> </PopoverContent>
</div> </Popover>
<div className="hidden lg:inline-grid"> </div>
<Menubar> <div className="hidden lg:inline-grid">
<MenubarMenu> <Menubar>
<MenubarTrigger> <MenubarMenu>
<WholeWordIcon className="w-4 h-4 mr-2" />{" "} <MenubarTrigger>
{chatStore.totalTokens} <WholeWordIcon className="w-4 h-4 mr-2" />{" "}
<CircleDollarSignIcon className="w-4 h-4 mx-2" /> {chatStore.totalTokens}
{chatStore.cost?.toFixed(4)} <CircleDollarSignIcon className="w-4 h-4 mx-2" />
</MenubarTrigger> {chatStore.cost?.toFixed(4)}
<MenubarContent> </MenubarTrigger>
<MenubarItem> <MenubarContent>
<RulerIcon className="w-4 h-4 mr-2" /> <MenubarItem>
Max Length: {chatStore.maxTokens} <RulerIcon className="w-4 h-4 mr-2" />
</MenubarItem> Max Length: {chatStore.maxTokens}
<MenubarItem> </MenubarItem>
<ReceiptIcon className="w-4 h-4 mr-2" /> <MenubarItem>
Price:{" "} <ReceiptIcon className="w-4 h-4 mr-2" />
{models[chatStore.model]?.price?.prompt * 1000 * 1000}$ / Price:{" "}
1M input tokens {models[chatStore.model]?.price?.prompt * 1000 * 1000}$
</MenubarItem> / 1M input tokens
<MenubarItem> </MenubarItem>
<WalletIcon className="w-4 h-4 mr-2" /> <MenubarItem>
Total: {getTotalCost().toFixed(2)}$ <WalletIcon className="w-4 h-4 mr-2" />
</MenubarItem> Total: {getTotalCost().toFixed(2)}$
<MenubarItem> </MenubarItem>
<ArrowUpDownIcon className="w-4 h-4 mr-2" /> <MenubarItem>
{chatStore.streamMode ? ( <ArrowUpDownIcon className="w-4 h-4 mr-2" />
<> {chatStore.streamMode ? (
<span>{Tr("STREAM")}</span>· <>
<span style={{ color: "gray" }}>{Tr("FETCH")}</span> <span>{Tr("STREAM")}</span>·
</> <span style={{ color: "gray" }}>{Tr("FETCH")}</span>
) : ( </>
<> ) : (
<span style={{ color: "gray" }}>{Tr("STREAM")}</span>· <>
<span>{Tr("FETCH")}</span> <span style={{ color: "gray" }}>
</> {Tr("STREAM")}
)} </span>
</MenubarItem> ·<span>{Tr("FETCH")}</span>
<MenubarItem> </>
<ScissorsIcon className="w-4 h-4 mr-2" /> )}
{chatStore.postBeginIndex} / {chatStore.history.length} </MenubarItem>
</MenubarItem> <MenubarItem>
<MenubarSeparator /> <ScissorsIcon className="w-4 h-4 mr-2" />
<MenubarItem disabled>Switch to Model (TODO):</MenubarItem> {chatStore.postBeginIndex} / {chatStore.history.length}
<MenubarCheckboxItem checked>gpt-4o</MenubarCheckboxItem> </MenubarItem>
<MenubarCheckboxItem>gpt-o1</MenubarCheckboxItem> <MenubarSeparator />
<MenubarCheckboxItem>gpt-o1-mini</MenubarCheckboxItem> <MenubarItem disabled>
<MenubarCheckboxItem>gpt-o3</MenubarCheckboxItem> Switch to Model (TODO):
</MenubarContent> </MenubarItem>
</MenubarMenu> <MenubarCheckboxItem checked>gpt-4o</MenubarCheckboxItem>
</Menubar> <MenubarCheckboxItem>gpt-o1</MenubarCheckboxItem>
<MenubarCheckboxItem>gpt-o1-mini</MenubarCheckboxItem>
<MenubarCheckboxItem>gpt-o3</MenubarCheckboxItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
</div>
</div> </div>
<ModeToggle />
</div> </div>
<ModeToggle /> </div>
<div className="flex ml-auto gap-2 px-3">
<Settings />
<Search />
</div> </div>
</div> </div>
</header> </header>

View File

@@ -429,18 +429,6 @@ export default function ChatBOX() {
return ( return (
<> <>
<div className="flex flex-col p-2 gap-2 w-full"> <div className="flex flex-col p-2 gap-2 w-full">
<div className="flex items-center gap-2 justify-between">
{true && <Settings setShow={setShowSettings} />}
<Button
variant="outline"
size="icon"
onClick={() => setShowSearch(true)}
>
<SearchIcon />
</Button>
</div>
{showSearch && <Search show={showSearch} setShow={setShowSearch} />}
{!chatStore.apiKey && ( {!chatStore.apiKey && (
<Alert> <Alert>
<KeyIcon className="h-4 w-4" /> <KeyIcon className="h-4 w-4" />

View File

@@ -25,6 +25,8 @@ import {
import { Input } from "./components/ui/input"; import { Input } from "./components/ui/input";
import { AppContext } from "./pages/App"; import { AppContext } from "./pages/App";
import { Button } from "./components/ui/button";
import { SearchIcon } from "lucide-react";
interface ChatStoreSearchResult { interface ChatStoreSearchResult {
key: IDBValidKey; key: IDBValidKey;
@@ -33,10 +35,7 @@ interface ChatStoreSearchResult {
preview: string; preview: string;
} }
export default function Search(props: { export default function Search() {
show: boolean;
setShow: (show: boolean) => void;
}) {
const ctx = useContext(AppContext); const ctx = useContext(AppContext);
if (ctx === null) return <></>; if (ctx === null) return <></>;
const { setSelectedChatIndex, db } = ctx; const { setSelectedChatIndex, db } = ctx;
@@ -46,9 +45,15 @@ export default function Search(props: {
const [searchingNow, setSearchingNow] = useState<number>(0); const [searchingNow, setSearchingNow] = useState<number>(0);
const [pageIndex, setPageIndex] = useState<number>(0); const [pageIndex, setPageIndex] = useState<number>(0);
const searchAbortRef = useRef<AbortController | null>(null); const searchAbortRef = useRef<AbortController | null>(null);
const [open, setOpen] = useState<boolean>(false);
return ( return (
<Dialog open={props.show} onOpenChange={props.setShow}> <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger>
<Button variant="outline" size="icon">
<SearchIcon />
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[80%]"> <DialogContent className="sm:max-w-[80%]">
<DialogHeader> <DialogHeader>
<DialogTitle>Search</DialogTitle> <DialogTitle>Search</DialogTitle>
@@ -160,7 +165,7 @@ export default function Search(props: {
key={result.key as number} key={result.key as number}
onClick={() => { onClick={() => {
setSelectedChatIndex(parseInt(result.key.toString())); setSelectedChatIndex(parseInt(result.key.toString()));
props.setShow(false); setOpen(false);
}} }}
> >
<div className="m-1 p-1 font-bold"> <div className="m-1 p-1 font-bold">