Refactor Settings component and integrate shadcn UI elements for improved user experience

This commit is contained in:
ecwu
2024-12-20 16:06:01 +08:00
parent 7ecdae8f1d
commit bac65994b0
6 changed files with 1095 additions and 843 deletions

View File

@@ -0,0 +1,13 @@
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet";

View File

@@ -4,6 +4,7 @@ import { useState, useEffect } from "preact/hooks";
import { App } from "@/pages/App";
import { Tr, langCodeContext, LANG_OPTIONS } from "@/translate";
import { SidebarProvider } from "@/components/ui/sidebar";
import { Toaster } from "@/components/ui/toaster";
function Base() {
const [langCode, _setLangCode] = useState("en-US");
@@ -49,6 +50,7 @@ function Base() {
<langCodeContext.Provider value={{ langCode, setLangCode }}>
<SidebarProvider>
<App />
<Toaster />
</SidebarProvider>
</langCodeContext.Provider>
);

View File

@@ -12,10 +12,42 @@ import { newChatStore } from "@/types/newChatstore";
import { STORAGE_NAME, STORAGE_NAME_SELECTED } from "@/const";
import { upgrade } from "@/indexedDB/upgrade";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarRail,
SidebarInset,
SidebarTrigger,
} from "@/components/ui/sidebar";
import { Button } from "@/components/ui/button";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { useToast } from "@/hooks/use-toast";
import { Separator } from "@/components/ui/separator";
export function App() {
// init selected index
const [selectedChatIndex, setSelectedChatIndex] = useState(
parseInt(localStorage.getItem(STORAGE_NAME_SELECTED) ?? "1"),
parseInt(localStorage.getItem(STORAGE_NAME_SELECTED) ?? "1")
);
console.log("selectedChatIndex", selectedChatIndex);
useEffect(() => {
@@ -27,6 +59,8 @@ export function App() {
upgrade,
});
const { toast } = useToast();
const getChatStoreByIndex = async (index: number): Promise<ChatStore> => {
const ret: ChatStore = await (await db).get(STORAGE_NAME, index);
if (ret === null || ret === undefined) return newChatStore({});
@@ -54,7 +88,7 @@ export function App() {
const max = chatStore.maxTokens - chatStore.tokenMargin;
let sum = 0;
chatStore.postBeginIndex = chatStore.history.filter(
({ hide }) => !hide,
({ hide }) => !hide
).length;
for (const msg of chatStore.history
.filter(({ hide }) => !hide)
@@ -69,7 +103,7 @@ export function App() {
// manually estimate token
chatStore.totalTokens = calculate_token_length(
chatStore.systemMessageContent,
chatStore.systemMessageContent
);
for (const msg of chatStore.history
.filter(({ hide }) => !hide)
@@ -82,7 +116,7 @@ export function App() {
// update total tokens
chatStore.totalTokens = calculate_token_length(
chatStore.systemMessageContent,
chatStore.systemMessageContent
);
for (const msg of chatStore.history
.filter(({ hide }) => !hide)
@@ -101,7 +135,7 @@ export function App() {
// all chat store indexes
const [allChatStoreIndexes, setAllChatStoreIndexes] = useState<IDBValidKey>(
[],
[]
);
const handleNewChatStoreWithOldOne = async (chatStore: ChatStore) => {
@@ -114,7 +148,6 @@ export function App() {
};
const handleDEL = async () => {
if (!confirm("Are you sure you want to delete this chat history?")) return;
console.log("remove item", `${STORAGE_NAME}-${selectedChatIndex}`);
(await db).delete(STORAGE_NAME, selectedChatIndex);
const newAllChatStoreIndexes = await (await db).getAllKeys(STORAGE_NAME);
@@ -129,6 +162,11 @@ export function App() {
console.log("next is", next);
setSelectedChatIndex(next as number);
setAllChatStoreIndexes(newAllChatStoreIndexes);
toast({
title: "Chat history deleted",
description: `Chat history ${selectedChatIndex} has been deleted.`,
});
};
const handleCLS = async () => {
@@ -176,53 +214,79 @@ export function App() {
}, []);
return (
<div className="flex text-sm h-full">
<div className="flex flex-col h-full p-2 bg-primary">
<div className="grow overflow-scroll">
<button
className="btn btn-sm btn-info p-1 my-1 w-full"
onClick={handleNewChatStore}
>
{Tr("NEW")}
</button>
<ul className="pt-2">
<>
<Sidebar>
<SidebarHeader>
<Button onClick={handleNewChatStore}>
<span>{Tr("New")}</span>
</Button>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Conversation</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{(allChatStoreIndexes as number[])
.slice()
.reverse()
.map((i) => {
// reverse
return (
<li>
<button
className={`w-full my-1 p-1 btn btn-sm ${
i === selectedChatIndex ? "btn-accent" : "btn-secondary"
}`}
<SidebarMenuItem
key={i}
onClick={() => setSelectedChatIndex(i)}
>
{i}
</button>
</li>
<SidebarMenuButton
asChild
isActive={i === selectedChatIndex}
>
<span>{i}</span>
</SidebarMenuButton>
</SidebarMenuItem>
);
})}
</ul>
</div>
<div>
<button
className="btn btn-warning btn-sm p-1 my-1 w-full"
onClick={async () => handleDEL()}
>
{Tr("DEL")}
</button>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive">{Tr("DEL")}</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the
chat history.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={handleDEL}>
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{chatStore.develop_mode && (
<button
className="btn btn-sm btn-warning p-1 my-1 w-full"
onClick={async () => handleCLS()}
>
{Tr("CLS")}
</button>
<Button onClick={handleCLS} variant="destructive">
<span>{Tr("CLS")}</span>
</Button>
)}
</SidebarFooter>
<SidebarRail />
</Sidebar>
<SidebarInset>
<header className="flex h-16 shrink-0 items-center gap-2 border-b">
<div className="flex items-center gap-2 px-3">
<SidebarTrigger />
<Separator orientation="vertical" className="mr-2 h-4" />
<h1 className="text-lg font-bold">MikuChat</h1>
</div>
</div>
</header>
<ChatBOX
db={db}
chatStore={chatStore}
@@ -230,6 +294,7 @@ export function App() {
selectedChatIndex={selectedChatIndex}
setSelectedChatIndex={setSelectedChatIndex}
/>
</div>
</SidebarInset>
</>
);
}

View File

@@ -131,7 +131,7 @@ export default function ChatBOX(props: {
// update tool call arguments
const tool = allChunkTool.find(
(tool) => tool.index === tool_call.index,
(tool) => tool.index === tool_call.index
);
if (!tool) {
@@ -146,7 +146,7 @@ export default function ChatBOX(props: {
allChunkMessage.join("") +
allChunkTool.map((tool) => {
return `Tool Call ID: ${tool.id}\nType: ${tool.type}\nFunction: ${tool.function.name}\nArguments: ${tool.function.arguments}`;
}),
})
);
}
setShowGenerating(false);
@@ -295,7 +295,7 @@ export default function ChatBOX(props: {
setShowGenerating(true);
const response = await client._fetch(
chatStore.streamMode,
chatStore.logprobs,
chatStore.logprobs
);
const contentType = response.headers.get("content-type");
if (contentType?.startsWith("text/event-stream")) {
@@ -365,33 +365,33 @@ export default function ChatBOX(props: {
const [templates, _setTemplates] = useState(
JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE) || "[]",
) as TemplateChatStore[],
localStorage.getItem(STORAGE_NAME_TEMPLATE) || "[]"
) as TemplateChatStore[]
);
const [templateAPIs, _setTemplateAPIs] = useState(
JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE_API) || "[]",
) as TemplateAPI[],
localStorage.getItem(STORAGE_NAME_TEMPLATE_API) || "[]"
) as TemplateAPI[]
);
const [templateAPIsWhisper, _setTemplateAPIsWhisper] = useState(
JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_WHISPER) || "[]",
) as TemplateAPI[],
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_WHISPER) || "[]"
) as TemplateAPI[]
);
const [templateAPIsTTS, _setTemplateAPIsTTS] = useState(
JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_TTS) || "[]",
) as TemplateAPI[],
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_TTS) || "[]"
) as TemplateAPI[]
);
const [templateAPIsImageGen, _setTemplateAPIsImageGen] = useState(
JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_IMAGE_GEN) || "[]",
) as TemplateAPI[],
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_IMAGE_GEN) || "[]"
) as TemplateAPI[]
);
const [toolsTemplates, _setToolsTemplates] = useState(
JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE_TOOLS) || "[]",
) as TemplateTools[],
localStorage.getItem(STORAGE_NAME_TEMPLATE_TOOLS) || "[]"
) as TemplateTools[]
);
const setTemplates = (templates: TemplateChatStore[]) => {
localStorage.setItem(STORAGE_NAME_TEMPLATE, JSON.stringify(templates));
@@ -400,35 +400,35 @@ export default function ChatBOX(props: {
const setTemplateAPIs = (templateAPIs: TemplateAPI[]) => {
localStorage.setItem(
STORAGE_NAME_TEMPLATE_API,
JSON.stringify(templateAPIs),
JSON.stringify(templateAPIs)
);
_setTemplateAPIs(templateAPIs);
};
const setTemplateAPIsWhisper = (templateAPIWhisper: TemplateAPI[]) => {
localStorage.setItem(
STORAGE_NAME_TEMPLATE_API_WHISPER,
JSON.stringify(templateAPIWhisper),
JSON.stringify(templateAPIWhisper)
);
_setTemplateAPIsWhisper(templateAPIWhisper);
};
const setTemplateAPIsTTS = (templateAPITTS: TemplateAPI[]) => {
localStorage.setItem(
STORAGE_NAME_TEMPLATE_API_TTS,
JSON.stringify(templateAPITTS),
JSON.stringify(templateAPITTS)
);
_setTemplateAPIsTTS(templateAPITTS);
};
const setTemplateAPIsImageGen = (templateAPIImageGen: TemplateAPI[]) => {
localStorage.setItem(
STORAGE_NAME_TEMPLATE_API_IMAGE_GEN,
JSON.stringify(templateAPIImageGen),
JSON.stringify(templateAPIImageGen)
);
_setTemplateAPIsImageGen(templateAPIImageGen);
};
const setTemplateTools = (templateTools: TemplateTools[]) => {
localStorage.setItem(
STORAGE_NAME_TEMPLATE_TOOLS,
JSON.stringify(templateTools),
JSON.stringify(templateTools)
);
_setToolsTemplates(templateTools);
};
@@ -574,7 +574,7 @@ export default function ChatBOX(props: {
<br />{Tr("Click the conor to create a new chat")}
<br />
{Tr(
"All chat history and settings are stored in the local browser",
"All chat history and settings are stored in the local browser"
)}
<br />
</p>

View File

@@ -1,5 +1,6 @@
import { TemplateAPI } from "@/types/chatstore";
import { Tr } from "@/translate";
import { Button } from "./components/ui/button";
interface Props {
tmps: TemplateAPI[];
@@ -16,8 +17,10 @@ export function SetAPIsTemplate({
label,
}: Props) {
return (
<button
className="btn btn-primary btn-sm mt-3"
<Button
variant="default"
size="sm"
className="mt-3"
onClick={() => {
const name = prompt(`Give this **${label}** template a name:`);
if (!name) {
@@ -34,6 +37,6 @@ export function SetAPIsTemplate({
}}
>
{Tr(`Save ${label}`)}
</button>
</Button>
);
}

File diff suppressed because it is too large Load Diff