ChatGPT-Next-Web/app/components/settings.tsx
2024-08-03 21:30:32 +08:00

1546 lines
47 KiB
TypeScript

import { useState, useEffect, useMemo } from "react";
import styles from "./settings.module.scss";
import ResetIcon from "../icons/reload.svg";
import AddIcon from "../icons/add.svg";
import CloseIcon from "../icons/close.svg";
import CopyIcon from "../icons/copy.svg";
import ClearIcon from "../icons/clear.svg";
import LoadingIcon from "../icons/three-dots.svg";
import EditIcon from "../icons/edit.svg";
import EyeIcon from "../icons/eye.svg";
import DownloadIcon from "../icons/download.svg";
import UploadIcon from "../icons/upload.svg";
import ConfigIcon from "../icons/config.svg";
import ConfirmIcon from "../icons/confirm.svg";
import ConnectionIcon from "../icons/connection.svg";
import CloudSuccessIcon from "../icons/cloud-success.svg";
import CloudFailIcon from "../icons/cloud-fail.svg";
import {
Input,
List,
ListItem,
Modal,
PasswordInput,
Popover,
Select,
showConfirm,
showToast,
} from "./ui-lib";
import { ModelConfigList } from "./model-config";
import { IconButton } from "./button";
import {
SubmitKey,
useChatStore,
Theme,
useUpdateStore,
useAccessStore,
useAppConfig,
} from "../store";
import Locale, {
AllLangs,
ALL_LANG_OPTIONS,
changeLang,
getLang,
} from "../locales";
import { copyToClipboard } from "../utils";
import Link from "next/link";
import {
Anthropic,
Azure,
Baidu,
Tencent,
ByteDance,
Alibaba,
Moonshot,
Google,
GoogleSafetySettingsThreshold,
OPENAI_BASE_URL,
Path,
RELEASE_URL,
STORAGE_KEY,
ServiceProvider,
SlotID,
UPDATE_URL,
Stability,
} from "../constant";
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
import { ErrorBoundary } from "./error";
import { InputRange } from "./input-range";
import { useNavigate } from "react-router-dom";
import { Avatar, AvatarPicker } from "./emoji";
import { getClientConfig } from "../config/client";
import { useSyncStore } from "../store/sync";
import { nanoid } from "nanoid";
import { useMaskStore } from "../store/mask";
import { ProviderType } from "../utils/cloud";
function EditPromptModal(props: { id: string; onClose: () => void }) {
const promptStore = usePromptStore();
const prompt = promptStore.get(props.id);
return prompt ? (
<div className="modal-mask">
<Modal
title={Locale.Settings.Prompt.EditModal.Title}
onClose={props.onClose}
actions={[
<IconButton
key=""
onClick={props.onClose}
text={Locale.UI.Confirm}
bordered
/>,
]}
>
<div className={styles["edit-prompt-modal"]}>
<input
type="text"
value={prompt.title}
readOnly={!prompt.isUser}
className={styles["edit-prompt-title"]}
onInput={(e) =>
promptStore.updatePrompt(
props.id,
(prompt) => (prompt.title = e.currentTarget.value),
)
}
></input>
<Input
value={prompt.content}
readOnly={!prompt.isUser}
className={styles["edit-prompt-content"]}
rows={10}
onInput={(e) =>
promptStore.updatePrompt(
props.id,
(prompt) => (prompt.content = e.currentTarget.value),
)
}
></Input>
</div>
</Modal>
</div>
) : null;
}
function UserPromptModal(props: { onClose?: () => void }) {
const promptStore = usePromptStore();
const userPrompts = promptStore.getUserPrompts();
const builtinPrompts = SearchService.builtinPrompts;
const allPrompts = userPrompts.concat(builtinPrompts);
const [searchInput, setSearchInput] = useState("");
const [searchPrompts, setSearchPrompts] = useState<Prompt[]>([]);
const prompts = searchInput.length > 0 ? searchPrompts : allPrompts;
const [editingPromptId, setEditingPromptId] = useState<string>();
useEffect(() => {
if (searchInput.length > 0) {
const searchResult = SearchService.search(searchInput);
setSearchPrompts(searchResult);
} else {
setSearchPrompts([]);
}
}, [searchInput]);
return (
<div className="modal-mask">
<Modal
title={Locale.Settings.Prompt.Modal.Title}
onClose={() => props.onClose?.()}
actions={[
<IconButton
key="add"
onClick={() => {
const promptId = promptStore.add({
id: nanoid(),
createdAt: Date.now(),
title: "Empty Prompt",
content: "Empty Prompt Content",
});
setEditingPromptId(promptId);
}}
icon={<AddIcon />}
bordered
text={Locale.Settings.Prompt.Modal.Add}
/>,
]}
>
<div className={styles["user-prompt-modal"]}>
<input
type="text"
className={styles["user-prompt-search"]}
placeholder={Locale.Settings.Prompt.Modal.Search}
value={searchInput}
onInput={(e) => setSearchInput(e.currentTarget.value)}
></input>
<div className={styles["user-prompt-list"]}>
{prompts.map((v, _) => (
<div className={styles["user-prompt-item"]} key={v.id ?? v.title}>
<div className={styles["user-prompt-header"]}>
<div className={styles["user-prompt-title"]}>{v.title}</div>
<div className={styles["user-prompt-content"] + " one-line"}>
{v.content}
</div>
</div>
<div className={styles["user-prompt-buttons"]}>
{v.isUser && (
<IconButton
icon={<ClearIcon />}
className={styles["user-prompt-button"]}
onClick={() => promptStore.remove(v.id!)}
/>
)}
{v.isUser ? (
<IconButton
icon={<EditIcon />}
className={styles["user-prompt-button"]}
onClick={() => setEditingPromptId(v.id)}
/>
) : (
<IconButton
icon={<EyeIcon />}
className={styles["user-prompt-button"]}
onClick={() => setEditingPromptId(v.id)}
/>
)}
<IconButton
icon={<CopyIcon />}
className={styles["user-prompt-button"]}
onClick={() => copyToClipboard(v.content)}
/>
</div>
</div>
))}
</div>
</div>
</Modal>
{editingPromptId !== undefined && (
<EditPromptModal
id={editingPromptId!}
onClose={() => setEditingPromptId(undefined)}
/>
)}
</div>
);
}
function DangerItems() {
const chatStore = useChatStore();
const appConfig = useAppConfig();
return (
<List>
<ListItem
title={Locale.Settings.Danger.Reset.Title}
subTitle={Locale.Settings.Danger.Reset.SubTitle}
>
<IconButton
text={Locale.Settings.Danger.Reset.Action}
onClick={async () => {
if (await showConfirm(Locale.Settings.Danger.Reset.Confirm)) {
appConfig.reset();
}
}}
type="danger"
/>
</ListItem>
<ListItem
title={Locale.Settings.Danger.Clear.Title}
subTitle={Locale.Settings.Danger.Clear.SubTitle}
>
<IconButton
text={Locale.Settings.Danger.Clear.Action}
onClick={async () => {
if (await showConfirm(Locale.Settings.Danger.Clear.Confirm)) {
chatStore.clearAllData();
}
}}
type="danger"
/>
</ListItem>
</List>
);
}
function CheckButton() {
const syncStore = useSyncStore();
const couldCheck = useMemo(() => {
return syncStore.cloudSync();
}, [syncStore]);
const [checkState, setCheckState] = useState<
"none" | "checking" | "success" | "failed"
>("none");
async function check() {
setCheckState("checking");
const valid = await syncStore.check();
setCheckState(valid ? "success" : "failed");
}
if (!couldCheck) return null;
return (
<IconButton
text={Locale.Settings.Sync.Config.Modal.Check}
bordered
onClick={check}
icon={
checkState === "none" ? (
<ConnectionIcon />
) : checkState === "checking" ? (
<LoadingIcon />
) : checkState === "success" ? (
<CloudSuccessIcon />
) : checkState === "failed" ? (
<CloudFailIcon />
) : (
<ConnectionIcon />
)
}
></IconButton>
);
}
function SyncConfigModal(props: { onClose?: () => void }) {
const syncStore = useSyncStore();
return (
<div className="modal-mask">
<Modal
title={Locale.Settings.Sync.Config.Modal.Title}
onClose={() => props.onClose?.()}
actions={[
<CheckButton key="check" />,
<IconButton
key="confirm"
onClick={props.onClose}
icon={<ConfirmIcon />}
bordered
text={Locale.UI.Confirm}
/>,
]}
>
<List>
<ListItem
title={Locale.Settings.Sync.Config.SyncType.Title}
subTitle={Locale.Settings.Sync.Config.SyncType.SubTitle}
>
<select
value={syncStore.provider}
onChange={(e) => {
syncStore.update(
(config) =>
(config.provider = e.target.value as ProviderType),
);
}}
>
{Object.entries(ProviderType).map(([k, v]) => (
<option value={v} key={k}>
{k}
</option>
))}
</select>
</ListItem>
<ListItem
title={Locale.Settings.Sync.Config.Proxy.Title}
subTitle={Locale.Settings.Sync.Config.Proxy.SubTitle}
>
<input
type="checkbox"
checked={syncStore.useProxy}
onChange={(e) => {
syncStore.update(
(config) => (config.useProxy = e.currentTarget.checked),
);
}}
></input>
</ListItem>
{syncStore.useProxy ? (
<ListItem
title={Locale.Settings.Sync.Config.ProxyUrl.Title}
subTitle={Locale.Settings.Sync.Config.ProxyUrl.SubTitle}
>
<input
type="text"
value={syncStore.proxyUrl}
onChange={(e) => {
syncStore.update(
(config) => (config.proxyUrl = e.currentTarget.value),
);
}}
></input>
</ListItem>
) : null}
</List>
{syncStore.provider === ProviderType.WebDAV && (
<>
<List>
<ListItem title={Locale.Settings.Sync.Config.WebDav.Endpoint}>
<input
type="text"
value={syncStore.webdav.endpoint}
onChange={(e) => {
syncStore.update(
(config) =>
(config.webdav.endpoint = e.currentTarget.value),
);
}}
></input>
</ListItem>
<ListItem title={Locale.Settings.Sync.Config.WebDav.UserName}>
<input
type="text"
value={syncStore.webdav.username}
onChange={(e) => {
syncStore.update(
(config) =>
(config.webdav.username = e.currentTarget.value),
);
}}
></input>
</ListItem>
<ListItem title={Locale.Settings.Sync.Config.WebDav.Password}>
<PasswordInput
value={syncStore.webdav.password}
onChange={(e) => {
syncStore.update(
(config) =>
(config.webdav.password = e.currentTarget.value),
);
}}
></PasswordInput>
</ListItem>
</List>
</>
)}
{syncStore.provider === ProviderType.UpStash && (
<List>
<ListItem title={Locale.Settings.Sync.Config.UpStash.Endpoint}>
<input
type="text"
value={syncStore.upstash.endpoint}
onChange={(e) => {
syncStore.update(
(config) =>
(config.upstash.endpoint = e.currentTarget.value),
);
}}
></input>
</ListItem>
<ListItem title={Locale.Settings.Sync.Config.UpStash.UserName}>
<input
type="text"
value={syncStore.upstash.username}
placeholder={STORAGE_KEY}
onChange={(e) => {
syncStore.update(
(config) =>
(config.upstash.username = e.currentTarget.value),
);
}}
></input>
</ListItem>
<ListItem title={Locale.Settings.Sync.Config.UpStash.Password}>
<PasswordInput
value={syncStore.upstash.apiKey}
onChange={(e) => {
syncStore.update(
(config) => (config.upstash.apiKey = e.currentTarget.value),
);
}}
></PasswordInput>
</ListItem>
</List>
)}
</Modal>
</div>
);
}
function SyncItems() {
const syncStore = useSyncStore();
const chatStore = useChatStore();
const promptStore = usePromptStore();
const maskStore = useMaskStore();
const couldSync = useMemo(() => {
return syncStore.cloudSync();
}, [syncStore]);
const [showSyncConfigModal, setShowSyncConfigModal] = useState(false);
const stateOverview = useMemo(() => {
const sessions = chatStore.sessions;
const messageCount = sessions.reduce((p, c) => p + c.messages.length, 0);
return {
chat: sessions.length,
message: messageCount,
prompt: Object.keys(promptStore.prompts).length,
mask: Object.keys(maskStore.masks).length,
};
}, [chatStore.sessions, maskStore.masks, promptStore.prompts]);
return (
<>
<List>
{/*<ListItem*/}
{/* title={Locale.Settings.Sync.CloudState}*/}
{/* subTitle={*/}
{/* syncStore.lastProvider*/}
{/* ? `${new Date(syncStore.lastSyncTime).toLocaleString()} [${*/}
{/* syncStore.lastProvider*/}
{/* }]`*/}
{/* : Locale.Settings.Sync.NotSyncYet*/}
{/* }*/}
{/*>*/}
{/* <div style={{ display: "flex" }}>*/}
{/* <IconButton*/}
{/* icon={<ConfigIcon />}*/}
{/* text={Locale.UI.Config}*/}
{/* onClick={() => {*/}
{/* setShowSyncConfigModal(true);*/}
{/* }}*/}
{/* />*/}
{/* {couldSync && (*/}
{/* <IconButton*/}
{/* icon={<ResetIcon />}*/}
{/* text={Locale.UI.Sync}*/}
{/* onClick={async () => {*/}
{/* try {*/}
{/* await syncStore.sync();*/}
{/* showToast(Locale.Settings.Sync.Success);*/}
{/* } catch (e) {*/}
{/* showToast(Locale.Settings.Sync.Fail);*/}
{/* console.error("[Sync]", e);*/}
{/* }*/}
{/* }}*/}
{/* />*/}
{/* )}*/}
{/* </div>*/}
{/*</ListItem>*/}
<ListItem
title={Locale.Settings.Sync.LocalState}
subTitle={Locale.Settings.Sync.Overview(stateOverview)}
>
<div style={{ display: "flex" }}>
<IconButton
icon={<UploadIcon />}
text={Locale.UI.Export}
onClick={() => {
syncStore.export();
}}
/>
<IconButton
icon={<DownloadIcon />}
text={Locale.UI.Import}
onClick={() => {
syncStore.import();
}}
/>
</div>
</ListItem>
</List>
{showSyncConfigModal && (
<SyncConfigModal onClose={() => setShowSyncConfigModal(false)} />
)}
</>
);
}
export function Settings() {
const navigate = useNavigate();
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const config = useAppConfig();
const updateConfig = config.update;
const updateStore = useUpdateStore();
const [checkingUpdate, setCheckingUpdate] = useState(false);
const currentVersion = updateStore.formatVersion(updateStore.version);
const remoteId = updateStore.formatVersion(updateStore.remoteVersion);
const hasNewVersion = currentVersion !== remoteId;
const updateUrl = getClientConfig()?.isApp ? RELEASE_URL : UPDATE_URL;
function checkUpdate(force = false) {
setCheckingUpdate(true);
updateStore.getLatestVersion(force).then(() => {
setCheckingUpdate(false);
});
console.log("[Update] local version ", updateStore.version);
console.log("[Update] remote version ", updateStore.remoteVersion);
}
const accessStore = useAccessStore();
const shouldHideBalanceQuery = useMemo(() => {
const isOpenAiUrl = accessStore.openaiUrl.includes(OPENAI_BASE_URL);
return (
accessStore.hideBalanceQuery ||
isOpenAiUrl ||
accessStore.provider === ServiceProvider.Azure
);
}, [
accessStore.hideBalanceQuery,
accessStore.openaiUrl,
accessStore.provider,
]);
const usage = {
used: updateStore.used,
subscription: updateStore.subscription,
};
const [loadingUsage, setLoadingUsage] = useState(false);
function checkUsage(force = false) {
if (shouldHideBalanceQuery) {
return;
}
setLoadingUsage(true);
updateStore.updateUsage(force).finally(() => {
setLoadingUsage(false);
});
}
const enabledAccessControl = useMemo(
() => accessStore.enabledAccessControl(),
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
const promptStore = usePromptStore();
const builtinCount = SearchService.count.builtin;
const customCount = promptStore.getUserPrompts().length ?? 0;
const [shouldShowPromptModal, setShowPromptModal] = useState(false);
const showUsage = accessStore.isAuthorized();
useEffect(() => {
// checks per minutes
// checkUpdate();
showUsage && checkUsage();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
const keydownEvent = (e: KeyboardEvent) => {
if (e.key === "Escape") {
navigate(Path.Home);
}
};
if (clientConfig?.isApp) {
// Force to set custom endpoint to true if it's app
accessStore.update((state) => {
state.useCustomConfig = true;
});
}
document.addEventListener("keydown", keydownEvent);
return () => {
document.removeEventListener("keydown", keydownEvent);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const clientConfig = useMemo(() => getClientConfig(), []);
const showAccessCode = enabledAccessControl && !clientConfig?.isApp;
const accessCodeComponent = showAccessCode && (
<ListItem
title={Locale.Settings.Access.AccessCode.Title}
subTitle={Locale.Settings.Access.AccessCode.SubTitle}
>
<PasswordInput
value={accessStore.accessCode}
type="text"
placeholder={Locale.Settings.Access.AccessCode.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.accessCode = e.currentTarget.value),
);
}}
/>
</ListItem>
);
const useCustomConfigComponent = // Conditionally render the following ListItem based on clientConfig.isApp
!clientConfig?.isApp && ( // only show if isApp is false
<ListItem
title={Locale.Settings.Access.CustomEndpoint.Title}
subTitle={Locale.Settings.Access.CustomEndpoint.SubTitle}
>
<input
type="checkbox"
checked={accessStore.useCustomConfig}
onChange={(e) =>
accessStore.update(
(access) => (access.useCustomConfig = e.currentTarget.checked),
)
}
></input>
</ListItem>
);
const openAIConfigComponent = accessStore.provider ===
ServiceProvider.OpenAI && (
<>
<ListItem
title={Locale.Settings.Access.OpenAI.Endpoint.Title}
subTitle={Locale.Settings.Access.OpenAI.Endpoint.SubTitle}
>
<input
type="text"
value={accessStore.openaiUrl}
placeholder={OPENAI_BASE_URL}
onChange={(e) =>
accessStore.update(
(access) => (access.openaiUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.OpenAI.ApiKey.Title}
subTitle={Locale.Settings.Access.OpenAI.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.openaiApiKey}
type="text"
placeholder={Locale.Settings.Access.OpenAI.ApiKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.openaiApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
</>
);
const azureConfigComponent = accessStore.provider ===
ServiceProvider.Azure && (
<>
<ListItem
title={Locale.Settings.Access.Azure.Endpoint.Title}
subTitle={
Locale.Settings.Access.Azure.Endpoint.SubTitle + Azure.ExampleEndpoint
}
>
<input
type="text"
value={accessStore.azureUrl}
placeholder={Azure.ExampleEndpoint}
onChange={(e) =>
accessStore.update(
(access) => (access.azureUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.Azure.ApiKey.Title}
subTitle={Locale.Settings.Access.Azure.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.azureApiKey}
type="text"
placeholder={Locale.Settings.Access.Azure.ApiKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.azureApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
<ListItem
title={Locale.Settings.Access.Azure.ApiVerion.Title}
subTitle={Locale.Settings.Access.Azure.ApiVerion.SubTitle}
>
<input
type="text"
value={accessStore.azureApiVersion}
placeholder="2023-08-01-preview"
onChange={(e) =>
accessStore.update(
(access) => (access.azureApiVersion = e.currentTarget.value),
)
}
></input>
</ListItem>
</>
);
const googleConfigComponent = accessStore.provider ===
ServiceProvider.Google && (
<>
<ListItem
title={Locale.Settings.Access.Google.Endpoint.Title}
subTitle={
Locale.Settings.Access.Google.Endpoint.SubTitle +
Google.ExampleEndpoint
}
>
<input
type="text"
value={accessStore.googleUrl}
placeholder={Google.ExampleEndpoint}
onChange={(e) =>
accessStore.update(
(access) => (access.googleUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.Google.ApiKey.Title}
subTitle={Locale.Settings.Access.Google.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.googleApiKey}
type="text"
placeholder={Locale.Settings.Access.Google.ApiKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.googleApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
<ListItem
title={Locale.Settings.Access.Google.ApiVersion.Title}
subTitle={Locale.Settings.Access.Google.ApiVersion.SubTitle}
>
<input
type="text"
value={accessStore.googleApiVersion}
placeholder="2023-08-01-preview"
onChange={(e) =>
accessStore.update(
(access) => (access.googleApiVersion = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.Google.GoogleSafetySettings.Title}
subTitle={Locale.Settings.Access.Google.GoogleSafetySettings.SubTitle}
>
<Select
value={accessStore.googleSafetySettings}
onChange={(e) => {
accessStore.update(
(access) =>
(access.googleSafetySettings = e.target
.value as GoogleSafetySettingsThreshold),
);
}}
>
{Object.entries(GoogleSafetySettingsThreshold).map(([k, v]) => (
<option value={v} key={k}>
{k}
</option>
))}
</Select>
</ListItem>
</>
);
const anthropicConfigComponent = accessStore.provider ===
ServiceProvider.Anthropic && (
<>
<ListItem
title={Locale.Settings.Access.Anthropic.Endpoint.Title}
subTitle={
Locale.Settings.Access.Anthropic.Endpoint.SubTitle +
Anthropic.ExampleEndpoint
}
>
<input
type="text"
value={accessStore.anthropicUrl}
placeholder={Anthropic.ExampleEndpoint}
onChange={(e) =>
accessStore.update(
(access) => (access.anthropicUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.Anthropic.ApiKey.Title}
subTitle={Locale.Settings.Access.Anthropic.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.anthropicApiKey}
type="text"
placeholder={Locale.Settings.Access.Anthropic.ApiKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.anthropicApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
<ListItem
title={Locale.Settings.Access.Anthropic.ApiVerion.Title}
subTitle={Locale.Settings.Access.Anthropic.ApiVerion.SubTitle}
>
<input
type="text"
value={accessStore.anthropicApiVersion}
placeholder={Anthropic.Vision}
onChange={(e) =>
accessStore.update(
(access) => (access.anthropicApiVersion = e.currentTarget.value),
)
}
></input>
</ListItem>
</>
);
const baiduConfigComponent = accessStore.provider ===
ServiceProvider.Baidu && (
<>
<ListItem
title={Locale.Settings.Access.Baidu.Endpoint.Title}
subTitle={Locale.Settings.Access.Baidu.Endpoint.SubTitle}
>
<input
type="text"
value={accessStore.baiduUrl}
placeholder={Baidu.ExampleEndpoint}
onChange={(e) =>
accessStore.update(
(access) => (access.baiduUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.Baidu.ApiKey.Title}
subTitle={Locale.Settings.Access.Baidu.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.baiduApiKey}
type="text"
placeholder={Locale.Settings.Access.Baidu.ApiKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.baiduApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
<ListItem
title={Locale.Settings.Access.Baidu.SecretKey.Title}
subTitle={Locale.Settings.Access.Baidu.SecretKey.SubTitle}
>
<PasswordInput
value={accessStore.baiduSecretKey}
type="text"
placeholder={Locale.Settings.Access.Baidu.SecretKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.baiduSecretKey = e.currentTarget.value),
);
}}
/>
</ListItem>
</>
);
const tencentConfigComponent = accessStore.provider ===
ServiceProvider.Tencent && (
<>
<ListItem
title={Locale.Settings.Access.Tencent.Endpoint.Title}
subTitle={Locale.Settings.Access.Tencent.Endpoint.SubTitle}
>
<input
type="text"
value={accessStore.tencentUrl}
placeholder={Tencent.ExampleEndpoint}
onChange={(e) =>
accessStore.update(
(access) => (access.tencentUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.Tencent.ApiKey.Title}
subTitle={Locale.Settings.Access.Tencent.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.tencentSecretId}
type="text"
placeholder={Locale.Settings.Access.Tencent.ApiKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.tencentSecretId = e.currentTarget.value),
);
}}
/>
</ListItem>
<ListItem
title={Locale.Settings.Access.Tencent.SecretKey.Title}
subTitle={Locale.Settings.Access.Tencent.SecretKey.SubTitle}
>
<PasswordInput
value={accessStore.tencentSecretKey}
type="text"
placeholder={Locale.Settings.Access.Tencent.SecretKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.tencentSecretKey = e.currentTarget.value),
);
}}
/>
</ListItem>
</>
);
const byteDanceConfigComponent = accessStore.provider ===
ServiceProvider.ByteDance && (
<>
<ListItem
title={Locale.Settings.Access.ByteDance.Endpoint.Title}
subTitle={
Locale.Settings.Access.ByteDance.Endpoint.SubTitle +
ByteDance.ExampleEndpoint
}
>
<input
type="text"
value={accessStore.bytedanceUrl}
placeholder={ByteDance.ExampleEndpoint}
onChange={(e) =>
accessStore.update(
(access) => (access.bytedanceUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.ByteDance.ApiKey.Title}
subTitle={Locale.Settings.Access.ByteDance.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.bytedanceApiKey}
type="text"
placeholder={Locale.Settings.Access.ByteDance.ApiKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.bytedanceApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
</>
);
const alibabaConfigComponent = accessStore.provider ===
ServiceProvider.Alibaba && (
<>
<ListItem
title={Locale.Settings.Access.Alibaba.Endpoint.Title}
subTitle={
Locale.Settings.Access.Alibaba.Endpoint.SubTitle +
Alibaba.ExampleEndpoint
}
>
<input
type="text"
value={accessStore.alibabaUrl}
placeholder={Alibaba.ExampleEndpoint}
onChange={(e) =>
accessStore.update(
(access) => (access.alibabaUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.Alibaba.ApiKey.Title}
subTitle={Locale.Settings.Access.Alibaba.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.alibabaApiKey}
type="text"
placeholder={Locale.Settings.Access.Alibaba.ApiKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.alibabaApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
</>
);
const moonshotConfigComponent = accessStore.provider ===
ServiceProvider.Moonshot && (
<>
<ListItem
title={Locale.Settings.Access.Moonshot.Endpoint.Title}
subTitle={
Locale.Settings.Access.Moonshot.Endpoint.SubTitle +
Moonshot.ExampleEndpoint
}
>
<input
type="text"
value={accessStore.moonshotUrl}
placeholder={Moonshot.ExampleEndpoint}
onChange={(e) =>
accessStore.update(
(access) => (access.moonshotUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.Moonshot.ApiKey.Title}
subTitle={Locale.Settings.Access.Moonshot.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.moonshotApiKey}
type="text"
placeholder={Locale.Settings.Access.Moonshot.ApiKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.moonshotApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
</>
);
const stabilityConfigComponent = accessStore.provider ===
ServiceProvider.Stability && (
<>
<ListItem
title={Locale.Settings.Access.Stability.Endpoint.Title}
subTitle={
Locale.Settings.Access.Stability.Endpoint.SubTitle +
Stability.ExampleEndpoint
}
>
<input
type="text"
value={accessStore.stabilityUrl}
placeholder={Stability.ExampleEndpoint}
onChange={(e) =>
accessStore.update(
(access) => (access.stabilityUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.Stability.ApiKey.Title}
subTitle={Locale.Settings.Access.Stability.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.stabilityApiKey}
type="text"
placeholder={Locale.Settings.Access.Stability.ApiKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.stabilityApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
</>
);
return (
<ErrorBoundary>
<div className="window-header" data-tauri-drag-region>
<div className="window-header-title">
<div className="window-header-main-title">
{Locale.Settings.Title}
</div>
<div className="window-header-sub-title">
{Locale.Settings.SubTitle}
</div>
</div>
<div className="window-actions">
<div className="window-action-button"></div>
<div className="window-action-button"></div>
<div className="window-action-button">
<IconButton
icon={<CloseIcon />}
onClick={() => navigate(Path.Home)}
bordered
/>
</div>
</div>
</div>
<div className={styles["settings"]} id="user-settings">
<List>
<ListItem title={Locale.Settings.Avatar}>
<Popover
onClose={() => setShowEmojiPicker(false)}
content={
<AvatarPicker
onEmojiClick={(avatar: string) => {
updateConfig((config) => (config.avatar = avatar));
setShowEmojiPicker(false);
}}
/>
}
open={showEmojiPicker}
>
<div
className={styles.avatar}
onClick={() => {
setShowEmojiPicker(!showEmojiPicker);
}}
>
<Avatar avatar={config.avatar} />
</div>
</Popover>
</ListItem>
{/*<ListItem*/}
{/* title={Locale.Settings.Update.Version(currentVersion ?? "unknown")}*/}
{/* subTitle={*/}
{/* checkingUpdate*/}
{/* ? Locale.Settings.Update.IsChecking*/}
{/* : hasNewVersion*/}
{/* ? Locale.Settings.Update.FoundUpdate(remoteId ?? "ERROR")*/}
{/* : Locale.Settings.Update.IsLatest*/}
{/* }*/}
{/*>*/}
{/* {checkingUpdate ? (*/}
{/* <LoadingIcon />*/}
{/* ) : hasNewVersion ? (*/}
{/* <Link href={updateUrl} target="_blank" className="link">*/}
{/* {Locale.Settings.Update.GoToUpdate}*/}
{/* </Link>*/}
{/* ) : (*/}
{/* <IconButton*/}
{/* icon={<ResetIcon></ResetIcon>}*/}
{/* text={Locale.Settings.Update.CheckUpdate}*/}
{/* onClick={() => checkUpdate(true)}*/}
{/* />*/}
{/* )}*/}
{/*</ListItem>*/}
{/*<ListItem title={Locale.Settings.SendKey}>*/}
{/* <Select*/}
{/* value={config.submitKey}*/}
{/* onChange={(e) => {*/}
{/* updateConfig(*/}
{/* (config) =>*/}
{/* (config.submitKey = e.target.value as any as SubmitKey),*/}
{/* );*/}
{/* }}*/}
{/* >*/}
{/* {Object.values(SubmitKey).map((v) => (*/}
{/* <option value={v} key={v}>*/}
{/* {v}*/}
{/* </option>*/}
{/* ))}*/}
{/* </Select>*/}
{/*</ListItem>*/}
<ListItem title={Locale.Settings.Theme}>
<Select
value={config.theme}
onChange={(e) => {
updateConfig(
(config) => (config.theme = e.target.value as any as Theme),
);
}}
>
{Object.values(Theme).map((v) => (
<option value={v} key={v}>
{v}
</option>
))}
</Select>
</ListItem>
{/*<ListItem title={Locale.Settings.Lang.Name}>*/}
{/* <Select*/}
{/* value={getLang()}*/}
{/* onChange={(e) => {*/}
{/* changeLang(e.target.value as any);*/}
{/* }}*/}
{/* >*/}
{/* {AllLangs.map((lang) => (*/}
{/* <option value={lang} key={lang}>*/}
{/* {ALL_LANG_OPTIONS[lang]}*/}
{/* </option>*/}
{/* ))}*/}
{/* </Select>*/}
{/*</ListItem>*/}
<ListItem
title={Locale.Settings.FontSize.Title}
subTitle={Locale.Settings.FontSize.SubTitle}
>
<InputRange
title={`${config.fontSize ?? 14}px`}
value={config.fontSize}
min="12"
max="40"
step="1"
onChange={(e) =>
updateConfig(
(config) =>
(config.fontSize = Number.parseInt(e.currentTarget.value)),
)
}
></InputRange>
</ListItem>
<ListItem
title={Locale.Settings.FontFamily.Title}
subTitle={Locale.Settings.FontFamily.SubTitle}
>
<input
type="text"
value={config.fontFamily}
placeholder={Locale.Settings.FontFamily.Placeholder}
onChange={(e) =>
updateConfig(
(config) => (config.fontFamily = e.currentTarget.value),
)
}
></input>
</ListItem>
{/*<ListItem*/}
{/* title={Locale.Settings.AutoGenerateTitle.Title}*/}
{/* subTitle={Locale.Settings.AutoGenerateTitle.SubTitle}*/}
{/*>*/}
{/* <input*/}
{/* type="checkbox"*/}
{/* checked={config.enableAutoGenerateTitle}*/}
{/* onChange={(e) =>*/}
{/* updateConfig(*/}
{/* (config) =>*/}
{/* (config.enableAutoGenerateTitle = e.currentTarget.checked),*/}
{/* )*/}
{/* }*/}
{/* ></input>*/}
{/*</ListItem>*/}
{/*<ListItem*/}
{/* title={Locale.Settings.SendPreviewBubble.Title}*/}
{/* subTitle={Locale.Settings.SendPreviewBubble.SubTitle}*/}
{/*>*/}
{/* <input*/}
{/* type="checkbox"*/}
{/* checked={config.sendPreviewBubble}*/}
{/* onChange={(e) =>*/}
{/* updateConfig(*/}
{/* (config) =>*/}
{/* (config.sendPreviewBubble = e.currentTarget.checked),*/}
{/* )*/}
{/* }*/}
{/* ></input>*/}
{/*</ListItem>*/}
</List>
<SyncItems />
{/*<List>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Mask.Splash.Title}*/}
{/* subTitle={Locale.Settings.Mask.Splash.SubTitle}*/}
{/* >*/}
{/* <input*/}
{/* type="checkbox"*/}
{/* checked={!config.dontShowMaskSplashScreen}*/}
{/* onChange={(e) =>*/}
{/* updateConfig(*/}
{/* (config) =>*/}
{/* (config.dontShowMaskSplashScreen =*/}
{/* !e.currentTarget.checked),*/}
{/* )*/}
{/* }*/}
{/* ></input>*/}
{/* </ListItem>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Mask.Builtin.Title}*/}
{/* subTitle={Locale.Settings.Mask.Builtin.SubTitle}*/}
{/* >*/}
{/* <input*/}
{/* type="checkbox"*/}
{/* checked={config.hideBuiltinMasks}*/}
{/* onChange={(e) =>*/}
{/* updateConfig(*/}
{/* (config) =>*/}
{/* (config.hideBuiltinMasks = e.currentTarget.checked),*/}
{/* )*/}
{/* }*/}
{/* ></input>*/}
{/* </ListItem>*/}
{/*</List>*/}
<List>
<ListItem
title={Locale.Settings.Prompt.Disable.Title}
subTitle={Locale.Settings.Prompt.Disable.SubTitle}
>
<input
type="checkbox"
checked={config.disablePromptHint}
onChange={(e) =>
updateConfig(
(config) =>
(config.disablePromptHint = e.currentTarget.checked),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Prompt.List}
subTitle={Locale.Settings.Prompt.ListCount(
builtinCount,
customCount,
)}
>
<IconButton
icon={<EditIcon />}
text={Locale.Settings.Prompt.Edit}
onClick={() => setShowPromptModal(true)}
/>
</ListItem>
</List>
{/*<List id={SlotID.CustomModel}>*/}
{/* {accessCodeComponent}*/}
{/* {!accessStore.hideUserApiKey && (*/}
{/* <>*/}
{/* {useCustomConfigComponent}*/}
{/* {accessStore.useCustomConfig && (*/}
{/* <>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.Provider.Title}*/}
{/* subTitle={Locale.Settings.Access.Provider.SubTitle}*/}
{/* >*/}
{/* <Select*/}
{/* value={accessStore.provider}*/}
{/* onChange={(e) => {*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.provider = e.target*/}
{/* .value as ServiceProvider),*/}
{/* );*/}
{/* }}*/}
{/* >*/}
{/* {Object.entries(ServiceProvider).map(([k, v]) => (*/}
{/* <option value={v} key={k}>*/}
{/* {k}*/}
{/* </option>*/}
{/* ))}*/}
{/* </Select>*/}
{/* </ListItem>*/}
{/* {openAIConfigComponent}*/}
{/* {azureConfigComponent}*/}
{/* {googleConfigComponent}*/}
{/* {anthropicConfigComponent}*/}
{/* {baiduConfigComponent}*/}
{/* {byteDanceConfigComponent}*/}
{/* {alibabaConfigComponent}*/}
{/* {tencentConfigComponent}*/}
{/* {moonshotConfigComponent}*/}
{/* {stabilityConfigComponent}*/}
{/* </>*/}
{/* )}*/}
{/* </>*/}
{/*)}*/}
{/* {!shouldHideBalanceQuery && !clientConfig?.isApp ? (*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Usage.Title}*/}
{/* subTitle={*/}
{/* showUsage*/}
{/* ? loadingUsage*/}
{/* ? Locale.Settings.Usage.IsChecking*/}
{/* : Locale.Settings.Usage.SubTitle(*/}
{/* usage?.used ?? "[?]",*/}
{/* usage?.subscription ?? "[?]",*/}
{/* )*/}
{/* : Locale.Settings.Usage.NoAccess*/}
{/* }*/}
{/* >*/}
{/* {!showUsage || loadingUsage ? (*/}
{/* <div />*/}
{/* ) : (*/}
{/* <IconButton*/}
{/* icon={<ResetIcon></ResetIcon>}*/}
{/* text={Locale.Settings.Usage.Check}*/}
{/* onClick={() => checkUsage(true)}*/}
{/* />*/}
{/* )}*/}
{/* </ListItem>*/}
{/* ) : null}*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.CustomModel.Title}*/}
{/* subTitle={Locale.Settings.Access.CustomModel.SubTitle}*/}
{/* >*/}
{/* <input*/}
{/* type="text"*/}
{/* value={config.customModels}*/}
{/* placeholder="model1,model2,model3"*/}
{/* onChange={(e) =>*/}
{/* config.update(*/}
{/* (config) => (config.customModels = e.currentTarget.value),*/}
{/* )*/}
{/* }*/}
{/* ></input>*/}
{/* </ListItem>*/}
{/*</List>*/}
<List>
<ModelConfigList
modelConfig={config.modelConfig}
updateConfig={(updater) => {
const modelConfig = { ...config.modelConfig };
updater(modelConfig);
config.update((config) => (config.modelConfig = modelConfig));
}}
/>
</List>
{shouldShowPromptModal && (
<UserPromptModal onClose={() => setShowPromptModal(false)} />
)}
<DangerItems />
</div>
</ErrorBoundary>
);
}