settings.tsx 61 KB

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