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 {
Input,
List,
ListItem,
Modal,
PasswordInput,
Popover,
Select,
showConfirm,
} 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 { Path, RELEASE_URL, UPDATE_URL } 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";
function EditPromptModal(props: { id: number; onClose: () => void }) {
const promptStore = usePromptStore();
const prompt = promptStore.get(props.id);
return prompt ? (
) : 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([]);
const prompts = searchInput.length > 0 ? searchPrompts : allPrompts;
const [editingPromptId, setEditingPromptId] = useState();
useEffect(() => {
if (searchInput.length > 0) {
const searchResult = SearchService.search(searchInput);
setSearchPrompts(searchResult);
} else {
setSearchPrompts([]);
}
}, [searchInput]);
return (
props.onClose?.()}
actions={[
promptStore.add({
title: "Empty Prompt",
content: "Empty Prompt Content",
})
}
icon={}
bordered
text={Locale.Settings.Prompt.Modal.Add}
/>,
]}
>
setSearchInput(e.currentTarget.value)}
>
{prompts.map((v, _) => (
{v.isUser && (
}
className={styles["user-prompt-button"]}
onClick={() => promptStore.remove(v.id!)}
/>
)}
{v.isUser ? (
}
className={styles["user-prompt-button"]}
onClick={() => setEditingPromptId(v.id)}
/>
) : (
}
className={styles["user-prompt-button"]}
onClick={() => setEditingPromptId(v.id)}
/>
)}
}
className={styles["user-prompt-button"]}
onClick={() => copyToClipboard(v.content)}
/>
))}
{editingPromptId !== undefined && (
setEditingPromptId(undefined)}
/>
)}
);
}
function DangerItems() {
const chatStore = useChatStore();
const appConfig = useAppConfig();
return (
{
if (await showConfirm(Locale.Settings.Danger.Reset.Confirm)) {
appConfig.reset();
}
}}
type="danger"
/>
{
if (await showConfirm(Locale.Settings.Danger.Clear.Confirm)) {
chatStore.clearAllData();
}
}}
type="danger"
/>
);
}
function SyncItems() {
const syncStore = useSyncStore();
const webdav = syncStore.webDavConfig;
// not ready: https://github.com/Yidadaa/ChatGPT-Next-Web/issues/920#issuecomment-1609866332
return null;
return (
}
text="同步"
onClick={() => {
syncStore.check().then(console.log);
}}
/>
{
syncStore.update(
(config) => (config.server = e.currentTarget.value),
);
}}
/>
{
syncStore.update(
(config) => (config.username = e.currentTarget.value),
);
}}
/>
{
syncStore.update(
(config) => (config.password = e.currentTarget.value),
);
}}
/>
);
}
export function Settings() {
const navigate = useNavigate();
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const config = useAppConfig();
const updateConfig = config.update;
const chatStore = useChatStore();
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 usage = {
used: updateStore.used,
subscription: updateStore.subscription,
};
const [loadingUsage, setLoadingUsage] = useState(false);
function checkUsage(force = false) {
setLoadingUsage(true);
updateStore.updateUsage(force).finally(() => {
setLoadingUsage(false);
});
}
const accessStore = useAccessStore();
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);
}
};
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;
return (
{Locale.Settings.Title}
{Locale.Settings.SubTitle}
}
onClick={() => navigate(Path.Home)}
bordered
/>
);
}