settings.tsx 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653
  1. import { useState, useEffect, useMemo } from "react";
  2. import styles from "./settings.module.scss";
  3. import ResetIcon from "../icons/reload.svg";
  4. import AddIcon from "../icons/add.svg";
  5. import CloseIcon from "../icons/close.svg";
  6. import CopyIcon from "../icons/copy.svg";
  7. import ClearIcon from "../icons/clear.svg";
  8. import LoadingIcon from "../icons/three-dots.svg";
  9. import EditIcon from "../icons/edit.svg";
  10. import EyeIcon from "../icons/eye.svg";
  11. import DownloadIcon from "../icons/download.svg";
  12. import UploadIcon from "../icons/upload.svg";
  13. import ConfigIcon from "../icons/config.svg";
  14. import ConfirmIcon from "../icons/confirm.svg";
  15. import ConnectionIcon from "../icons/connection.svg";
  16. import CloudSuccessIcon from "../icons/cloud-success.svg";
  17. import CloudFailIcon from "../icons/cloud-fail.svg";
  18. import {
  19. Input,
  20. List,
  21. ListItem,
  22. Modal,
  23. PasswordInput,
  24. Popover,
  25. Select,
  26. showConfirm,
  27. showToast,
  28. } from "./ui-lib";
  29. import { ModelConfigList } from "./model-config";
  30. import { IconButton } from "./button";
  31. import {
  32. SubmitKey,
  33. useChatStore,
  34. Theme,
  35. useUpdateStore,
  36. useAccessStore,
  37. useAppConfig,
  38. } from "../store";
  39. import Locale, {
  40. AllLangs,
  41. ALL_LANG_OPTIONS,
  42. changeLang,
  43. getLang,
  44. } from "../locales";
  45. import { copyToClipboard } from "../utils";
  46. import Link from "next/link";
  47. import {
  48. Anthropic,
  49. Azure,
  50. Baidu,
  51. Tencent,
  52. ByteDance,
  53. Alibaba,
  54. Moonshot,
  55. Google,
  56. GoogleSafetySettingsThreshold,
  57. OPENAI_BASE_URL,
  58. Path,
  59. RELEASE_URL,
  60. STORAGE_KEY,
  61. ServiceProvider,
  62. SlotID,
  63. UPDATE_URL,
  64. Stability,
  65. Iflytek,
  66. } from "../constant";
  67. import { Prompt, SearchService, usePromptStore } from "../store/prompt";
  68. import { ErrorBoundary } from "./error";
  69. import { InputRange } from "./input-range";
  70. import { useNavigate } from "react-router-dom";
  71. import { Avatar, AvatarPicker } from "./emoji";
  72. import { getClientConfig } from "../config/client";
  73. import { useSyncStore } from "../store/sync";
  74. import { nanoid } from "nanoid";
  75. import { useMaskStore } from "../store/mask";
  76. import { ProviderType } from "../utils/cloud";
  77. function EditPromptModal(props: { id: string; onClose: () => void }) {
  78. const promptStore = usePromptStore();
  79. const prompt = promptStore.get(props.id);
  80. return prompt ? (
  81. <div className="modal-mask">
  82. <Modal
  83. title={Locale.Settings.Prompt.EditModal.Title}
  84. onClose={props.onClose}
  85. actions={[
  86. <IconButton
  87. key=""
  88. onClick={props.onClose}
  89. text={Locale.UI.Confirm}
  90. bordered
  91. />,
  92. ]}
  93. >
  94. <div className={styles["edit-prompt-modal"]}>
  95. <input
  96. type="text"
  97. value={prompt.title}
  98. readOnly={!prompt.isUser}
  99. className={styles["edit-prompt-title"]}
  100. onInput={(e) =>
  101. promptStore.updatePrompt(
  102. props.id,
  103. (prompt) => (prompt.title = e.currentTarget.value),
  104. )
  105. }
  106. ></input>
  107. <Input
  108. value={prompt.content}
  109. readOnly={!prompt.isUser}
  110. className={styles["edit-prompt-content"]}
  111. rows={10}
  112. onInput={(e) =>
  113. promptStore.updatePrompt(
  114. props.id,
  115. (prompt) => (prompt.content = e.currentTarget.value),
  116. )
  117. }
  118. ></Input>
  119. </div>
  120. </Modal>
  121. </div>
  122. ) : null;
  123. }
  124. function UserPromptModal(props: { onClose?: () => void }) {
  125. const promptStore = usePromptStore();
  126. const userPrompts = promptStore.getUserPrompts();
  127. const builtinPrompts = SearchService.builtinPrompts;
  128. const allPrompts = userPrompts.concat(builtinPrompts);
  129. const [searchInput, setSearchInput] = useState("");
  130. const [searchPrompts, setSearchPrompts] = useState<Prompt[]>([]);
  131. const prompts = searchInput.length > 0 ? searchPrompts : allPrompts;
  132. const [editingPromptId, setEditingPromptId] = useState<string>();
  133. useEffect(() => {
  134. if (searchInput.length > 0) {
  135. const searchResult = SearchService.search(searchInput);
  136. setSearchPrompts(searchResult);
  137. } else {
  138. setSearchPrompts([]);
  139. }
  140. }, [searchInput]);
  141. return (
  142. <div className="modal-mask">
  143. <Modal
  144. title={Locale.Settings.Prompt.Modal.Title}
  145. onClose={() => props.onClose?.()}
  146. actions={[
  147. <IconButton
  148. key="add"
  149. onClick={() => {
  150. const promptId = promptStore.add({
  151. id: nanoid(),
  152. createdAt: Date.now(),
  153. title: "Empty Prompt",
  154. content: "Empty Prompt Content",
  155. });
  156. setEditingPromptId(promptId);
  157. }}
  158. icon={<AddIcon />}
  159. bordered
  160. text={Locale.Settings.Prompt.Modal.Add}
  161. />,
  162. ]}
  163. >
  164. <div className={styles["user-prompt-modal"]}>
  165. <input
  166. type="text"
  167. className={styles["user-prompt-search"]}
  168. placeholder={Locale.Settings.Prompt.Modal.Search}
  169. value={searchInput}
  170. onInput={(e) => setSearchInput(e.currentTarget.value)}
  171. ></input>
  172. <div className={styles["user-prompt-list"]}>
  173. {prompts.map((v, _) => (
  174. <div className={styles["user-prompt-item"]} key={v.id ?? v.title}>
  175. <div className={styles["user-prompt-header"]}>
  176. <div className={styles["user-prompt-title"]}>{v.title}</div>
  177. <div className={styles["user-prompt-content"] + " one-line"}>
  178. {v.content}
  179. </div>
  180. </div>
  181. <div className={styles["user-prompt-buttons"]}>
  182. {v.isUser && (
  183. <IconButton
  184. icon={<ClearIcon />}
  185. className={styles["user-prompt-button"]}
  186. onClick={() => promptStore.remove(v.id!)}
  187. />
  188. )}
  189. {v.isUser ? (
  190. <IconButton
  191. icon={<EditIcon />}
  192. className={styles["user-prompt-button"]}
  193. onClick={() => setEditingPromptId(v.id)}
  194. />
  195. ) : (
  196. <IconButton
  197. icon={<EyeIcon />}
  198. className={styles["user-prompt-button"]}
  199. onClick={() => setEditingPromptId(v.id)}
  200. />
  201. )}
  202. <IconButton
  203. icon={<CopyIcon />}
  204. className={styles["user-prompt-button"]}
  205. onClick={() => copyToClipboard(v.content)}
  206. />
  207. </div>
  208. </div>
  209. ))}
  210. </div>
  211. </div>
  212. </Modal>
  213. {editingPromptId !== undefined && (
  214. <EditPromptModal
  215. id={editingPromptId!}
  216. onClose={() => setEditingPromptId(undefined)}
  217. />
  218. )}
  219. </div>
  220. );
  221. }
  222. function DangerItems() {
  223. const chatStore = useChatStore();
  224. const appConfig = useAppConfig();
  225. return (
  226. <List>
  227. <ListItem
  228. title={Locale.Settings.Danger.Reset.Title}
  229. subTitle={Locale.Settings.Danger.Reset.SubTitle}
  230. >
  231. <IconButton
  232. aria={Locale.Settings.Danger.Reset.Title}
  233. text={Locale.Settings.Danger.Reset.Action}
  234. onClick={async () => {
  235. if (await showConfirm(Locale.Settings.Danger.Reset.Confirm)) {
  236. appConfig.reset();
  237. }
  238. }}
  239. type="danger"
  240. />
  241. </ListItem>
  242. <ListItem
  243. title={Locale.Settings.Danger.Clear.Title}
  244. subTitle={Locale.Settings.Danger.Clear.SubTitle}
  245. >
  246. <IconButton
  247. aria={Locale.Settings.Danger.Clear.Title}
  248. text={Locale.Settings.Danger.Clear.Action}
  249. onClick={async () => {
  250. if (await showConfirm(Locale.Settings.Danger.Clear.Confirm)) {
  251. chatStore.clearAllData();
  252. }
  253. }}
  254. type="danger"
  255. />
  256. </ListItem>
  257. </List>
  258. );
  259. }
  260. function CheckButton() {
  261. const syncStore = useSyncStore();
  262. const couldCheck = useMemo(() => {
  263. return syncStore.cloudSync();
  264. }, [syncStore]);
  265. const [checkState, setCheckState] = useState<
  266. "none" | "checking" | "success" | "failed"
  267. >("none");
  268. async function check() {
  269. setCheckState("checking");
  270. const valid = await syncStore.check();
  271. setCheckState(valid ? "success" : "failed");
  272. }
  273. if (!couldCheck) return null;
  274. return (
  275. <IconButton
  276. text={Locale.Settings.Sync.Config.Modal.Check}
  277. bordered
  278. onClick={check}
  279. icon={
  280. checkState === "none" ? (
  281. <ConnectionIcon />
  282. ) : checkState === "checking" ? (
  283. <LoadingIcon />
  284. ) : checkState === "success" ? (
  285. <CloudSuccessIcon />
  286. ) : checkState === "failed" ? (
  287. <CloudFailIcon />
  288. ) : (
  289. <ConnectionIcon />
  290. )
  291. }
  292. ></IconButton>
  293. );
  294. }
  295. function SyncConfigModal(props: { onClose?: () => void }) {
  296. const syncStore = useSyncStore();
  297. return (
  298. <div className="modal-mask">
  299. <Modal
  300. title={Locale.Settings.Sync.Config.Modal.Title}
  301. onClose={() => props.onClose?.()}
  302. actions={[
  303. <CheckButton key="check" />,
  304. <IconButton
  305. key="confirm"
  306. onClick={props.onClose}
  307. icon={<ConfirmIcon />}
  308. bordered
  309. text={Locale.UI.Confirm}
  310. />,
  311. ]}
  312. >
  313. <List>
  314. <ListItem
  315. title={Locale.Settings.Sync.Config.SyncType.Title}
  316. subTitle={Locale.Settings.Sync.Config.SyncType.SubTitle}
  317. >
  318. <select
  319. value={syncStore.provider}
  320. onChange={(e) => {
  321. syncStore.update(
  322. (config) =>
  323. (config.provider = e.target.value as ProviderType),
  324. );
  325. }}
  326. >
  327. {Object.entries(ProviderType).map(([k, v]) => (
  328. <option value={v} key={k}>
  329. {k}
  330. </option>
  331. ))}
  332. </select>
  333. </ListItem>
  334. <ListItem
  335. title={Locale.Settings.Sync.Config.Proxy.Title}
  336. subTitle={Locale.Settings.Sync.Config.Proxy.SubTitle}
  337. >
  338. <input
  339. type="checkbox"
  340. checked={syncStore.useProxy}
  341. onChange={(e) => {
  342. syncStore.update(
  343. (config) => (config.useProxy = e.currentTarget.checked),
  344. );
  345. }}
  346. ></input>
  347. </ListItem>
  348. {syncStore.useProxy ? (
  349. <ListItem
  350. title={Locale.Settings.Sync.Config.ProxyUrl.Title}
  351. subTitle={Locale.Settings.Sync.Config.ProxyUrl.SubTitle}
  352. >
  353. <input
  354. type="text"
  355. value={syncStore.proxyUrl}
  356. onChange={(e) => {
  357. syncStore.update(
  358. (config) => (config.proxyUrl = e.currentTarget.value),
  359. );
  360. }}
  361. ></input>
  362. </ListItem>
  363. ) : null}
  364. </List>
  365. {syncStore.provider === ProviderType.WebDAV && (
  366. <>
  367. <List>
  368. <ListItem title={Locale.Settings.Sync.Config.WebDav.Endpoint}>
  369. <input
  370. type="text"
  371. value={syncStore.webdav.endpoint}
  372. onChange={(e) => {
  373. syncStore.update(
  374. (config) =>
  375. (config.webdav.endpoint = e.currentTarget.value),
  376. );
  377. }}
  378. ></input>
  379. </ListItem>
  380. <ListItem title={Locale.Settings.Sync.Config.WebDav.UserName}>
  381. <input
  382. type="text"
  383. value={syncStore.webdav.username}
  384. onChange={(e) => {
  385. syncStore.update(
  386. (config) =>
  387. (config.webdav.username = e.currentTarget.value),
  388. );
  389. }}
  390. ></input>
  391. </ListItem>
  392. <ListItem title={Locale.Settings.Sync.Config.WebDav.Password}>
  393. <PasswordInput
  394. value={syncStore.webdav.password}
  395. onChange={(e) => {
  396. syncStore.update(
  397. (config) =>
  398. (config.webdav.password = e.currentTarget.value),
  399. );
  400. }}
  401. ></PasswordInput>
  402. </ListItem>
  403. </List>
  404. </>
  405. )}
  406. {syncStore.provider === ProviderType.UpStash && (
  407. <List>
  408. <ListItem title={Locale.Settings.Sync.Config.UpStash.Endpoint}>
  409. <input
  410. type="text"
  411. value={syncStore.upstash.endpoint}
  412. onChange={(e) => {
  413. syncStore.update(
  414. (config) =>
  415. (config.upstash.endpoint = e.currentTarget.value),
  416. );
  417. }}
  418. ></input>
  419. </ListItem>
  420. <ListItem title={Locale.Settings.Sync.Config.UpStash.UserName}>
  421. <input
  422. type="text"
  423. value={syncStore.upstash.username}
  424. placeholder={STORAGE_KEY}
  425. onChange={(e) => {
  426. syncStore.update(
  427. (config) =>
  428. (config.upstash.username = e.currentTarget.value),
  429. );
  430. }}
  431. ></input>
  432. </ListItem>
  433. <ListItem title={Locale.Settings.Sync.Config.UpStash.Password}>
  434. <PasswordInput
  435. value={syncStore.upstash.apiKey}
  436. onChange={(e) => {
  437. syncStore.update(
  438. (config) => (config.upstash.apiKey = e.currentTarget.value),
  439. );
  440. }}
  441. ></PasswordInput>
  442. </ListItem>
  443. </List>
  444. )}
  445. </Modal>
  446. </div>
  447. );
  448. }
  449. function SyncItems() {
  450. const syncStore = useSyncStore();
  451. const chatStore = useChatStore();
  452. const promptStore = usePromptStore();
  453. const maskStore = useMaskStore();
  454. const couldSync = useMemo(() => {
  455. return syncStore.cloudSync();
  456. }, [syncStore]);
  457. const [showSyncConfigModal, setShowSyncConfigModal] = useState(false);
  458. const stateOverview = useMemo(() => {
  459. const sessions = chatStore.sessions;
  460. const messageCount = sessions.reduce((p, c) => p + c.messages.length, 0);
  461. return {
  462. chat: sessions.length,
  463. message: messageCount,
  464. prompt: Object.keys(promptStore.prompts).length,
  465. mask: Object.keys(maskStore.masks).length,
  466. };
  467. }, [chatStore.sessions, maskStore.masks, promptStore.prompts]);
  468. return (
  469. <>
  470. <List>
  471. <ListItem
  472. title={Locale.Settings.Sync.CloudState}
  473. subTitle={
  474. syncStore.lastProvider
  475. ? `${new Date(syncStore.lastSyncTime).toLocaleString()} [${
  476. syncStore.lastProvider
  477. }]`
  478. : Locale.Settings.Sync.NotSyncYet
  479. }
  480. >
  481. <div style={{ display: "flex" }}>
  482. <IconButton
  483. aria={Locale.Settings.Sync.CloudState + Locale.UI.Config}
  484. icon={<ConfigIcon />}
  485. text={Locale.UI.Config}
  486. onClick={() => {
  487. setShowSyncConfigModal(true);
  488. }}
  489. />
  490. {couldSync && (
  491. <IconButton
  492. icon={<ResetIcon />}
  493. text={Locale.UI.Sync}
  494. onClick={async () => {
  495. try {
  496. await syncStore.sync();
  497. showToast(Locale.Settings.Sync.Success);
  498. } catch (e) {
  499. showToast(Locale.Settings.Sync.Fail);
  500. console.error("[Sync]", e);
  501. }
  502. }}
  503. />
  504. )}
  505. </div>
  506. </ListItem>
  507. <ListItem
  508. title={Locale.Settings.Sync.LocalState}
  509. subTitle={Locale.Settings.Sync.Overview(stateOverview)}
  510. >
  511. <div style={{ display: "flex" }}>
  512. <IconButton
  513. aria={Locale.Settings.Sync.LocalState + Locale.UI.Export}
  514. icon={<UploadIcon />}
  515. text={Locale.UI.Export}
  516. onClick={() => {
  517. syncStore.export();
  518. }}
  519. />
  520. <IconButton
  521. aria={Locale.Settings.Sync.LocalState + Locale.UI.Import}
  522. icon={<DownloadIcon />}
  523. text={Locale.UI.Import}
  524. onClick={() => {
  525. syncStore.import();
  526. }}
  527. />
  528. </div>
  529. </ListItem>
  530. </List>
  531. {showSyncConfigModal && (
  532. <SyncConfigModal onClose={() => setShowSyncConfigModal(false)} />
  533. )}
  534. </>
  535. );
  536. }
  537. export function Settings() {
  538. const navigate = useNavigate();
  539. const [showEmojiPicker, setShowEmojiPicker] = useState(false);
  540. const config = useAppConfig();
  541. const updateConfig = config.update;
  542. const updateStore = useUpdateStore();
  543. const [checkingUpdate, setCheckingUpdate] = useState(false);
  544. const currentVersion = updateStore.formatVersion(updateStore.version);
  545. const remoteId = updateStore.formatVersion(updateStore.remoteVersion);
  546. const hasNewVersion = currentVersion !== remoteId;
  547. const updateUrl = getClientConfig()?.isApp ? RELEASE_URL : UPDATE_URL;
  548. function checkUpdate(force = false) {
  549. setCheckingUpdate(true);
  550. updateStore.getLatestVersion(force).then(() => {
  551. setCheckingUpdate(false);
  552. });
  553. console.log("[Update] local version ", updateStore.version);
  554. console.log("[Update] remote version ", updateStore.remoteVersion);
  555. }
  556. const accessStore = useAccessStore();
  557. const shouldHideBalanceQuery = useMemo(() => {
  558. const isOpenAiUrl = accessStore.openaiUrl.includes(OPENAI_BASE_URL);
  559. return (
  560. accessStore.hideBalanceQuery ||
  561. isOpenAiUrl ||
  562. accessStore.provider === ServiceProvider.Azure
  563. );
  564. }, [
  565. accessStore.hideBalanceQuery,
  566. accessStore.openaiUrl,
  567. accessStore.provider,
  568. ]);
  569. const usage = {
  570. used: updateStore.used,
  571. subscription: updateStore.subscription,
  572. };
  573. const [loadingUsage, setLoadingUsage] = useState(false);
  574. function checkUsage(force = false) {
  575. if (shouldHideBalanceQuery) {
  576. return;
  577. }
  578. setLoadingUsage(true);
  579. updateStore.updateUsage(force).finally(() => {
  580. setLoadingUsage(false);
  581. });
  582. }
  583. const enabledAccessControl = useMemo(
  584. () => accessStore.enabledAccessControl(),
  585. // eslint-disable-next-line react-hooks/exhaustive-deps
  586. [],
  587. );
  588. const promptStore = usePromptStore();
  589. const builtinCount = SearchService.count.builtin;
  590. const customCount = promptStore.getUserPrompts().length ?? 0;
  591. const [shouldShowPromptModal, setShowPromptModal] = useState(false);
  592. const showUsage = accessStore.isAuthorized();
  593. useEffect(() => {
  594. // checks per minutes
  595. checkUpdate();
  596. showUsage && checkUsage();
  597. // eslint-disable-next-line react-hooks/exhaustive-deps
  598. }, []);
  599. useEffect(() => {
  600. const keydownEvent = (e: KeyboardEvent) => {
  601. if (e.key === "Escape") {
  602. navigate(Path.Home);
  603. }
  604. };
  605. if (clientConfig?.isApp) {
  606. // Force to set custom endpoint to true if it's app
  607. accessStore.update((state) => {
  608. state.useCustomConfig = true;
  609. });
  610. }
  611. document.addEventListener("keydown", keydownEvent);
  612. return () => {
  613. document.removeEventListener("keydown", keydownEvent);
  614. };
  615. // eslint-disable-next-line react-hooks/exhaustive-deps
  616. }, []);
  617. const clientConfig = useMemo(() => getClientConfig(), []);
  618. const showAccessCode = enabledAccessControl && !clientConfig?.isApp;
  619. const accessCodeComponent = showAccessCode && (
  620. <ListItem
  621. title={Locale.Settings.Access.AccessCode.Title}
  622. subTitle={Locale.Settings.Access.AccessCode.SubTitle}
  623. >
  624. <PasswordInput
  625. value={accessStore.accessCode}
  626. type="text"
  627. placeholder={Locale.Settings.Access.AccessCode.Placeholder}
  628. onChange={(e) => {
  629. accessStore.update(
  630. (access) => (access.accessCode = e.currentTarget.value),
  631. );
  632. }}
  633. />
  634. </ListItem>
  635. );
  636. const useCustomConfigComponent = // Conditionally render the following ListItem based on clientConfig.isApp
  637. !clientConfig?.isApp && ( // only show if isApp is false
  638. <ListItem
  639. title={Locale.Settings.Access.CustomEndpoint.Title}
  640. subTitle={Locale.Settings.Access.CustomEndpoint.SubTitle}
  641. >
  642. <input
  643. aria-label={Locale.Settings.Access.CustomEndpoint.Title}
  644. type="checkbox"
  645. checked={accessStore.useCustomConfig}
  646. onChange={(e) =>
  647. accessStore.update(
  648. (access) => (access.useCustomConfig = e.currentTarget.checked),
  649. )
  650. }
  651. ></input>
  652. </ListItem>
  653. );
  654. const openAIConfigComponent = accessStore.provider ===
  655. ServiceProvider.OpenAI && (
  656. <>
  657. <ListItem
  658. title={Locale.Settings.Access.OpenAI.Endpoint.Title}
  659. subTitle={Locale.Settings.Access.OpenAI.Endpoint.SubTitle}
  660. >
  661. <input
  662. aria-label={Locale.Settings.Access.OpenAI.Endpoint.Title}
  663. type="text"
  664. value={accessStore.openaiUrl}
  665. placeholder={OPENAI_BASE_URL}
  666. onChange={(e) =>
  667. accessStore.update(
  668. (access) => (access.openaiUrl = e.currentTarget.value),
  669. )
  670. }
  671. ></input>
  672. </ListItem>
  673. <ListItem
  674. title={Locale.Settings.Access.OpenAI.ApiKey.Title}
  675. subTitle={Locale.Settings.Access.OpenAI.ApiKey.SubTitle}
  676. >
  677. <PasswordInput
  678. aria={Locale.Settings.ShowPassword}
  679. aria-label={Locale.Settings.Access.OpenAI.ApiKey.Title}
  680. value={accessStore.openaiApiKey}
  681. type="text"
  682. placeholder={Locale.Settings.Access.OpenAI.ApiKey.Placeholder}
  683. onChange={(e) => {
  684. accessStore.update(
  685. (access) => (access.openaiApiKey = e.currentTarget.value),
  686. );
  687. }}
  688. />
  689. </ListItem>
  690. </>
  691. );
  692. const azureConfigComponent = accessStore.provider ===
  693. ServiceProvider.Azure && (
  694. <>
  695. <ListItem
  696. title={Locale.Settings.Access.Azure.Endpoint.Title}
  697. subTitle={
  698. Locale.Settings.Access.Azure.Endpoint.SubTitle + Azure.ExampleEndpoint
  699. }
  700. >
  701. <input
  702. aria-label={Locale.Settings.Access.Azure.Endpoint.Title}
  703. type="text"
  704. value={accessStore.azureUrl}
  705. placeholder={Azure.ExampleEndpoint}
  706. onChange={(e) =>
  707. accessStore.update(
  708. (access) => (access.azureUrl = e.currentTarget.value),
  709. )
  710. }
  711. ></input>
  712. </ListItem>
  713. <ListItem
  714. title={Locale.Settings.Access.Azure.ApiKey.Title}
  715. subTitle={Locale.Settings.Access.Azure.ApiKey.SubTitle}
  716. >
  717. <PasswordInput
  718. aria-label={Locale.Settings.Access.Azure.ApiKey.Title}
  719. value={accessStore.azureApiKey}
  720. type="text"
  721. placeholder={Locale.Settings.Access.Azure.ApiKey.Placeholder}
  722. onChange={(e) => {
  723. accessStore.update(
  724. (access) => (access.azureApiKey = e.currentTarget.value),
  725. );
  726. }}
  727. />
  728. </ListItem>
  729. <ListItem
  730. title={Locale.Settings.Access.Azure.ApiVerion.Title}
  731. subTitle={Locale.Settings.Access.Azure.ApiVerion.SubTitle}
  732. >
  733. <input
  734. aria-label={Locale.Settings.Access.Azure.ApiVerion.Title}
  735. type="text"
  736. value={accessStore.azureApiVersion}
  737. placeholder="2023-08-01-preview"
  738. onChange={(e) =>
  739. accessStore.update(
  740. (access) => (access.azureApiVersion = e.currentTarget.value),
  741. )
  742. }
  743. ></input>
  744. </ListItem>
  745. </>
  746. );
  747. const googleConfigComponent = accessStore.provider ===
  748. ServiceProvider.Google && (
  749. <>
  750. <ListItem
  751. title={Locale.Settings.Access.Google.Endpoint.Title}
  752. subTitle={
  753. Locale.Settings.Access.Google.Endpoint.SubTitle +
  754. Google.ExampleEndpoint
  755. }
  756. >
  757. <input
  758. aria-label={Locale.Settings.Access.Google.Endpoint.Title}
  759. type="text"
  760. value={accessStore.googleUrl}
  761. placeholder={Google.ExampleEndpoint}
  762. onChange={(e) =>
  763. accessStore.update(
  764. (access) => (access.googleUrl = e.currentTarget.value),
  765. )
  766. }
  767. ></input>
  768. </ListItem>
  769. <ListItem
  770. title={Locale.Settings.Access.Google.ApiKey.Title}
  771. subTitle={Locale.Settings.Access.Google.ApiKey.SubTitle}
  772. >
  773. <PasswordInput
  774. aria-label={Locale.Settings.Access.Google.ApiKey.Title}
  775. value={accessStore.googleApiKey}
  776. type="text"
  777. placeholder={Locale.Settings.Access.Google.ApiKey.Placeholder}
  778. onChange={(e) => {
  779. accessStore.update(
  780. (access) => (access.googleApiKey = e.currentTarget.value),
  781. );
  782. }}
  783. />
  784. </ListItem>
  785. <ListItem
  786. title={Locale.Settings.Access.Google.ApiVersion.Title}
  787. subTitle={Locale.Settings.Access.Google.ApiVersion.SubTitle}
  788. >
  789. <input
  790. aria-label={Locale.Settings.Access.Google.ApiVersion.Title}
  791. type="text"
  792. value={accessStore.googleApiVersion}
  793. placeholder="2023-08-01-preview"
  794. onChange={(e) =>
  795. accessStore.update(
  796. (access) => (access.googleApiVersion = e.currentTarget.value),
  797. )
  798. }
  799. ></input>
  800. </ListItem>
  801. <ListItem
  802. title={Locale.Settings.Access.Google.GoogleSafetySettings.Title}
  803. subTitle={Locale.Settings.Access.Google.GoogleSafetySettings.SubTitle}
  804. >
  805. <Select
  806. aria-label={Locale.Settings.Access.Google.GoogleSafetySettings.Title}
  807. value={accessStore.googleSafetySettings}
  808. onChange={(e) => {
  809. accessStore.update(
  810. (access) =>
  811. (access.googleSafetySettings = e.target
  812. .value as GoogleSafetySettingsThreshold),
  813. );
  814. }}
  815. >
  816. {Object.entries(GoogleSafetySettingsThreshold).map(([k, v]) => (
  817. <option value={v} key={k}>
  818. {k}
  819. </option>
  820. ))}
  821. </Select>
  822. </ListItem>
  823. </>
  824. );
  825. const anthropicConfigComponent = accessStore.provider ===
  826. ServiceProvider.Anthropic && (
  827. <>
  828. <ListItem
  829. title={Locale.Settings.Access.Anthropic.Endpoint.Title}
  830. subTitle={
  831. Locale.Settings.Access.Anthropic.Endpoint.SubTitle +
  832. Anthropic.ExampleEndpoint
  833. }
  834. >
  835. <input
  836. aria-label={Locale.Settings.Access.Anthropic.Endpoint.Title}
  837. type="text"
  838. value={accessStore.anthropicUrl}
  839. placeholder={Anthropic.ExampleEndpoint}
  840. onChange={(e) =>
  841. accessStore.update(
  842. (access) => (access.anthropicUrl = e.currentTarget.value),
  843. )
  844. }
  845. ></input>
  846. </ListItem>
  847. <ListItem
  848. title={Locale.Settings.Access.Anthropic.ApiKey.Title}
  849. subTitle={Locale.Settings.Access.Anthropic.ApiKey.SubTitle}
  850. >
  851. <PasswordInput
  852. aria-label={Locale.Settings.Access.Anthropic.ApiKey.Title}
  853. value={accessStore.anthropicApiKey}
  854. type="text"
  855. placeholder={Locale.Settings.Access.Anthropic.ApiKey.Placeholder}
  856. onChange={(e) => {
  857. accessStore.update(
  858. (access) => (access.anthropicApiKey = e.currentTarget.value),
  859. );
  860. }}
  861. />
  862. </ListItem>
  863. <ListItem
  864. title={Locale.Settings.Access.Anthropic.ApiVerion.Title}
  865. subTitle={Locale.Settings.Access.Anthropic.ApiVerion.SubTitle}
  866. >
  867. <input
  868. aria-label={Locale.Settings.Access.Anthropic.ApiVerion.Title}
  869. type="text"
  870. value={accessStore.anthropicApiVersion}
  871. placeholder={Anthropic.Vision}
  872. onChange={(e) =>
  873. accessStore.update(
  874. (access) => (access.anthropicApiVersion = e.currentTarget.value),
  875. )
  876. }
  877. ></input>
  878. </ListItem>
  879. </>
  880. );
  881. const baiduConfigComponent = accessStore.provider ===
  882. ServiceProvider.Baidu && (
  883. <>
  884. <ListItem
  885. title={Locale.Settings.Access.Baidu.Endpoint.Title}
  886. subTitle={Locale.Settings.Access.Baidu.Endpoint.SubTitle}
  887. >
  888. <input
  889. aria-label={Locale.Settings.Access.Baidu.Endpoint.Title}
  890. type="text"
  891. value={accessStore.baiduUrl}
  892. placeholder={Baidu.ExampleEndpoint}
  893. onChange={(e) =>
  894. accessStore.update(
  895. (access) => (access.baiduUrl = e.currentTarget.value),
  896. )
  897. }
  898. ></input>
  899. </ListItem>
  900. <ListItem
  901. title={Locale.Settings.Access.Baidu.ApiKey.Title}
  902. subTitle={Locale.Settings.Access.Baidu.ApiKey.SubTitle}
  903. >
  904. <PasswordInput
  905. aria-label={Locale.Settings.Access.Baidu.ApiKey.Title}
  906. value={accessStore.baiduApiKey}
  907. type="text"
  908. placeholder={Locale.Settings.Access.Baidu.ApiKey.Placeholder}
  909. onChange={(e) => {
  910. accessStore.update(
  911. (access) => (access.baiduApiKey = e.currentTarget.value),
  912. );
  913. }}
  914. />
  915. </ListItem>
  916. <ListItem
  917. title={Locale.Settings.Access.Baidu.SecretKey.Title}
  918. subTitle={Locale.Settings.Access.Baidu.SecretKey.SubTitle}
  919. >
  920. <PasswordInput
  921. aria-label={Locale.Settings.Access.Baidu.SecretKey.Title}
  922. value={accessStore.baiduSecretKey}
  923. type="text"
  924. placeholder={Locale.Settings.Access.Baidu.SecretKey.Placeholder}
  925. onChange={(e) => {
  926. accessStore.update(
  927. (access) => (access.baiduSecretKey = e.currentTarget.value),
  928. );
  929. }}
  930. />
  931. </ListItem>
  932. </>
  933. );
  934. const tencentConfigComponent = accessStore.provider ===
  935. ServiceProvider.Tencent && (
  936. <>
  937. <ListItem
  938. title={Locale.Settings.Access.Tencent.Endpoint.Title}
  939. subTitle={Locale.Settings.Access.Tencent.Endpoint.SubTitle}
  940. >
  941. <input
  942. aria-label={Locale.Settings.Access.Tencent.Endpoint.Title}
  943. type="text"
  944. value={accessStore.tencentUrl}
  945. placeholder={Tencent.ExampleEndpoint}
  946. onChange={(e) =>
  947. accessStore.update(
  948. (access) => (access.tencentUrl = e.currentTarget.value),
  949. )
  950. }
  951. ></input>
  952. </ListItem>
  953. <ListItem
  954. title={Locale.Settings.Access.Tencent.ApiKey.Title}
  955. subTitle={Locale.Settings.Access.Tencent.ApiKey.SubTitle}
  956. >
  957. <PasswordInput
  958. aria-label={Locale.Settings.Access.Tencent.ApiKey.Title}
  959. value={accessStore.tencentSecretId}
  960. type="text"
  961. placeholder={Locale.Settings.Access.Tencent.ApiKey.Placeholder}
  962. onChange={(e) => {
  963. accessStore.update(
  964. (access) => (access.tencentSecretId = e.currentTarget.value),
  965. );
  966. }}
  967. />
  968. </ListItem>
  969. <ListItem
  970. title={Locale.Settings.Access.Tencent.SecretKey.Title}
  971. subTitle={Locale.Settings.Access.Tencent.SecretKey.SubTitle}
  972. >
  973. <PasswordInput
  974. aria-label={Locale.Settings.Access.Tencent.SecretKey.Title}
  975. value={accessStore.tencentSecretKey}
  976. type="text"
  977. placeholder={Locale.Settings.Access.Tencent.SecretKey.Placeholder}
  978. onChange={(e) => {
  979. accessStore.update(
  980. (access) => (access.tencentSecretKey = e.currentTarget.value),
  981. );
  982. }}
  983. />
  984. </ListItem>
  985. </>
  986. );
  987. const byteDanceConfigComponent = accessStore.provider ===
  988. ServiceProvider.ByteDance && (
  989. <>
  990. <ListItem
  991. title={Locale.Settings.Access.ByteDance.Endpoint.Title}
  992. subTitle={
  993. Locale.Settings.Access.ByteDance.Endpoint.SubTitle +
  994. ByteDance.ExampleEndpoint
  995. }
  996. >
  997. <input
  998. aria-label={Locale.Settings.Access.ByteDance.Endpoint.Title}
  999. type="text"
  1000. value={accessStore.bytedanceUrl}
  1001. placeholder={ByteDance.ExampleEndpoint}
  1002. onChange={(e) =>
  1003. accessStore.update(
  1004. (access) => (access.bytedanceUrl = e.currentTarget.value),
  1005. )
  1006. }
  1007. ></input>
  1008. </ListItem>
  1009. <ListItem
  1010. title={Locale.Settings.Access.ByteDance.ApiKey.Title}
  1011. subTitle={Locale.Settings.Access.ByteDance.ApiKey.SubTitle}
  1012. >
  1013. <PasswordInput
  1014. aria-label={Locale.Settings.Access.ByteDance.ApiKey.Title}
  1015. value={accessStore.bytedanceApiKey}
  1016. type="text"
  1017. placeholder={Locale.Settings.Access.ByteDance.ApiKey.Placeholder}
  1018. onChange={(e) => {
  1019. accessStore.update(
  1020. (access) => (access.bytedanceApiKey = e.currentTarget.value),
  1021. );
  1022. }}
  1023. />
  1024. </ListItem>
  1025. </>
  1026. );
  1027. const alibabaConfigComponent = accessStore.provider ===
  1028. ServiceProvider.Alibaba && (
  1029. <>
  1030. <ListItem
  1031. title={Locale.Settings.Access.Alibaba.Endpoint.Title}
  1032. subTitle={
  1033. Locale.Settings.Access.Alibaba.Endpoint.SubTitle +
  1034. Alibaba.ExampleEndpoint
  1035. }
  1036. >
  1037. <input
  1038. aria-label={Locale.Settings.Access.Alibaba.Endpoint.Title}
  1039. type="text"
  1040. value={accessStore.alibabaUrl}
  1041. placeholder={Alibaba.ExampleEndpoint}
  1042. onChange={(e) =>
  1043. accessStore.update(
  1044. (access) => (access.alibabaUrl = e.currentTarget.value),
  1045. )
  1046. }
  1047. ></input>
  1048. </ListItem>
  1049. <ListItem
  1050. title={Locale.Settings.Access.Alibaba.ApiKey.Title}
  1051. subTitle={Locale.Settings.Access.Alibaba.ApiKey.SubTitle}
  1052. >
  1053. <PasswordInput
  1054. aria-label={Locale.Settings.Access.Alibaba.ApiKey.Title}
  1055. value={accessStore.alibabaApiKey}
  1056. type="text"
  1057. placeholder={Locale.Settings.Access.Alibaba.ApiKey.Placeholder}
  1058. onChange={(e) => {
  1059. accessStore.update(
  1060. (access) => (access.alibabaApiKey = e.currentTarget.value),
  1061. );
  1062. }}
  1063. />
  1064. </ListItem>
  1065. </>
  1066. );
  1067. const moonshotConfigComponent = accessStore.provider ===
  1068. ServiceProvider.Moonshot && (
  1069. <>
  1070. <ListItem
  1071. title={Locale.Settings.Access.Moonshot.Endpoint.Title}
  1072. subTitle={
  1073. Locale.Settings.Access.Moonshot.Endpoint.SubTitle +
  1074. Moonshot.ExampleEndpoint
  1075. }
  1076. >
  1077. <input
  1078. aria-label={Locale.Settings.Access.Moonshot.Endpoint.Title}
  1079. type="text"
  1080. value={accessStore.moonshotUrl}
  1081. placeholder={Moonshot.ExampleEndpoint}
  1082. onChange={(e) =>
  1083. accessStore.update(
  1084. (access) => (access.moonshotUrl = e.currentTarget.value),
  1085. )
  1086. }
  1087. ></input>
  1088. </ListItem>
  1089. <ListItem
  1090. title={Locale.Settings.Access.Moonshot.ApiKey.Title}
  1091. subTitle={Locale.Settings.Access.Moonshot.ApiKey.SubTitle}
  1092. >
  1093. <PasswordInput
  1094. aria-label={Locale.Settings.Access.Moonshot.ApiKey.Title}
  1095. value={accessStore.moonshotApiKey}
  1096. type="text"
  1097. placeholder={Locale.Settings.Access.Moonshot.ApiKey.Placeholder}
  1098. onChange={(e) => {
  1099. accessStore.update(
  1100. (access) => (access.moonshotApiKey = e.currentTarget.value),
  1101. );
  1102. }}
  1103. />
  1104. </ListItem>
  1105. </>
  1106. );
  1107. const stabilityConfigComponent = accessStore.provider ===
  1108. ServiceProvider.Stability && (
  1109. <>
  1110. <ListItem
  1111. title={Locale.Settings.Access.Stability.Endpoint.Title}
  1112. subTitle={
  1113. Locale.Settings.Access.Stability.Endpoint.SubTitle +
  1114. Stability.ExampleEndpoint
  1115. }
  1116. >
  1117. <input
  1118. aria-label={Locale.Settings.Access.Stability.Endpoint.Title}
  1119. type="text"
  1120. value={accessStore.stabilityUrl}
  1121. placeholder={Stability.ExampleEndpoint}
  1122. onChange={(e) =>
  1123. accessStore.update(
  1124. (access) => (access.stabilityUrl = e.currentTarget.value),
  1125. )
  1126. }
  1127. ></input>
  1128. </ListItem>
  1129. <ListItem
  1130. title={Locale.Settings.Access.Stability.ApiKey.Title}
  1131. subTitle={Locale.Settings.Access.Stability.ApiKey.SubTitle}
  1132. >
  1133. <PasswordInput
  1134. aria-label={Locale.Settings.Access.Stability.ApiKey.Title}
  1135. value={accessStore.stabilityApiKey}
  1136. type="text"
  1137. placeholder={Locale.Settings.Access.Stability.ApiKey.Placeholder}
  1138. onChange={(e) => {
  1139. accessStore.update(
  1140. (access) => (access.stabilityApiKey = e.currentTarget.value),
  1141. );
  1142. }}
  1143. />
  1144. </ListItem>
  1145. </>
  1146. );
  1147. const lflytekConfigComponent = accessStore.provider ===
  1148. ServiceProvider.Iflytek && (
  1149. <>
  1150. <ListItem
  1151. title={Locale.Settings.Access.Iflytek.Endpoint.Title}
  1152. subTitle={
  1153. Locale.Settings.Access.Iflytek.Endpoint.SubTitle +
  1154. Iflytek.ExampleEndpoint
  1155. }
  1156. >
  1157. <input
  1158. aria-label={Locale.Settings.Access.Iflytek.Endpoint.Title}
  1159. type="text"
  1160. value={accessStore.iflytekUrl}
  1161. placeholder={Iflytek.ExampleEndpoint}
  1162. onChange={(e) =>
  1163. accessStore.update(
  1164. (access) => (access.iflytekUrl = e.currentTarget.value),
  1165. )
  1166. }
  1167. ></input>
  1168. </ListItem>
  1169. <ListItem
  1170. title={Locale.Settings.Access.Iflytek.ApiKey.Title}
  1171. subTitle={Locale.Settings.Access.Iflytek.ApiKey.SubTitle}
  1172. >
  1173. <PasswordInput
  1174. aria-label={Locale.Settings.Access.Iflytek.ApiKey.Title}
  1175. value={accessStore.iflytekApiKey}
  1176. type="text"
  1177. placeholder={Locale.Settings.Access.Iflytek.ApiKey.Placeholder}
  1178. onChange={(e) => {
  1179. accessStore.update(
  1180. (access) => (access.iflytekApiKey = e.currentTarget.value),
  1181. );
  1182. }}
  1183. />
  1184. </ListItem>
  1185. <ListItem
  1186. title={Locale.Settings.Access.Iflytek.ApiSecret.Title}
  1187. subTitle={Locale.Settings.Access.Iflytek.ApiSecret.SubTitle}
  1188. >
  1189. <PasswordInput
  1190. aria-label={Locale.Settings.Access.Iflytek.ApiSecret.Title}
  1191. value={accessStore.iflytekApiSecret}
  1192. type="text"
  1193. placeholder={Locale.Settings.Access.Iflytek.ApiSecret.Placeholder}
  1194. onChange={(e) => {
  1195. accessStore.update(
  1196. (access) => (access.iflytekApiSecret = e.currentTarget.value),
  1197. );
  1198. }}
  1199. />
  1200. </ListItem>
  1201. </>
  1202. );
  1203. return (
  1204. <ErrorBoundary>
  1205. <div className="window-header" data-tauri-drag-region>
  1206. <div className="window-header-title">
  1207. <div className="window-header-main-title">
  1208. {Locale.Settings.Title}
  1209. </div>
  1210. <div className="window-header-sub-title">
  1211. {Locale.Settings.SubTitle}
  1212. </div>
  1213. </div>
  1214. <div className="window-actions">
  1215. <div className="window-action-button"></div>
  1216. <div className="window-action-button"></div>
  1217. <div className="window-action-button">
  1218. <IconButton
  1219. aria={Locale.UI.Close}
  1220. icon={<CloseIcon />}
  1221. onClick={() => navigate(Path.Home)}
  1222. bordered
  1223. />
  1224. </div>
  1225. </div>
  1226. </div>
  1227. <div className={styles["settings"]}>
  1228. <List>
  1229. <ListItem title={Locale.Settings.Avatar}>
  1230. <Popover
  1231. onClose={() => setShowEmojiPicker(false)}
  1232. content={
  1233. <AvatarPicker
  1234. onEmojiClick={(avatar: string) => {
  1235. updateConfig((config) => (config.avatar = avatar));
  1236. setShowEmojiPicker(false);
  1237. }}
  1238. />
  1239. }
  1240. open={showEmojiPicker}
  1241. >
  1242. <div
  1243. aria-label={Locale.Settings.Avatar}
  1244. tabIndex={0}
  1245. className={styles.avatar}
  1246. onClick={() => {
  1247. setShowEmojiPicker(!showEmojiPicker);
  1248. }}
  1249. >
  1250. <Avatar avatar={config.avatar} />
  1251. </div>
  1252. </Popover>
  1253. </ListItem>
  1254. <ListItem
  1255. title={Locale.Settings.Update.Version(currentVersion ?? "unknown")}
  1256. subTitle={
  1257. checkingUpdate
  1258. ? Locale.Settings.Update.IsChecking
  1259. : hasNewVersion
  1260. ? Locale.Settings.Update.FoundUpdate(remoteId ?? "ERROR")
  1261. : Locale.Settings.Update.IsLatest
  1262. }
  1263. >
  1264. {checkingUpdate ? (
  1265. <LoadingIcon />
  1266. ) : hasNewVersion ? (
  1267. <Link href={updateUrl} target="_blank" className="link">
  1268. {Locale.Settings.Update.GoToUpdate}
  1269. </Link>
  1270. ) : (
  1271. <IconButton
  1272. icon={<ResetIcon></ResetIcon>}
  1273. text={Locale.Settings.Update.CheckUpdate}
  1274. onClick={() => checkUpdate(true)}
  1275. />
  1276. )}
  1277. </ListItem>
  1278. <ListItem title={Locale.Settings.SendKey}>
  1279. <Select
  1280. aria-label={Locale.Settings.SendKey}
  1281. value={config.submitKey}
  1282. onChange={(e) => {
  1283. updateConfig(
  1284. (config) =>
  1285. (config.submitKey = e.target.value as any as SubmitKey),
  1286. );
  1287. }}
  1288. >
  1289. {Object.values(SubmitKey).map((v) => (
  1290. <option value={v} key={v}>
  1291. {v}
  1292. </option>
  1293. ))}
  1294. </Select>
  1295. </ListItem>
  1296. <ListItem title={Locale.Settings.Theme}>
  1297. <Select
  1298. aria-label={Locale.Settings.Theme}
  1299. value={config.theme}
  1300. onChange={(e) => {
  1301. updateConfig(
  1302. (config) => (config.theme = e.target.value as any as Theme),
  1303. );
  1304. }}
  1305. >
  1306. {Object.values(Theme).map((v) => (
  1307. <option value={v} key={v}>
  1308. {v}
  1309. </option>
  1310. ))}
  1311. </Select>
  1312. </ListItem>
  1313. <ListItem title={Locale.Settings.Lang.Name}>
  1314. <Select
  1315. aria-label={Locale.Settings.Lang.Name}
  1316. value={getLang()}
  1317. onChange={(e) => {
  1318. changeLang(e.target.value as any);
  1319. }}
  1320. >
  1321. {AllLangs.map((lang) => (
  1322. <option value={lang} key={lang}>
  1323. {ALL_LANG_OPTIONS[lang]}
  1324. </option>
  1325. ))}
  1326. </Select>
  1327. </ListItem>
  1328. <ListItem
  1329. title={Locale.Settings.FontSize.Title}
  1330. subTitle={Locale.Settings.FontSize.SubTitle}
  1331. >
  1332. <InputRange
  1333. aria={Locale.Settings.FontSize.Title}
  1334. title={`${config.fontSize ?? 14}px`}
  1335. value={config.fontSize}
  1336. min="12"
  1337. max="40"
  1338. step="1"
  1339. onChange={(e) =>
  1340. updateConfig(
  1341. (config) =>
  1342. (config.fontSize = Number.parseInt(e.currentTarget.value)),
  1343. )
  1344. }
  1345. ></InputRange>
  1346. </ListItem>
  1347. <ListItem
  1348. title={Locale.Settings.FontFamily.Title}
  1349. subTitle={Locale.Settings.FontFamily.SubTitle}
  1350. >
  1351. <input
  1352. aria-label={Locale.Settings.FontFamily.Title}
  1353. type="text"
  1354. value={config.fontFamily}
  1355. placeholder={Locale.Settings.FontFamily.Placeholder}
  1356. onChange={(e) =>
  1357. updateConfig(
  1358. (config) => (config.fontFamily = e.currentTarget.value),
  1359. )
  1360. }
  1361. ></input>
  1362. </ListItem>
  1363. <ListItem
  1364. title={Locale.Settings.AutoGenerateTitle.Title}
  1365. subTitle={Locale.Settings.AutoGenerateTitle.SubTitle}
  1366. >
  1367. <input
  1368. aria-label={Locale.Settings.AutoGenerateTitle.Title}
  1369. type="checkbox"
  1370. checked={config.enableAutoGenerateTitle}
  1371. onChange={(e) =>
  1372. updateConfig(
  1373. (config) =>
  1374. (config.enableAutoGenerateTitle = e.currentTarget.checked),
  1375. )
  1376. }
  1377. ></input>
  1378. </ListItem>
  1379. <ListItem
  1380. title={Locale.Settings.SendPreviewBubble.Title}
  1381. subTitle={Locale.Settings.SendPreviewBubble.SubTitle}
  1382. >
  1383. <input
  1384. aria-label={Locale.Settings.SendPreviewBubble.Title}
  1385. type="checkbox"
  1386. checked={config.sendPreviewBubble}
  1387. onChange={(e) =>
  1388. updateConfig(
  1389. (config) =>
  1390. (config.sendPreviewBubble = e.currentTarget.checked),
  1391. )
  1392. }
  1393. ></input>
  1394. </ListItem>
  1395. </List>
  1396. <SyncItems />
  1397. <List>
  1398. <ListItem
  1399. title={Locale.Settings.Mask.Splash.Title}
  1400. subTitle={Locale.Settings.Mask.Splash.SubTitle}
  1401. >
  1402. <input
  1403. aria-label={Locale.Settings.Mask.Splash.Title}
  1404. type="checkbox"
  1405. checked={!config.dontShowMaskSplashScreen}
  1406. onChange={(e) =>
  1407. updateConfig(
  1408. (config) =>
  1409. (config.dontShowMaskSplashScreen =
  1410. !e.currentTarget.checked),
  1411. )
  1412. }
  1413. ></input>
  1414. </ListItem>
  1415. <ListItem
  1416. title={Locale.Settings.Mask.Builtin.Title}
  1417. subTitle={Locale.Settings.Mask.Builtin.SubTitle}
  1418. >
  1419. <input
  1420. aria-label={Locale.Settings.Mask.Builtin.Title}
  1421. type="checkbox"
  1422. checked={config.hideBuiltinMasks}
  1423. onChange={(e) =>
  1424. updateConfig(
  1425. (config) =>
  1426. (config.hideBuiltinMasks = e.currentTarget.checked),
  1427. )
  1428. }
  1429. ></input>
  1430. </ListItem>
  1431. </List>
  1432. <List>
  1433. <ListItem
  1434. title={Locale.Settings.Prompt.Disable.Title}
  1435. subTitle={Locale.Settings.Prompt.Disable.SubTitle}
  1436. >
  1437. <input
  1438. aria-label={Locale.Settings.Prompt.Disable.Title}
  1439. type="checkbox"
  1440. checked={config.disablePromptHint}
  1441. onChange={(e) =>
  1442. updateConfig(
  1443. (config) =>
  1444. (config.disablePromptHint = e.currentTarget.checked),
  1445. )
  1446. }
  1447. ></input>
  1448. </ListItem>
  1449. <ListItem
  1450. title={Locale.Settings.Prompt.List}
  1451. subTitle={Locale.Settings.Prompt.ListCount(
  1452. builtinCount,
  1453. customCount,
  1454. )}
  1455. >
  1456. <IconButton
  1457. aria={Locale.Settings.Prompt.List + Locale.Settings.Prompt.Edit}
  1458. icon={<EditIcon />}
  1459. text={Locale.Settings.Prompt.Edit}
  1460. onClick={() => setShowPromptModal(true)}
  1461. />
  1462. </ListItem>
  1463. </List>
  1464. <List id={SlotID.CustomModel}>
  1465. {accessCodeComponent}
  1466. {!accessStore.hideUserApiKey && (
  1467. <>
  1468. {useCustomConfigComponent}
  1469. {accessStore.useCustomConfig && (
  1470. <>
  1471. <ListItem
  1472. title={Locale.Settings.Access.Provider.Title}
  1473. subTitle={Locale.Settings.Access.Provider.SubTitle}
  1474. >
  1475. <Select
  1476. aria-label={Locale.Settings.Access.Provider.Title}
  1477. value={accessStore.provider}
  1478. onChange={(e) => {
  1479. accessStore.update(
  1480. (access) =>
  1481. (access.provider = e.target
  1482. .value as ServiceProvider),
  1483. );
  1484. }}
  1485. >
  1486. {Object.entries(ServiceProvider).map(([k, v]) => (
  1487. <option value={v} key={k}>
  1488. {k}
  1489. </option>
  1490. ))}
  1491. </Select>
  1492. </ListItem>
  1493. {openAIConfigComponent}
  1494. {azureConfigComponent}
  1495. {googleConfigComponent}
  1496. {anthropicConfigComponent}
  1497. {baiduConfigComponent}
  1498. {byteDanceConfigComponent}
  1499. {alibabaConfigComponent}
  1500. {tencentConfigComponent}
  1501. {moonshotConfigComponent}
  1502. {stabilityConfigComponent}
  1503. {lflytekConfigComponent}
  1504. </>
  1505. )}
  1506. </>
  1507. )}
  1508. {!shouldHideBalanceQuery && !clientConfig?.isApp ? (
  1509. <ListItem
  1510. title={Locale.Settings.Usage.Title}
  1511. subTitle={
  1512. showUsage
  1513. ? loadingUsage
  1514. ? Locale.Settings.Usage.IsChecking
  1515. : Locale.Settings.Usage.SubTitle(
  1516. usage?.used ?? "[?]",
  1517. usage?.subscription ?? "[?]",
  1518. )
  1519. : Locale.Settings.Usage.NoAccess
  1520. }
  1521. >
  1522. {!showUsage || loadingUsage ? (
  1523. <div />
  1524. ) : (
  1525. <IconButton
  1526. icon={<ResetIcon></ResetIcon>}
  1527. text={Locale.Settings.Usage.Check}
  1528. onClick={() => checkUsage(true)}
  1529. />
  1530. )}
  1531. </ListItem>
  1532. ) : null}
  1533. <ListItem
  1534. title={Locale.Settings.Access.CustomModel.Title}
  1535. subTitle={Locale.Settings.Access.CustomModel.SubTitle}
  1536. >
  1537. <input
  1538. aria-label={Locale.Settings.Access.CustomModel.Title}
  1539. type="text"
  1540. value={config.customModels}
  1541. placeholder="model1,model2,model3"
  1542. onChange={(e) =>
  1543. config.update(
  1544. (config) => (config.customModels = e.currentTarget.value),
  1545. )
  1546. }
  1547. ></input>
  1548. </ListItem>
  1549. </List>
  1550. <List>
  1551. <ModelConfigList
  1552. modelConfig={config.modelConfig}
  1553. updateConfig={(updater) => {
  1554. const modelConfig = { ...config.modelConfig };
  1555. updater(modelConfig);
  1556. config.update((config) => (config.modelConfig = modelConfig));
  1557. }}
  1558. />
  1559. </List>
  1560. {shouldShowPromptModal && (
  1561. <UserPromptModal onClose={() => setShowPromptModal(false)} />
  1562. )}
  1563. <DangerItems />
  1564. </div>
  1565. </ErrorBoundary>
  1566. );
  1567. }