settings.tsx 58 KB

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