settings.tsx 52 KB

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