From 422e53e6079c596538e9baef51b87c5e2d609384 Mon Sep 17 00:00:00 2001 From: Xuan Son Nguyen Date: Fri, 7 Feb 2025 22:53:34 +0100 Subject: [PATCH] redo Settings modal UI --- examples/server/webui/package-lock.json | 10 + examples/server/webui/package.json | 1 + .../webui/src/components/SettingDialog.tsx | 538 ++++++++++++------ 3 files changed, 364 insertions(+), 185 deletions(-) diff --git a/examples/server/webui/package-lock.json b/examples/server/webui/package-lock.json index e69fd2aa5..c6c5de3c0 100644 --- a/examples/server/webui/package-lock.json +++ b/examples/server/webui/package-lock.json @@ -8,6 +8,7 @@ "name": "webui", "version": "0.0.0", "dependencies": { + "@heroicons/react": "^2.2.0", "@sec-ant/readable-stream": "^0.6.0", "@vscode/markdown-it-katex": "^1.1.1", "autoprefixer": "^10.4.20", @@ -902,6 +903,15 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@heroicons/react": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz", + "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16 || ^19.0.0-rc" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", diff --git a/examples/server/webui/package.json b/examples/server/webui/package.json index f3c7dde43..3be2b14de 100644 --- a/examples/server/webui/package.json +++ b/examples/server/webui/package.json @@ -11,6 +11,7 @@ "preview": "vite preview" }, "dependencies": { + "@heroicons/react": "^2.2.0", "@sec-ant/readable-stream": "^0.6.0", "@vscode/markdown-it-katex": "^1.1.1", "autoprefixer": "^10.4.20", diff --git a/examples/server/webui/src/components/SettingDialog.tsx b/examples/server/webui/src/components/SettingDialog.tsx index 5565ab7bb..d30925e6c 100644 --- a/examples/server/webui/src/components/SettingDialog.tsx +++ b/examples/server/webui/src/components/SettingDialog.tsx @@ -3,18 +3,25 @@ import { useAppContext } from '../utils/app.context'; import { CONFIG_DEFAULT, CONFIG_INFO } from '../Config'; import { isDev } from '../Config'; import StorageUtils from '../utils/storage'; -import { isBoolean, isNumeric, isString } from '../utils/misc'; +import { classNames, isBoolean, isNumeric, isString } from '../utils/misc'; +import { + ChatBubbleOvalLeftEllipsisIcon, + Cog6ToothIcon, + FunnelIcon, + HandRaisedIcon, + SquaresPlusIcon, +} from '@heroicons/react/24/outline'; type SettKey = keyof typeof CONFIG_DEFAULT; -const COMMON_SAMPLER_KEYS: SettKey[] = [ +const BASIC_KEYS: SettKey[] = [ 'temperature', 'top_k', 'top_p', 'min_p', 'max_tokens', ]; -const OTHER_SAMPLER_KEYS: SettKey[] = [ +const SAMPLER_KEYS: SettKey[] = [ 'dynatemp_range', 'dynatemp_exponent', 'typical_p', @@ -32,6 +39,178 @@ const PENALTY_KEYS: SettKey[] = [ 'dry_penalty_last_n', ]; +enum SettingInputType { + SHORT_INPUT, + LONG_INPUT, + CHECKBOX, + CUSTOM, +} + +interface SettingFieldInput { + type: Exclude; + label: string | React.ReactElement; + help?: string | React.ReactElement; + key: SettKey; +} + +interface SettingFieldCustom { + type: SettingInputType.CUSTOM; + key: SettKey; + component: + | string + | React.FC<{ + value: string | boolean | number; + onChange: (value: string) => void; + }>; +} + +interface SettingSection { + title: React.ReactElement; + fields: (SettingFieldInput | SettingFieldCustom)[]; +} + +const ICON_CLASSNAME = 'w-4 h-4 mr-1 inline'; + +const SETTING_SECTIONS: SettingSection[] = [ + { + title: ( + <> + + General + + ), + fields: [ + { + type: SettingInputType.SHORT_INPUT, + label: 'API Key', + key: 'apiKey', + }, + { + type: SettingInputType.LONG_INPUT, + label: 'System Message (will be disabled if left empty)', + key: 'systemMessage', + }, + ...BASIC_KEYS.map( + (key) => + ({ + type: SettingInputType.SHORT_INPUT, + label: key, + key, + }) as SettingFieldInput + ), + ], + }, + { + title: ( + <> + + Samplers + + ), + fields: [ + { + type: SettingInputType.SHORT_INPUT, + label: 'Samplers queue', + key: 'samplers', + }, + ...SAMPLER_KEYS.map( + (key) => + ({ + type: SettingInputType.SHORT_INPUT, + label: key, + key, + }) as SettingFieldInput + ), + ], + }, + { + title: ( + <> + + Penalties + + ), + fields: PENALTY_KEYS.map((key) => ({ + type: SettingInputType.SHORT_INPUT, + label: key, + key, + })), + }, + { + title: ( + <> + + Reasoning + + ), + fields: [ + { + type: SettingInputType.CHECKBOX, + label: 'Expand though process by default for generating message', + key: 'showThoughtInProgress', + }, + { + type: SettingInputType.CHECKBOX, + label: + 'Exclude thought process when sending request to API (Recommended for DeepSeek-R1)', + key: 'excludeThoughtOnReq', + }, + ], + }, + { + title: ( + <> + + Advanced + + ), + fields: [ + { + type: SettingInputType.CUSTOM, + key: 'custom', // dummy key, won't be used + component: () => { + const debugImportDemoConv = async () => { + const res = await fetch('/demo-conversation.json'); + const demoConv = await res.json(); + StorageUtils.remove(demoConv.id); + for (const msg of demoConv.messages) { + StorageUtils.appendMsg(demoConv.id, msg); + } + }; + return ( + + ); + }, + }, + { + type: SettingInputType.CHECKBOX, + label: 'Show tokens per second', + key: 'showTokensPerSecond', + }, + { + type: SettingInputType.LONG_INPUT, + label: ( + <> + Custom JSON config (For more info, refer to{' '} + + server documentation + + ) + + ), + key: 'custom', + }, + ], + }, +]; + export default function SettingDialog({ show, onClose, @@ -40,6 +219,7 @@ export default function SettingDialog({ onClose: () => void; }) { const { config, saveConfig } = useAppContext(); + const [sectionIdx, setSectionIdx] = useState(0); // clone the config object to prevent direct mutation const [localConfig, setLocalConfig] = useState( @@ -92,16 +272,6 @@ export default function SettingDialog({ onClose(); }; - const debugImportDemoConv = async () => { - const res = await fetch('/demo-conversation.json'); - const demoConv = await res.json(); - StorageUtils.remove(demoConv.id); - for (const msg of demoConv.messages) { - StorageUtils.appendMsg(demoConv.id, msg); - } - onClose(); - }; - const onChange = (key: SettKey) => (value: string | boolean) => { // note: we do not perform validation here, because we may get incomplete value as user is still typing it setLocalConfig({ ...localConfig, [key]: value }); @@ -109,164 +279,102 @@ export default function SettingDialog({ return ( -
+

Settings

-
-

- Settings below are saved in browser's localStorage -

- - - -