settings.tsx 43 KB

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