settings.tsx 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690
  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. title={
  642. Locale.Settings.Access.SaasStart.Title +
  643. `${Locale.Settings.Access.SaasStart.Label}`
  644. }
  645. subTitle={Locale.Settings.Access.SaasStart.SubTitle}
  646. >
  647. <IconButton
  648. aria={
  649. Locale.Settings.Access.SaasStart.Title +
  650. Locale.Settings.Access.SaasStart.ChatNow
  651. }
  652. icon={<FireIcon />}
  653. text={Locale.Settings.Access.SaasStart.ChatNow}
  654. onClick={() => {
  655. window.location.href = SAAS_CHAT_URL;
  656. }}
  657. />
  658. </ListItem>
  659. );
  660. const useCustomConfigComponent = // Conditionally render the following ListItem based on clientConfig.isApp
  661. !clientConfig?.isApp && ( // only show if isApp is false
  662. <ListItem
  663. title={Locale.Settings.Access.CustomEndpoint.Title}
  664. subTitle={Locale.Settings.Access.CustomEndpoint.SubTitle}
  665. >
  666. <input
  667. aria-label={Locale.Settings.Access.CustomEndpoint.Title}
  668. type="checkbox"
  669. checked={accessStore.useCustomConfig}
  670. onChange={(e) =>
  671. accessStore.update(
  672. (access) => (access.useCustomConfig = e.currentTarget.checked),
  673. )
  674. }
  675. ></input>
  676. </ListItem>
  677. );
  678. const openAIConfigComponent = accessStore.provider ===
  679. ServiceProvider.OpenAI && (
  680. <>
  681. <ListItem
  682. title={Locale.Settings.Access.OpenAI.Endpoint.Title}
  683. subTitle={Locale.Settings.Access.OpenAI.Endpoint.SubTitle}
  684. >
  685. <input
  686. aria-label={Locale.Settings.Access.OpenAI.Endpoint.Title}
  687. type="text"
  688. value={accessStore.openaiUrl}
  689. placeholder={OPENAI_BASE_URL}
  690. onChange={(e) =>
  691. accessStore.update(
  692. (access) => (access.openaiUrl = e.currentTarget.value),
  693. )
  694. }
  695. ></input>
  696. </ListItem>
  697. <ListItem
  698. title={Locale.Settings.Access.OpenAI.ApiKey.Title}
  699. subTitle={Locale.Settings.Access.OpenAI.ApiKey.SubTitle}
  700. >
  701. <PasswordInput
  702. aria={Locale.Settings.ShowPassword}
  703. aria-label={Locale.Settings.Access.OpenAI.ApiKey.Title}
  704. value={accessStore.openaiApiKey}
  705. type="text"
  706. placeholder={Locale.Settings.Access.OpenAI.ApiKey.Placeholder}
  707. onChange={(e) => {
  708. accessStore.update(
  709. (access) => (access.openaiApiKey = e.currentTarget.value),
  710. );
  711. }}
  712. />
  713. </ListItem>
  714. </>
  715. );
  716. const azureConfigComponent = accessStore.provider ===
  717. ServiceProvider.Azure && (
  718. <>
  719. <ListItem
  720. title={Locale.Settings.Access.Azure.Endpoint.Title}
  721. subTitle={
  722. Locale.Settings.Access.Azure.Endpoint.SubTitle + Azure.ExampleEndpoint
  723. }
  724. >
  725. <input
  726. aria-label={Locale.Settings.Access.Azure.Endpoint.Title}
  727. type="text"
  728. value={accessStore.azureUrl}
  729. placeholder={Azure.ExampleEndpoint}
  730. onChange={(e) =>
  731. accessStore.update(
  732. (access) => (access.azureUrl = e.currentTarget.value),
  733. )
  734. }
  735. ></input>
  736. </ListItem>
  737. <ListItem
  738. title={Locale.Settings.Access.Azure.ApiKey.Title}
  739. subTitle={Locale.Settings.Access.Azure.ApiKey.SubTitle}
  740. >
  741. <PasswordInput
  742. aria-label={Locale.Settings.Access.Azure.ApiKey.Title}
  743. value={accessStore.azureApiKey}
  744. type="text"
  745. placeholder={Locale.Settings.Access.Azure.ApiKey.Placeholder}
  746. onChange={(e) => {
  747. accessStore.update(
  748. (access) => (access.azureApiKey = e.currentTarget.value),
  749. );
  750. }}
  751. />
  752. </ListItem>
  753. <ListItem
  754. title={Locale.Settings.Access.Azure.ApiVerion.Title}
  755. subTitle={Locale.Settings.Access.Azure.ApiVerion.SubTitle}
  756. >
  757. <input
  758. aria-label={Locale.Settings.Access.Azure.ApiVerion.Title}
  759. type="text"
  760. value={accessStore.azureApiVersion}
  761. placeholder="2023-08-01-preview"
  762. onChange={(e) =>
  763. accessStore.update(
  764. (access) => (access.azureApiVersion = e.currentTarget.value),
  765. )
  766. }
  767. ></input>
  768. </ListItem>
  769. </>
  770. );
  771. const googleConfigComponent = accessStore.provider ===
  772. ServiceProvider.Google && (
  773. <>
  774. <ListItem
  775. title={Locale.Settings.Access.Google.Endpoint.Title}
  776. subTitle={
  777. Locale.Settings.Access.Google.Endpoint.SubTitle +
  778. Google.ExampleEndpoint
  779. }
  780. >
  781. <input
  782. aria-label={Locale.Settings.Access.Google.Endpoint.Title}
  783. type="text"
  784. value={accessStore.googleUrl}
  785. placeholder={Google.ExampleEndpoint}
  786. onChange={(e) =>
  787. accessStore.update(
  788. (access) => (access.googleUrl = e.currentTarget.value),
  789. )
  790. }
  791. ></input>
  792. </ListItem>
  793. <ListItem
  794. title={Locale.Settings.Access.Google.ApiKey.Title}
  795. subTitle={Locale.Settings.Access.Google.ApiKey.SubTitle}
  796. >
  797. <PasswordInput
  798. aria-label={Locale.Settings.Access.Google.ApiKey.Title}
  799. value={accessStore.googleApiKey}
  800. type="text"
  801. placeholder={Locale.Settings.Access.Google.ApiKey.Placeholder}
  802. onChange={(e) => {
  803. accessStore.update(
  804. (access) => (access.googleApiKey = e.currentTarget.value),
  805. );
  806. }}
  807. />
  808. </ListItem>
  809. <ListItem
  810. title={Locale.Settings.Access.Google.ApiVersion.Title}
  811. subTitle={Locale.Settings.Access.Google.ApiVersion.SubTitle}
  812. >
  813. <input
  814. aria-label={Locale.Settings.Access.Google.ApiVersion.Title}
  815. type="text"
  816. value={accessStore.googleApiVersion}
  817. placeholder="2023-08-01-preview"
  818. onChange={(e) =>
  819. accessStore.update(
  820. (access) => (access.googleApiVersion = e.currentTarget.value),
  821. )
  822. }
  823. ></input>
  824. </ListItem>
  825. <ListItem
  826. title={Locale.Settings.Access.Google.GoogleSafetySettings.Title}
  827. subTitle={Locale.Settings.Access.Google.GoogleSafetySettings.SubTitle}
  828. >
  829. <Select
  830. aria-label={Locale.Settings.Access.Google.GoogleSafetySettings.Title}
  831. value={accessStore.googleSafetySettings}
  832. onChange={(e) => {
  833. accessStore.update(
  834. (access) =>
  835. (access.googleSafetySettings = e.target
  836. .value as GoogleSafetySettingsThreshold),
  837. );
  838. }}
  839. >
  840. {Object.entries(GoogleSafetySettingsThreshold).map(([k, v]) => (
  841. <option value={v} key={k}>
  842. {k}
  843. </option>
  844. ))}
  845. </Select>
  846. </ListItem>
  847. </>
  848. );
  849. const anthropicConfigComponent = accessStore.provider ===
  850. ServiceProvider.Anthropic && (
  851. <>
  852. <ListItem
  853. title={Locale.Settings.Access.Anthropic.Endpoint.Title}
  854. subTitle={
  855. Locale.Settings.Access.Anthropic.Endpoint.SubTitle +
  856. Anthropic.ExampleEndpoint
  857. }
  858. >
  859. <input
  860. aria-label={Locale.Settings.Access.Anthropic.Endpoint.Title}
  861. type="text"
  862. value={accessStore.anthropicUrl}
  863. placeholder={Anthropic.ExampleEndpoint}
  864. onChange={(e) =>
  865. accessStore.update(
  866. (access) => (access.anthropicUrl = e.currentTarget.value),
  867. )
  868. }
  869. ></input>
  870. </ListItem>
  871. <ListItem
  872. title={Locale.Settings.Access.Anthropic.ApiKey.Title}
  873. subTitle={Locale.Settings.Access.Anthropic.ApiKey.SubTitle}
  874. >
  875. <PasswordInput
  876. aria-label={Locale.Settings.Access.Anthropic.ApiKey.Title}
  877. value={accessStore.anthropicApiKey}
  878. type="text"
  879. placeholder={Locale.Settings.Access.Anthropic.ApiKey.Placeholder}
  880. onChange={(e) => {
  881. accessStore.update(
  882. (access) => (access.anthropicApiKey = e.currentTarget.value),
  883. );
  884. }}
  885. />
  886. </ListItem>
  887. <ListItem
  888. title={Locale.Settings.Access.Anthropic.ApiVerion.Title}
  889. subTitle={Locale.Settings.Access.Anthropic.ApiVerion.SubTitle}
  890. >
  891. <input
  892. aria-label={Locale.Settings.Access.Anthropic.ApiVerion.Title}
  893. type="text"
  894. value={accessStore.anthropicApiVersion}
  895. placeholder={Anthropic.Vision}
  896. onChange={(e) =>
  897. accessStore.update(
  898. (access) => (access.anthropicApiVersion = e.currentTarget.value),
  899. )
  900. }
  901. ></input>
  902. </ListItem>
  903. </>
  904. );
  905. const baiduConfigComponent = accessStore.provider ===
  906. ServiceProvider.Baidu && (
  907. <>
  908. <ListItem
  909. title={Locale.Settings.Access.Baidu.Endpoint.Title}
  910. subTitle={Locale.Settings.Access.Baidu.Endpoint.SubTitle}
  911. >
  912. <input
  913. aria-label={Locale.Settings.Access.Baidu.Endpoint.Title}
  914. type="text"
  915. value={accessStore.baiduUrl}
  916. placeholder={Baidu.ExampleEndpoint}
  917. onChange={(e) =>
  918. accessStore.update(
  919. (access) => (access.baiduUrl = e.currentTarget.value),
  920. )
  921. }
  922. ></input>
  923. </ListItem>
  924. <ListItem
  925. title={Locale.Settings.Access.Baidu.ApiKey.Title}
  926. subTitle={Locale.Settings.Access.Baidu.ApiKey.SubTitle}
  927. >
  928. <PasswordInput
  929. aria-label={Locale.Settings.Access.Baidu.ApiKey.Title}
  930. value={accessStore.baiduApiKey}
  931. type="text"
  932. placeholder={Locale.Settings.Access.Baidu.ApiKey.Placeholder}
  933. onChange={(e) => {
  934. accessStore.update(
  935. (access) => (access.baiduApiKey = e.currentTarget.value),
  936. );
  937. }}
  938. />
  939. </ListItem>
  940. <ListItem
  941. title={Locale.Settings.Access.Baidu.SecretKey.Title}
  942. subTitle={Locale.Settings.Access.Baidu.SecretKey.SubTitle}
  943. >
  944. <PasswordInput
  945. aria-label={Locale.Settings.Access.Baidu.SecretKey.Title}
  946. value={accessStore.baiduSecretKey}
  947. type="text"
  948. placeholder={Locale.Settings.Access.Baidu.SecretKey.Placeholder}
  949. onChange={(e) => {
  950. accessStore.update(
  951. (access) => (access.baiduSecretKey = e.currentTarget.value),
  952. );
  953. }}
  954. />
  955. </ListItem>
  956. </>
  957. );
  958. const tencentConfigComponent = accessStore.provider ===
  959. ServiceProvider.Tencent && (
  960. <>
  961. <ListItem
  962. title={Locale.Settings.Access.Tencent.Endpoint.Title}
  963. subTitle={Locale.Settings.Access.Tencent.Endpoint.SubTitle}
  964. >
  965. <input
  966. aria-label={Locale.Settings.Access.Tencent.Endpoint.Title}
  967. type="text"
  968. value={accessStore.tencentUrl}
  969. placeholder={Tencent.ExampleEndpoint}
  970. onChange={(e) =>
  971. accessStore.update(
  972. (access) => (access.tencentUrl = e.currentTarget.value),
  973. )
  974. }
  975. ></input>
  976. </ListItem>
  977. <ListItem
  978. title={Locale.Settings.Access.Tencent.ApiKey.Title}
  979. subTitle={Locale.Settings.Access.Tencent.ApiKey.SubTitle}
  980. >
  981. <PasswordInput
  982. aria-label={Locale.Settings.Access.Tencent.ApiKey.Title}
  983. value={accessStore.tencentSecretId}
  984. type="text"
  985. placeholder={Locale.Settings.Access.Tencent.ApiKey.Placeholder}
  986. onChange={(e) => {
  987. accessStore.update(
  988. (access) => (access.tencentSecretId = e.currentTarget.value),
  989. );
  990. }}
  991. />
  992. </ListItem>
  993. <ListItem
  994. title={Locale.Settings.Access.Tencent.SecretKey.Title}
  995. subTitle={Locale.Settings.Access.Tencent.SecretKey.SubTitle}
  996. >
  997. <PasswordInput
  998. aria-label={Locale.Settings.Access.Tencent.SecretKey.Title}
  999. value={accessStore.tencentSecretKey}
  1000. type="text"
  1001. placeholder={Locale.Settings.Access.Tencent.SecretKey.Placeholder}
  1002. onChange={(e) => {
  1003. accessStore.update(
  1004. (access) => (access.tencentSecretKey = e.currentTarget.value),
  1005. );
  1006. }}
  1007. />
  1008. </ListItem>
  1009. </>
  1010. );
  1011. const byteDanceConfigComponent = accessStore.provider ===
  1012. ServiceProvider.ByteDance && (
  1013. <>
  1014. <ListItem
  1015. title={Locale.Settings.Access.ByteDance.Endpoint.Title}
  1016. subTitle={
  1017. Locale.Settings.Access.ByteDance.Endpoint.SubTitle +
  1018. ByteDance.ExampleEndpoint
  1019. }
  1020. >
  1021. <input
  1022. aria-label={Locale.Settings.Access.ByteDance.Endpoint.Title}
  1023. type="text"
  1024. value={accessStore.bytedanceUrl}
  1025. placeholder={ByteDance.ExampleEndpoint}
  1026. onChange={(e) =>
  1027. accessStore.update(
  1028. (access) => (access.bytedanceUrl = e.currentTarget.value),
  1029. )
  1030. }
  1031. ></input>
  1032. </ListItem>
  1033. <ListItem
  1034. title={Locale.Settings.Access.ByteDance.ApiKey.Title}
  1035. subTitle={Locale.Settings.Access.ByteDance.ApiKey.SubTitle}
  1036. >
  1037. <PasswordInput
  1038. aria-label={Locale.Settings.Access.ByteDance.ApiKey.Title}
  1039. value={accessStore.bytedanceApiKey}
  1040. type="text"
  1041. placeholder={Locale.Settings.Access.ByteDance.ApiKey.Placeholder}
  1042. onChange={(e) => {
  1043. accessStore.update(
  1044. (access) => (access.bytedanceApiKey = e.currentTarget.value),
  1045. );
  1046. }}
  1047. />
  1048. </ListItem>
  1049. </>
  1050. );
  1051. const alibabaConfigComponent = accessStore.provider ===
  1052. ServiceProvider.Alibaba && (
  1053. <>
  1054. <ListItem
  1055. title={Locale.Settings.Access.Alibaba.Endpoint.Title}
  1056. subTitle={
  1057. Locale.Settings.Access.Alibaba.Endpoint.SubTitle +
  1058. Alibaba.ExampleEndpoint
  1059. }
  1060. >
  1061. <input
  1062. aria-label={Locale.Settings.Access.Alibaba.Endpoint.Title}
  1063. type="text"
  1064. value={accessStore.alibabaUrl}
  1065. placeholder={Alibaba.ExampleEndpoint}
  1066. onChange={(e) =>
  1067. accessStore.update(
  1068. (access) => (access.alibabaUrl = e.currentTarget.value),
  1069. )
  1070. }
  1071. ></input>
  1072. </ListItem>
  1073. <ListItem
  1074. title={Locale.Settings.Access.Alibaba.ApiKey.Title}
  1075. subTitle={Locale.Settings.Access.Alibaba.ApiKey.SubTitle}
  1076. >
  1077. <PasswordInput
  1078. aria-label={Locale.Settings.Access.Alibaba.ApiKey.Title}
  1079. value={accessStore.alibabaApiKey}
  1080. type="text"
  1081. placeholder={Locale.Settings.Access.Alibaba.ApiKey.Placeholder}
  1082. onChange={(e) => {
  1083. accessStore.update(
  1084. (access) => (access.alibabaApiKey = e.currentTarget.value),
  1085. );
  1086. }}
  1087. />
  1088. </ListItem>
  1089. </>
  1090. );
  1091. const moonshotConfigComponent = accessStore.provider ===
  1092. ServiceProvider.Moonshot && (
  1093. <>
  1094. <ListItem
  1095. title={Locale.Settings.Access.Moonshot.Endpoint.Title}
  1096. subTitle={
  1097. Locale.Settings.Access.Moonshot.Endpoint.SubTitle +
  1098. Moonshot.ExampleEndpoint
  1099. }
  1100. >
  1101. <input
  1102. aria-label={Locale.Settings.Access.Moonshot.Endpoint.Title}
  1103. type="text"
  1104. value={accessStore.moonshotUrl}
  1105. placeholder={Moonshot.ExampleEndpoint}
  1106. onChange={(e) =>
  1107. accessStore.update(
  1108. (access) => (access.moonshotUrl = e.currentTarget.value),
  1109. )
  1110. }
  1111. ></input>
  1112. </ListItem>
  1113. <ListItem
  1114. title={Locale.Settings.Access.Moonshot.ApiKey.Title}
  1115. subTitle={Locale.Settings.Access.Moonshot.ApiKey.SubTitle}
  1116. >
  1117. <PasswordInput
  1118. aria-label={Locale.Settings.Access.Moonshot.ApiKey.Title}
  1119. value={accessStore.moonshotApiKey}
  1120. type="text"
  1121. placeholder={Locale.Settings.Access.Moonshot.ApiKey.Placeholder}
  1122. onChange={(e) => {
  1123. accessStore.update(
  1124. (access) => (access.moonshotApiKey = e.currentTarget.value),
  1125. );
  1126. }}
  1127. />
  1128. </ListItem>
  1129. </>
  1130. );
  1131. const stabilityConfigComponent = accessStore.provider ===
  1132. ServiceProvider.Stability && (
  1133. <>
  1134. <ListItem
  1135. title={Locale.Settings.Access.Stability.Endpoint.Title}
  1136. subTitle={
  1137. Locale.Settings.Access.Stability.Endpoint.SubTitle +
  1138. Stability.ExampleEndpoint
  1139. }
  1140. >
  1141. <input
  1142. aria-label={Locale.Settings.Access.Stability.Endpoint.Title}
  1143. type="text"
  1144. value={accessStore.stabilityUrl}
  1145. placeholder={Stability.ExampleEndpoint}
  1146. onChange={(e) =>
  1147. accessStore.update(
  1148. (access) => (access.stabilityUrl = e.currentTarget.value),
  1149. )
  1150. }
  1151. ></input>
  1152. </ListItem>
  1153. <ListItem
  1154. title={Locale.Settings.Access.Stability.ApiKey.Title}
  1155. subTitle={Locale.Settings.Access.Stability.ApiKey.SubTitle}
  1156. >
  1157. <PasswordInput
  1158. aria-label={Locale.Settings.Access.Stability.ApiKey.Title}
  1159. value={accessStore.stabilityApiKey}
  1160. type="text"
  1161. placeholder={Locale.Settings.Access.Stability.ApiKey.Placeholder}
  1162. onChange={(e) => {
  1163. accessStore.update(
  1164. (access) => (access.stabilityApiKey = e.currentTarget.value),
  1165. );
  1166. }}
  1167. />
  1168. </ListItem>
  1169. </>
  1170. );
  1171. const lflytekConfigComponent = accessStore.provider ===
  1172. ServiceProvider.Iflytek && (
  1173. <>
  1174. <ListItem
  1175. title={Locale.Settings.Access.Iflytek.Endpoint.Title}
  1176. subTitle={
  1177. Locale.Settings.Access.Iflytek.Endpoint.SubTitle +
  1178. Iflytek.ExampleEndpoint
  1179. }
  1180. >
  1181. <input
  1182. aria-label={Locale.Settings.Access.Iflytek.Endpoint.Title}
  1183. type="text"
  1184. value={accessStore.iflytekUrl}
  1185. placeholder={Iflytek.ExampleEndpoint}
  1186. onChange={(e) =>
  1187. accessStore.update(
  1188. (access) => (access.iflytekUrl = e.currentTarget.value),
  1189. )
  1190. }
  1191. ></input>
  1192. </ListItem>
  1193. <ListItem
  1194. title={Locale.Settings.Access.Iflytek.ApiKey.Title}
  1195. subTitle={Locale.Settings.Access.Iflytek.ApiKey.SubTitle}
  1196. >
  1197. <PasswordInput
  1198. aria-label={Locale.Settings.Access.Iflytek.ApiKey.Title}
  1199. value={accessStore.iflytekApiKey}
  1200. type="text"
  1201. placeholder={Locale.Settings.Access.Iflytek.ApiKey.Placeholder}
  1202. onChange={(e) => {
  1203. accessStore.update(
  1204. (access) => (access.iflytekApiKey = e.currentTarget.value),
  1205. );
  1206. }}
  1207. />
  1208. </ListItem>
  1209. <ListItem
  1210. title={Locale.Settings.Access.Iflytek.ApiSecret.Title}
  1211. subTitle={Locale.Settings.Access.Iflytek.ApiSecret.SubTitle}
  1212. >
  1213. <PasswordInput
  1214. aria-label={Locale.Settings.Access.Iflytek.ApiSecret.Title}
  1215. value={accessStore.iflytekApiSecret}
  1216. type="text"
  1217. placeholder={Locale.Settings.Access.Iflytek.ApiSecret.Placeholder}
  1218. onChange={(e) => {
  1219. accessStore.update(
  1220. (access) => (access.iflytekApiSecret = e.currentTarget.value),
  1221. );
  1222. }}
  1223. />
  1224. </ListItem>
  1225. </>
  1226. );
  1227. return (
  1228. <ErrorBoundary>
  1229. <div className="window-header" data-tauri-drag-region>
  1230. <div className="window-header-title">
  1231. <div className="window-header-main-title">
  1232. {Locale.Settings.Title}
  1233. </div>
  1234. <div className="window-header-sub-title">
  1235. {Locale.Settings.SubTitle}
  1236. </div>
  1237. </div>
  1238. <div className="window-actions">
  1239. <div className="window-action-button"></div>
  1240. <div className="window-action-button"></div>
  1241. <div className="window-action-button">
  1242. <IconButton
  1243. aria={Locale.UI.Close}
  1244. icon={<CloseIcon />}
  1245. onClick={() => navigate(Path.Home)}
  1246. bordered
  1247. />
  1248. </div>
  1249. </div>
  1250. </div>
  1251. <div className={styles["settings"]}>
  1252. <List>
  1253. <ListItem title={Locale.Settings.Avatar}>
  1254. <Popover
  1255. onClose={() => setShowEmojiPicker(false)}
  1256. content={
  1257. <AvatarPicker
  1258. onEmojiClick={(avatar: string) => {
  1259. updateConfig((config) => (config.avatar = avatar));
  1260. setShowEmojiPicker(false);
  1261. }}
  1262. />
  1263. }
  1264. open={showEmojiPicker}
  1265. >
  1266. <div
  1267. aria-label={Locale.Settings.Avatar}
  1268. tabIndex={0}
  1269. className={styles.avatar}
  1270. onClick={() => {
  1271. setShowEmojiPicker(!showEmojiPicker);
  1272. }}
  1273. >
  1274. <Avatar avatar={config.avatar} />
  1275. </div>
  1276. </Popover>
  1277. </ListItem>
  1278. <ListItem
  1279. title={Locale.Settings.Update.Version(currentVersion ?? "unknown")}
  1280. subTitle={
  1281. checkingUpdate
  1282. ? Locale.Settings.Update.IsChecking
  1283. : hasNewVersion
  1284. ? Locale.Settings.Update.FoundUpdate(remoteId ?? "ERROR")
  1285. : Locale.Settings.Update.IsLatest
  1286. }
  1287. >
  1288. {checkingUpdate ? (
  1289. <LoadingIcon />
  1290. ) : hasNewVersion ? (
  1291. <Link href={updateUrl} target="_blank" className="link">
  1292. {Locale.Settings.Update.GoToUpdate}
  1293. </Link>
  1294. ) : (
  1295. <IconButton
  1296. icon={<ResetIcon></ResetIcon>}
  1297. text={Locale.Settings.Update.CheckUpdate}
  1298. onClick={() => checkUpdate(true)}
  1299. />
  1300. )}
  1301. </ListItem>
  1302. <ListItem title={Locale.Settings.SendKey}>
  1303. <Select
  1304. aria-label={Locale.Settings.SendKey}
  1305. value={config.submitKey}
  1306. onChange={(e) => {
  1307. updateConfig(
  1308. (config) =>
  1309. (config.submitKey = e.target.value as any as SubmitKey),
  1310. );
  1311. }}
  1312. >
  1313. {Object.values(SubmitKey).map((v) => (
  1314. <option value={v} key={v}>
  1315. {v}
  1316. </option>
  1317. ))}
  1318. </Select>
  1319. </ListItem>
  1320. <ListItem title={Locale.Settings.Theme}>
  1321. <Select
  1322. aria-label={Locale.Settings.Theme}
  1323. value={config.theme}
  1324. onChange={(e) => {
  1325. updateConfig(
  1326. (config) => (config.theme = e.target.value as any as Theme),
  1327. );
  1328. }}
  1329. >
  1330. {Object.values(Theme).map((v) => (
  1331. <option value={v} key={v}>
  1332. {v}
  1333. </option>
  1334. ))}
  1335. </Select>
  1336. </ListItem>
  1337. <ListItem title={Locale.Settings.Lang.Name}>
  1338. <Select
  1339. aria-label={Locale.Settings.Lang.Name}
  1340. value={getLang()}
  1341. onChange={(e) => {
  1342. changeLang(e.target.value as any);
  1343. }}
  1344. >
  1345. {AllLangs.map((lang) => (
  1346. <option value={lang} key={lang}>
  1347. {ALL_LANG_OPTIONS[lang]}
  1348. </option>
  1349. ))}
  1350. </Select>
  1351. </ListItem>
  1352. <ListItem
  1353. title={Locale.Settings.FontSize.Title}
  1354. subTitle={Locale.Settings.FontSize.SubTitle}
  1355. >
  1356. <InputRange
  1357. aria={Locale.Settings.FontSize.Title}
  1358. title={`${config.fontSize ?? 14}px`}
  1359. value={config.fontSize}
  1360. min="12"
  1361. max="40"
  1362. step="1"
  1363. onChange={(e) =>
  1364. updateConfig(
  1365. (config) =>
  1366. (config.fontSize = Number.parseInt(e.currentTarget.value)),
  1367. )
  1368. }
  1369. ></InputRange>
  1370. </ListItem>
  1371. <ListItem
  1372. title={Locale.Settings.FontFamily.Title}
  1373. subTitle={Locale.Settings.FontFamily.SubTitle}
  1374. >
  1375. <input
  1376. aria-label={Locale.Settings.FontFamily.Title}
  1377. type="text"
  1378. value={config.fontFamily}
  1379. placeholder={Locale.Settings.FontFamily.Placeholder}
  1380. onChange={(e) =>
  1381. updateConfig(
  1382. (config) => (config.fontFamily = e.currentTarget.value),
  1383. )
  1384. }
  1385. ></input>
  1386. </ListItem>
  1387. <ListItem
  1388. title={Locale.Settings.AutoGenerateTitle.Title}
  1389. subTitle={Locale.Settings.AutoGenerateTitle.SubTitle}
  1390. >
  1391. <input
  1392. aria-label={Locale.Settings.AutoGenerateTitle.Title}
  1393. type="checkbox"
  1394. checked={config.enableAutoGenerateTitle}
  1395. onChange={(e) =>
  1396. updateConfig(
  1397. (config) =>
  1398. (config.enableAutoGenerateTitle = e.currentTarget.checked),
  1399. )
  1400. }
  1401. ></input>
  1402. </ListItem>
  1403. <ListItem
  1404. title={Locale.Settings.SendPreviewBubble.Title}
  1405. subTitle={Locale.Settings.SendPreviewBubble.SubTitle}
  1406. >
  1407. <input
  1408. aria-label={Locale.Settings.SendPreviewBubble.Title}
  1409. type="checkbox"
  1410. checked={config.sendPreviewBubble}
  1411. onChange={(e) =>
  1412. updateConfig(
  1413. (config) =>
  1414. (config.sendPreviewBubble = e.currentTarget.checked),
  1415. )
  1416. }
  1417. ></input>
  1418. </ListItem>
  1419. </List>
  1420. <SyncItems />
  1421. <List>
  1422. <ListItem
  1423. title={Locale.Settings.Mask.Splash.Title}
  1424. subTitle={Locale.Settings.Mask.Splash.SubTitle}
  1425. >
  1426. <input
  1427. aria-label={Locale.Settings.Mask.Splash.Title}
  1428. type="checkbox"
  1429. checked={!config.dontShowMaskSplashScreen}
  1430. onChange={(e) =>
  1431. updateConfig(
  1432. (config) =>
  1433. (config.dontShowMaskSplashScreen =
  1434. !e.currentTarget.checked),
  1435. )
  1436. }
  1437. ></input>
  1438. </ListItem>
  1439. <ListItem
  1440. title={Locale.Settings.Mask.Builtin.Title}
  1441. subTitle={Locale.Settings.Mask.Builtin.SubTitle}
  1442. >
  1443. <input
  1444. aria-label={Locale.Settings.Mask.Builtin.Title}
  1445. type="checkbox"
  1446. checked={config.hideBuiltinMasks}
  1447. onChange={(e) =>
  1448. updateConfig(
  1449. (config) =>
  1450. (config.hideBuiltinMasks = e.currentTarget.checked),
  1451. )
  1452. }
  1453. ></input>
  1454. </ListItem>
  1455. </List>
  1456. <List>
  1457. <ListItem
  1458. title={Locale.Settings.Prompt.Disable.Title}
  1459. subTitle={Locale.Settings.Prompt.Disable.SubTitle}
  1460. >
  1461. <input
  1462. aria-label={Locale.Settings.Prompt.Disable.Title}
  1463. type="checkbox"
  1464. checked={config.disablePromptHint}
  1465. onChange={(e) =>
  1466. updateConfig(
  1467. (config) =>
  1468. (config.disablePromptHint = e.currentTarget.checked),
  1469. )
  1470. }
  1471. ></input>
  1472. </ListItem>
  1473. <ListItem
  1474. title={Locale.Settings.Prompt.List}
  1475. subTitle={Locale.Settings.Prompt.ListCount(
  1476. builtinCount,
  1477. customCount,
  1478. )}
  1479. >
  1480. <IconButton
  1481. aria={Locale.Settings.Prompt.List + Locale.Settings.Prompt.Edit}
  1482. icon={<EditIcon />}
  1483. text={Locale.Settings.Prompt.Edit}
  1484. onClick={() => setShowPromptModal(true)}
  1485. />
  1486. </ListItem>
  1487. </List>
  1488. <List id={SlotID.CustomModel}>
  1489. {saasStartComponent}
  1490. {accessCodeComponent}
  1491. {!accessStore.hideUserApiKey && (
  1492. <>
  1493. {useCustomConfigComponent}
  1494. {accessStore.useCustomConfig && (
  1495. <>
  1496. <ListItem
  1497. title={Locale.Settings.Access.Provider.Title}
  1498. subTitle={Locale.Settings.Access.Provider.SubTitle}
  1499. >
  1500. <Select
  1501. aria-label={Locale.Settings.Access.Provider.Title}
  1502. value={accessStore.provider}
  1503. onChange={(e) => {
  1504. accessStore.update(
  1505. (access) =>
  1506. (access.provider = e.target
  1507. .value as ServiceProvider),
  1508. );
  1509. }}
  1510. >
  1511. {Object.entries(ServiceProvider).map(([k, v]) => (
  1512. <option value={v} key={k}>
  1513. {k}
  1514. </option>
  1515. ))}
  1516. </Select>
  1517. </ListItem>
  1518. {openAIConfigComponent}
  1519. {azureConfigComponent}
  1520. {googleConfigComponent}
  1521. {anthropicConfigComponent}
  1522. {baiduConfigComponent}
  1523. {byteDanceConfigComponent}
  1524. {alibabaConfigComponent}
  1525. {tencentConfigComponent}
  1526. {moonshotConfigComponent}
  1527. {stabilityConfigComponent}
  1528. {lflytekConfigComponent}
  1529. </>
  1530. )}
  1531. </>
  1532. )}
  1533. {!shouldHideBalanceQuery && !clientConfig?.isApp ? (
  1534. <ListItem
  1535. title={Locale.Settings.Usage.Title}
  1536. subTitle={
  1537. showUsage
  1538. ? loadingUsage
  1539. ? Locale.Settings.Usage.IsChecking
  1540. : Locale.Settings.Usage.SubTitle(
  1541. usage?.used ?? "[?]",
  1542. usage?.subscription ?? "[?]",
  1543. )
  1544. : Locale.Settings.Usage.NoAccess
  1545. }
  1546. >
  1547. {!showUsage || loadingUsage ? (
  1548. <div />
  1549. ) : (
  1550. <IconButton
  1551. icon={<ResetIcon></ResetIcon>}
  1552. text={Locale.Settings.Usage.Check}
  1553. onClick={() => checkUsage(true)}
  1554. />
  1555. )}
  1556. </ListItem>
  1557. ) : null}
  1558. <ListItem
  1559. title={Locale.Settings.Access.CustomModel.Title}
  1560. subTitle={Locale.Settings.Access.CustomModel.SubTitle}
  1561. >
  1562. <input
  1563. aria-label={Locale.Settings.Access.CustomModel.Title}
  1564. type="text"
  1565. value={config.customModels}
  1566. placeholder="model1,model2,model3"
  1567. onChange={(e) =>
  1568. config.update(
  1569. (config) => (config.customModels = e.currentTarget.value),
  1570. )
  1571. }
  1572. ></input>
  1573. </ListItem>
  1574. </List>
  1575. <List>
  1576. <ModelConfigList
  1577. modelConfig={config.modelConfig}
  1578. updateConfig={(updater) => {
  1579. const modelConfig = { ...config.modelConfig };
  1580. updater(modelConfig);
  1581. config.update((config) => (config.modelConfig = modelConfig));
  1582. }}
  1583. />
  1584. </List>
  1585. {shouldShowPromptModal && (
  1586. <UserPromptModal onClose={() => setShowPromptModal(false)} />
  1587. )}
  1588. <List>
  1589. <TTSConfigList
  1590. ttsConfig={config.ttsConfig}
  1591. updateConfig={(updater) => {
  1592. const ttsConfig = { ...config.ttsConfig };
  1593. updater(ttsConfig);
  1594. config.update((config) => (config.ttsConfig = ttsConfig));
  1595. }}
  1596. />
  1597. </List>
  1598. <DangerItems />
  1599. </div>
  1600. </ErrorBoundary>
  1601. );
  1602. }