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 { App } from "@/pages/App";
|
||||||
import { Tr, langCodeContext, LANG_OPTIONS } from "@/translate";
|
import { Tr, langCodeContext, LANG_OPTIONS } from "@/translate";
|
||||||
import { SidebarProvider } from "@/components/ui/sidebar";
|
import { SidebarProvider } from "@/components/ui/sidebar";
|
||||||
|
import { Toaster } from "@/components/ui/toaster";
|
||||||
|
|
||||||
function Base() {
|
function Base() {
|
||||||
const [langCode, _setLangCode] = useState("en-US");
|
const [langCode, _setLangCode] = useState("en-US");
|
||||||
@@ -49,6 +50,7 @@ function Base() {
|
|||||||
<langCodeContext.Provider value={{ langCode, setLangCode }}>
|
<langCodeContext.Provider value={{ langCode, setLangCode }}>
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<App />
|
<App />
|
||||||
|
<Toaster />
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
</langCodeContext.Provider>
|
</langCodeContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,10 +12,42 @@ import { newChatStore } from "@/types/newChatstore";
|
|||||||
import { STORAGE_NAME, STORAGE_NAME_SELECTED } from "@/const";
|
import { STORAGE_NAME, STORAGE_NAME_SELECTED } from "@/const";
|
||||||
import { upgrade } from "@/indexedDB/upgrade";
|
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() {
|
export function App() {
|
||||||
// init selected index
|
// init selected index
|
||||||
const [selectedChatIndex, setSelectedChatIndex] = useState(
|
const [selectedChatIndex, setSelectedChatIndex] = useState(
|
||||||
parseInt(localStorage.getItem(STORAGE_NAME_SELECTED) ?? "1"),
|
parseInt(localStorage.getItem(STORAGE_NAME_SELECTED) ?? "1")
|
||||||
);
|
);
|
||||||
console.log("selectedChatIndex", selectedChatIndex);
|
console.log("selectedChatIndex", selectedChatIndex);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -27,6 +59,8 @@ export function App() {
|
|||||||
upgrade,
|
upgrade,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
const getChatStoreByIndex = async (index: number): Promise<ChatStore> => {
|
const getChatStoreByIndex = async (index: number): Promise<ChatStore> => {
|
||||||
const ret: ChatStore = await (await db).get(STORAGE_NAME, index);
|
const ret: ChatStore = await (await db).get(STORAGE_NAME, index);
|
||||||
if (ret === null || ret === undefined) return newChatStore({});
|
if (ret === null || ret === undefined) return newChatStore({});
|
||||||
@@ -54,7 +88,7 @@ export function App() {
|
|||||||
const max = chatStore.maxTokens - chatStore.tokenMargin;
|
const max = chatStore.maxTokens - chatStore.tokenMargin;
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
chatStore.postBeginIndex = chatStore.history.filter(
|
chatStore.postBeginIndex = chatStore.history.filter(
|
||||||
({ hide }) => !hide,
|
({ hide }) => !hide
|
||||||
).length;
|
).length;
|
||||||
for (const msg of chatStore.history
|
for (const msg of chatStore.history
|
||||||
.filter(({ hide }) => !hide)
|
.filter(({ hide }) => !hide)
|
||||||
@@ -69,7 +103,7 @@ export function App() {
|
|||||||
|
|
||||||
// manually estimate token
|
// manually estimate token
|
||||||
chatStore.totalTokens = calculate_token_length(
|
chatStore.totalTokens = calculate_token_length(
|
||||||
chatStore.systemMessageContent,
|
chatStore.systemMessageContent
|
||||||
);
|
);
|
||||||
for (const msg of chatStore.history
|
for (const msg of chatStore.history
|
||||||
.filter(({ hide }) => !hide)
|
.filter(({ hide }) => !hide)
|
||||||
@@ -82,7 +116,7 @@ export function App() {
|
|||||||
|
|
||||||
// update total tokens
|
// update total tokens
|
||||||
chatStore.totalTokens = calculate_token_length(
|
chatStore.totalTokens = calculate_token_length(
|
||||||
chatStore.systemMessageContent,
|
chatStore.systemMessageContent
|
||||||
);
|
);
|
||||||
for (const msg of chatStore.history
|
for (const msg of chatStore.history
|
||||||
.filter(({ hide }) => !hide)
|
.filter(({ hide }) => !hide)
|
||||||
@@ -101,7 +135,7 @@ export function App() {
|
|||||||
|
|
||||||
// all chat store indexes
|
// all chat store indexes
|
||||||
const [allChatStoreIndexes, setAllChatStoreIndexes] = useState<IDBValidKey>(
|
const [allChatStoreIndexes, setAllChatStoreIndexes] = useState<IDBValidKey>(
|
||||||
[],
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleNewChatStoreWithOldOne = async (chatStore: ChatStore) => {
|
const handleNewChatStoreWithOldOne = async (chatStore: ChatStore) => {
|
||||||
@@ -114,7 +148,6 @@ export function App() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDEL = async () => {
|
const handleDEL = async () => {
|
||||||
if (!confirm("Are you sure you want to delete this chat history?")) return;
|
|
||||||
console.log("remove item", `${STORAGE_NAME}-${selectedChatIndex}`);
|
console.log("remove item", `${STORAGE_NAME}-${selectedChatIndex}`);
|
||||||
(await db).delete(STORAGE_NAME, selectedChatIndex);
|
(await db).delete(STORAGE_NAME, selectedChatIndex);
|
||||||
const newAllChatStoreIndexes = await (await db).getAllKeys(STORAGE_NAME);
|
const newAllChatStoreIndexes = await (await db).getAllKeys(STORAGE_NAME);
|
||||||
@@ -129,6 +162,11 @@ export function App() {
|
|||||||
console.log("next is", next);
|
console.log("next is", next);
|
||||||
setSelectedChatIndex(next as number);
|
setSelectedChatIndex(next as number);
|
||||||
setAllChatStoreIndexes(newAllChatStoreIndexes);
|
setAllChatStoreIndexes(newAllChatStoreIndexes);
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: "Chat history deleted",
|
||||||
|
description: `Chat history ${selectedChatIndex} has been deleted.`,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCLS = async () => {
|
const handleCLS = async () => {
|
||||||
@@ -176,53 +214,79 @@ export function App() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex text-sm h-full">
|
<>
|
||||||
<div className="flex flex-col h-full p-2 bg-primary">
|
<Sidebar>
|
||||||
<div className="grow overflow-scroll">
|
<SidebarHeader>
|
||||||
<button
|
<Button onClick={handleNewChatStore}>
|
||||||
className="btn btn-sm btn-info p-1 my-1 w-full"
|
<span>{Tr("New")}</span>
|
||||||
onClick={handleNewChatStore}
|
</Button>
|
||||||
>
|
</SidebarHeader>
|
||||||
{Tr("NEW")}
|
<SidebarContent>
|
||||||
</button>
|
<SidebarGroup>
|
||||||
<ul className="pt-2">
|
<SidebarGroupLabel>Conversation</SidebarGroupLabel>
|
||||||
|
<SidebarGroupContent>
|
||||||
|
<SidebarMenu>
|
||||||
{(allChatStoreIndexes as number[])
|
{(allChatStoreIndexes as number[])
|
||||||
.slice()
|
.slice()
|
||||||
.reverse()
|
.reverse()
|
||||||
.map((i) => {
|
.map((i) => {
|
||||||
// reverse
|
// reverse
|
||||||
return (
|
return (
|
||||||
<li>
|
<SidebarMenuItem
|
||||||
<button
|
key={i}
|
||||||
className={`w-full my-1 p-1 btn btn-sm ${
|
|
||||||
i === selectedChatIndex ? "btn-accent" : "btn-secondary"
|
|
||||||
}`}
|
|
||||||
onClick={() => setSelectedChatIndex(i)}
|
onClick={() => setSelectedChatIndex(i)}
|
||||||
>
|
>
|
||||||
{i}
|
<SidebarMenuButton
|
||||||
</button>
|
asChild
|
||||||
</li>
|
isActive={i === selectedChatIndex}
|
||||||
|
>
|
||||||
|
<span>{i}</span>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</SidebarMenu>
|
||||||
</div>
|
</SidebarGroupContent>
|
||||||
<div>
|
</SidebarGroup>
|
||||||
<button
|
</SidebarContent>
|
||||||
className="btn btn-warning btn-sm p-1 my-1 w-full"
|
<SidebarFooter>
|
||||||
onClick={async () => handleDEL()}
|
<AlertDialog>
|
||||||
>
|
<AlertDialogTrigger asChild>
|
||||||
{Tr("DEL")}
|
<Button variant="destructive">{Tr("DEL")}</Button>
|
||||||
</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 && (
|
{chatStore.develop_mode && (
|
||||||
<button
|
<Button onClick={handleCLS} variant="destructive">
|
||||||
className="btn btn-sm btn-warning p-1 my-1 w-full"
|
<span>{Tr("CLS")}</span>
|
||||||
onClick={async () => handleCLS()}
|
</Button>
|
||||||
>
|
|
||||||
{Tr("CLS")}
|
|
||||||
</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>
|
||||||
</div>
|
</header>
|
||||||
<ChatBOX
|
<ChatBOX
|
||||||
db={db}
|
db={db}
|
||||||
chatStore={chatStore}
|
chatStore={chatStore}
|
||||||
@@ -230,6 +294,7 @@ export function App() {
|
|||||||
selectedChatIndex={selectedChatIndex}
|
selectedChatIndex={selectedChatIndex}
|
||||||
setSelectedChatIndex={setSelectedChatIndex}
|
setSelectedChatIndex={setSelectedChatIndex}
|
||||||
/>
|
/>
|
||||||
</div>
|
</SidebarInset>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ export default function ChatBOX(props: {
|
|||||||
|
|
||||||
// update tool call arguments
|
// update tool call arguments
|
||||||
const tool = allChunkTool.find(
|
const tool = allChunkTool.find(
|
||||||
(tool) => tool.index === tool_call.index,
|
(tool) => tool.index === tool_call.index
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!tool) {
|
if (!tool) {
|
||||||
@@ -146,7 +146,7 @@ export default function ChatBOX(props: {
|
|||||||
allChunkMessage.join("") +
|
allChunkMessage.join("") +
|
||||||
allChunkTool.map((tool) => {
|
allChunkTool.map((tool) => {
|
||||||
return `Tool Call ID: ${tool.id}\nType: ${tool.type}\nFunction: ${tool.function.name}\nArguments: ${tool.function.arguments}`;
|
return `Tool Call ID: ${tool.id}\nType: ${tool.type}\nFunction: ${tool.function.name}\nArguments: ${tool.function.arguments}`;
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
setShowGenerating(false);
|
setShowGenerating(false);
|
||||||
@@ -295,7 +295,7 @@ export default function ChatBOX(props: {
|
|||||||
setShowGenerating(true);
|
setShowGenerating(true);
|
||||||
const response = await client._fetch(
|
const response = await client._fetch(
|
||||||
chatStore.streamMode,
|
chatStore.streamMode,
|
||||||
chatStore.logprobs,
|
chatStore.logprobs
|
||||||
);
|
);
|
||||||
const contentType = response.headers.get("content-type");
|
const contentType = response.headers.get("content-type");
|
||||||
if (contentType?.startsWith("text/event-stream")) {
|
if (contentType?.startsWith("text/event-stream")) {
|
||||||
@@ -365,33 +365,33 @@ export default function ChatBOX(props: {
|
|||||||
|
|
||||||
const [templates, _setTemplates] = useState(
|
const [templates, _setTemplates] = useState(
|
||||||
JSON.parse(
|
JSON.parse(
|
||||||
localStorage.getItem(STORAGE_NAME_TEMPLATE) || "[]",
|
localStorage.getItem(STORAGE_NAME_TEMPLATE) || "[]"
|
||||||
) as TemplateChatStore[],
|
) as TemplateChatStore[]
|
||||||
);
|
);
|
||||||
const [templateAPIs, _setTemplateAPIs] = useState(
|
const [templateAPIs, _setTemplateAPIs] = useState(
|
||||||
JSON.parse(
|
JSON.parse(
|
||||||
localStorage.getItem(STORAGE_NAME_TEMPLATE_API) || "[]",
|
localStorage.getItem(STORAGE_NAME_TEMPLATE_API) || "[]"
|
||||||
) as TemplateAPI[],
|
) as TemplateAPI[]
|
||||||
);
|
);
|
||||||
const [templateAPIsWhisper, _setTemplateAPIsWhisper] = useState(
|
const [templateAPIsWhisper, _setTemplateAPIsWhisper] = useState(
|
||||||
JSON.parse(
|
JSON.parse(
|
||||||
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_WHISPER) || "[]",
|
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_WHISPER) || "[]"
|
||||||
) as TemplateAPI[],
|
) as TemplateAPI[]
|
||||||
);
|
);
|
||||||
const [templateAPIsTTS, _setTemplateAPIsTTS] = useState(
|
const [templateAPIsTTS, _setTemplateAPIsTTS] = useState(
|
||||||
JSON.parse(
|
JSON.parse(
|
||||||
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_TTS) || "[]",
|
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_TTS) || "[]"
|
||||||
) as TemplateAPI[],
|
) as TemplateAPI[]
|
||||||
);
|
);
|
||||||
const [templateAPIsImageGen, _setTemplateAPIsImageGen] = useState(
|
const [templateAPIsImageGen, _setTemplateAPIsImageGen] = useState(
|
||||||
JSON.parse(
|
JSON.parse(
|
||||||
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_IMAGE_GEN) || "[]",
|
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_IMAGE_GEN) || "[]"
|
||||||
) as TemplateAPI[],
|
) as TemplateAPI[]
|
||||||
);
|
);
|
||||||
const [toolsTemplates, _setToolsTemplates] = useState(
|
const [toolsTemplates, _setToolsTemplates] = useState(
|
||||||
JSON.parse(
|
JSON.parse(
|
||||||
localStorage.getItem(STORAGE_NAME_TEMPLATE_TOOLS) || "[]",
|
localStorage.getItem(STORAGE_NAME_TEMPLATE_TOOLS) || "[]"
|
||||||
) as TemplateTools[],
|
) as TemplateTools[]
|
||||||
);
|
);
|
||||||
const setTemplates = (templates: TemplateChatStore[]) => {
|
const setTemplates = (templates: TemplateChatStore[]) => {
|
||||||
localStorage.setItem(STORAGE_NAME_TEMPLATE, JSON.stringify(templates));
|
localStorage.setItem(STORAGE_NAME_TEMPLATE, JSON.stringify(templates));
|
||||||
@@ -400,35 +400,35 @@ export default function ChatBOX(props: {
|
|||||||
const setTemplateAPIs = (templateAPIs: TemplateAPI[]) => {
|
const setTemplateAPIs = (templateAPIs: TemplateAPI[]) => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
STORAGE_NAME_TEMPLATE_API,
|
STORAGE_NAME_TEMPLATE_API,
|
||||||
JSON.stringify(templateAPIs),
|
JSON.stringify(templateAPIs)
|
||||||
);
|
);
|
||||||
_setTemplateAPIs(templateAPIs);
|
_setTemplateAPIs(templateAPIs);
|
||||||
};
|
};
|
||||||
const setTemplateAPIsWhisper = (templateAPIWhisper: TemplateAPI[]) => {
|
const setTemplateAPIsWhisper = (templateAPIWhisper: TemplateAPI[]) => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
STORAGE_NAME_TEMPLATE_API_WHISPER,
|
STORAGE_NAME_TEMPLATE_API_WHISPER,
|
||||||
JSON.stringify(templateAPIWhisper),
|
JSON.stringify(templateAPIWhisper)
|
||||||
);
|
);
|
||||||
_setTemplateAPIsWhisper(templateAPIWhisper);
|
_setTemplateAPIsWhisper(templateAPIWhisper);
|
||||||
};
|
};
|
||||||
const setTemplateAPIsTTS = (templateAPITTS: TemplateAPI[]) => {
|
const setTemplateAPIsTTS = (templateAPITTS: TemplateAPI[]) => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
STORAGE_NAME_TEMPLATE_API_TTS,
|
STORAGE_NAME_TEMPLATE_API_TTS,
|
||||||
JSON.stringify(templateAPITTS),
|
JSON.stringify(templateAPITTS)
|
||||||
);
|
);
|
||||||
_setTemplateAPIsTTS(templateAPITTS);
|
_setTemplateAPIsTTS(templateAPITTS);
|
||||||
};
|
};
|
||||||
const setTemplateAPIsImageGen = (templateAPIImageGen: TemplateAPI[]) => {
|
const setTemplateAPIsImageGen = (templateAPIImageGen: TemplateAPI[]) => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
STORAGE_NAME_TEMPLATE_API_IMAGE_GEN,
|
STORAGE_NAME_TEMPLATE_API_IMAGE_GEN,
|
||||||
JSON.stringify(templateAPIImageGen),
|
JSON.stringify(templateAPIImageGen)
|
||||||
);
|
);
|
||||||
_setTemplateAPIsImageGen(templateAPIImageGen);
|
_setTemplateAPIsImageGen(templateAPIImageGen);
|
||||||
};
|
};
|
||||||
const setTemplateTools = (templateTools: TemplateTools[]) => {
|
const setTemplateTools = (templateTools: TemplateTools[]) => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
STORAGE_NAME_TEMPLATE_TOOLS,
|
STORAGE_NAME_TEMPLATE_TOOLS,
|
||||||
JSON.stringify(templateTools),
|
JSON.stringify(templateTools)
|
||||||
);
|
);
|
||||||
_setToolsTemplates(templateTools);
|
_setToolsTemplates(templateTools);
|
||||||
};
|
};
|
||||||
@@ -574,7 +574,7 @@ export default function ChatBOX(props: {
|
|||||||
<br />↖{Tr("Click the conor to create a new chat")}
|
<br />↖{Tr("Click the conor to create a new chat")}
|
||||||
<br />⚠
|
<br />⚠
|
||||||
{Tr(
|
{Tr(
|
||||||
"All chat history and settings are stored in the local browser",
|
"All chat history and settings are stored in the local browser"
|
||||||
)}
|
)}
|
||||||
<br />
|
<br />
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { TemplateAPI } from "@/types/chatstore";
|
import { TemplateAPI } from "@/types/chatstore";
|
||||||
import { Tr } from "@/translate";
|
import { Tr } from "@/translate";
|
||||||
|
import { Button } from "./components/ui/button";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
tmps: TemplateAPI[];
|
tmps: TemplateAPI[];
|
||||||
@@ -16,8 +17,10 @@ export function SetAPIsTemplate({
|
|||||||
label,
|
label,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
return (
|
return (
|
||||||
<button
|
<Button
|
||||||
className="btn btn-primary btn-sm mt-3"
|
variant="default"
|
||||||
|
size="sm"
|
||||||
|
className="mt-3"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const name = prompt(`Give this **${label}** template a name:`);
|
const name = prompt(`Give this **${label}** template a name:`);
|
||||||
if (!name) {
|
if (!name) {
|
||||||
@@ -34,6 +37,6 @@ export function SetAPIsTemplate({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Tr(`Save ${label}`)}
|
{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