Refactor Settings component and integrate shadcn UI elements for improved user experience
This commit is contained in:
13
src/components/Settings.tsx
Normal file
13
src/components/Settings.tsx
Normal 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";
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
979
src/settings.tsx
979
src/settings.tsx
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user