settings.tsx 59 KB

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