char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Aleksander Grygierui: Improve Chat Form Actions UI/UX (models selector, add panel) (#27746)cae63579b

char/aliceai-t5-moe
24.4 KiB698 linesraw
1import { CLI_FLAGS } from './cli-flags.constants';
2import { DEFAULT_MCP_CONFIG } from './mcp.constants';
3import { SETTINGS_KEYS } from './settings-keys.constants';
4import { TITLE_GENERATION } from './title-generation.constants';
5import { FILE_GLOB_SEARCH_PICKERS } from './working-directory.constants';
6import {
7	Code,
8	Database,
9	Funnel,
10	ListRestart,
11	Monitor,
12	Moon,
13	PencilRuler,
14	SlidersVertical,
15	Sun
16} from '@lucide/svelte';
17import { SyncableParameterType } from '$lib/enums';
18import { SettingsFieldType } from '$lib/enums/settings.enums';
19import { ColorMode } from '$lib/enums/ui.enums';
20import type {
21	SettingsConfigValue,
22	SettingsEntry,
23	SettingsFieldConfig,
24	SettingsSection,
25	SettingsSectionEntry
26} from '$lib/types';
27
28/** Settings sections — slug is the routing identity, title is the display label. */
29export const SETTINGS_SECTIONS = {
30	AGENTIC: { slug: 'agentic', title: 'Agentic' },
31	DEVELOPER: { slug: 'developer', title: 'Developer' },
32	DISPLAY: { slug: 'display', title: 'Display' },
33	GENERAL: { slug: 'general', title: 'General' },
34	IMPORT_EXPORT: { slug: 'import-export', title: 'Import/Export' },
35	SAMPLING_PENALTIES: { slug: 'sampling-penalties', title: 'Sampling & Penalties' },
36	TOOLS: { slug: 'tools', title: 'Tools' }
37} as const;
38
39export const SETTINGS_SECTION_SLUGS = {
40	AGENTIC: SETTINGS_SECTIONS.AGENTIC.slug,
41	DEVELOPER: SETTINGS_SECTIONS.DEVELOPER.slug,
42	DISPLAY: SETTINGS_SECTIONS.DISPLAY.slug,
43	GENERAL: SETTINGS_SECTIONS.GENERAL.slug,
44	IMPORT_EXPORT: SETTINGS_SECTIONS.IMPORT_EXPORT.slug,
45	SAMPLING_PENALTIES: SETTINGS_SECTIONS.SAMPLING_PENALTIES.slug,
46	TOOLS: SETTINGS_SECTIONS.TOOLS.slug
47} as const;
48
49export const SETTINGS_SECTION_TITLES = {
50	AGENTIC: SETTINGS_SECTIONS.AGENTIC.title,
51	DEVELOPER: SETTINGS_SECTIONS.DEVELOPER.title,
52	DISPLAY: SETTINGS_SECTIONS.DISPLAY.title,
53	GENERAL: SETTINGS_SECTIONS.GENERAL.title,
54	IMPORT_EXPORT: SETTINGS_SECTIONS.IMPORT_EXPORT.title,
55	SAMPLING_PENALTIES: SETTINGS_SECTIONS.SAMPLING_PENALTIES.title,
56	TOOLS: SETTINGS_SECTIONS.TOOLS.title
57} as const;
58
59export const SETTINGS_REGISTRY: SettingsSectionEntry[] = [
60	// General
61	{
62		icon: SlidersVertical,
63		settings: [
64			{
65				defaultValue: ColorMode.SYSTEM,
66				help: 'Choose the color theme for the interface. You can choose between System (follows your device settings), Light, or Dark.',
67				key: SETTINGS_KEYS.THEME,
68				label: 'Theme',
69				options: [
70					{ icon: Monitor, label: 'System', value: ColorMode.SYSTEM },
71					{ icon: Sun, label: 'Light', value: ColorMode.LIGHT },
72					{ icon: Moon, label: 'Dark', value: ColorMode.DARK }
73				],
74				type: SettingsFieldType.SELECT
75			},
76			{
77				defaultValue: '',
78				help: `Set the API Key if you are using <code> ${CLI_FLAGS.API_KEY} </code> option for the server.`,
79				isPrivate: true,
80				key: SETTINGS_KEYS.API_KEY,
81				label: 'API Key',
82				type: SettingsFieldType.INPUT
83			},
84			{
85				defaultValue: '',
86				help: 'The starting message that defines how model should behave.',
87				key: SETTINGS_KEYS.SYSTEM_MESSAGE,
88				label: 'System Message',
89				type: SettingsFieldType.TEXTAREA
90			},
91			{
92				defaultValue: true,
93				help: 'Display the system message at the top of each conversation.',
94				key: SETTINGS_KEYS.SHOW_SYSTEM_MESSAGE,
95				label: 'Show system message',
96				standaloneField: false,
97				type: SettingsFieldType.CHECKBOX
98			},
99			{
100				defaultValue: 2500,
101				help: 'On pasting long text, it will be converted to a file. You can control the file length by setting the value of this parameter. Value 0 means disable.',
102				key: SETTINGS_KEYS.PASTE_LONG_TEXT_TO_FILE_LEN,
103				label: 'Paste long text to file length',
104				type: SettingsFieldType.INPUT
105			},
106			{
107				defaultValue: true,
108				help: 'Use Enter to send messages and Shift + Enter for new lines. When disabled, use Ctrl/Cmd + Enter.',
109				key: SETTINGS_KEYS.SEND_ON_ENTER,
110				label: 'Send message on Enter',
111				type: SettingsFieldType.CHECKBOX
112			},
113			{
114				defaultValue: true,
115				help: 'Automatically show microphone button instead of send button when textarea is empty for models with audio modality support.',
116				key: SETTINGS_KEYS.AUTO_MIC_ON_EMPTY,
117				label: 'Show microphone on empty input',
118				type: SettingsFieldType.CHECKBOX
119			},
120			{
121				defaultValue: false,
122				help: 'Enable "Continue" button for assistant messages, including reasoning models.',
123				isExperimental: true,
124				key: SETTINGS_KEYS.ENABLE_CONTINUE_GENERATION,
125				label: 'Enable "Continue" button',
126				type: SettingsFieldType.CHECKBOX
127			},
128			{
129				defaultValue: true,
130				help: 'Choose how conversation titles are generated. The first non-empty line uses a fast deterministic rule; the LLM option uses a model-generated title from the first message exchange.',
131				key: SETTINGS_KEYS.TITLE_GENERATION_USE_FIRST_LINE,
132				label: 'Conversation title',
133				radioOptions: [
134					{
135						key: SETTINGS_KEYS.TITLE_GENERATION_USE_FIRST_LINE,
136						label: 'Use first non-empty line for the conversation title',
137						value: 'firstLine'
138					},
139					{
140						isExperimental: true,
141						key: SETTINGS_KEYS.TITLE_GENERATION_USE_LLM,
142						label: 'Generate title with LLM',
143						value: 'llm'
144					}
145				],
146				type: SettingsFieldType.RADIO
147			},
148			{
149				defaultValue: TITLE_GENERATION.DEFAULT_PROMPT,
150				dependsOn: SETTINGS_KEYS.TITLE_GENERATION_USE_LLM,
151				help: 'Optional template for the title generation prompt. Use {{USER}} for the user message and {{ASSISTANT}} for the assistant message.',
152				key: SETTINGS_KEYS.TITLE_GENERATION_PROMPT,
153				label: 'LLM title generation prompt',
154				type: SettingsFieldType.TEXTAREA
155			},
156			{
157				defaultValue: false,
158				help: 'Counterpart of the conversation title radio; stored and synced without a dedicated UI field.',
159				key: SETTINGS_KEYS.TITLE_GENERATION_USE_LLM,
160				label: 'Generate title with LLM',
161				standaloneField: false,
162				type: SettingsFieldType.CHECKBOX
163			},
164			{
165				defaultValue: false,
166				help: 'When copying a message with text attachments, combine them into a single plain text string instead of a special format that can be pasted back as attachments.',
167				key: SETTINGS_KEYS.COPY_TEXT_ATTACHMENTS_AS_PLAIN_TEXT,
168				label: 'Copy text attachments as plain text',
169				type: SettingsFieldType.CHECKBOX
170			},
171			{
172				defaultValue: false,
173				help: 'Parse PDF as image instead of text. Automatically falls back to text processing for non-vision models.',
174				key: SETTINGS_KEYS.PDF_AS_IMAGE,
175				label: 'Parse PDF as image',
176				type: SettingsFieldType.CHECKBOX
177			},
178			{
179				defaultValue: 0,
180				help: 'Images larger than this will be resized before sending to server. Set to 0 to disable.',
181				key: SETTINGS_KEYS.MAX_IMAGE_RESOLUTION,
182				label: 'Maximum image resolution (megapixels)',
183				type: SettingsFieldType.INPUT
184			}
185		],
186		slug: SETTINGS_SECTION_SLUGS.GENERAL,
187		title: SETTINGS_SECTION_TITLES.GENERAL
188	},
189	// Display
190	{
191		icon: Monitor,
192		settings: [
193			{
194				defaultValue: true,
195				help: 'Display generation statistics (tokens/second, token count, duration) below each assistant message.',
196				key: SETTINGS_KEYS.SHOW_MESSAGE_STATS,
197				label: 'Show message generation statistics',
198				type: SettingsFieldType.CHECKBOX
199			},
200			{
201				defaultValue: false,
202				dependsOn: SETTINGS_KEYS.SHOW_MESSAGE_STATS,
203				help: 'Display per-turn statistics (tokens, duration) under each turn in agentic responses. Shown only when "Show message generation statistics" is enabled.',
204				key: SETTINGS_KEYS.SHOW_AGENTIC_TURN_STATS,
205				label: 'Show statistics for individual agentic turns',
206				type: SettingsFieldType.CHECKBOX
207			},
208			{
209				defaultValue: true,
210				help: 'Expand thought process by default when generating messages.',
211				key: SETTINGS_KEYS.SHOW_THOUGHT_IN_PROGRESS,
212				label: 'Show thought in progress',
213				type: SettingsFieldType.CHECKBOX
214			},
215			{
216				defaultValue: false,
217				help: 'Automatically expand tool call details while executing and keep them expanded after completion.',
218				key: SETTINGS_KEYS.ALWAYS_SHOW_TOOL_CALL_CONTENT,
219				label: 'Always show tool call content',
220				type: SettingsFieldType.CHECKBOX
221			},
222			{
223				defaultValue: true,
224				help: 'Render user messages using markdown formatting in the chat. Turn this off to keep a message exactly as typed; @-mention badges show either way.',
225				key: SETTINGS_KEYS.RENDER_USER_CONTENT_AS_MARKDOWN,
226				label: 'Render user content as Markdown',
227				type: SettingsFieldType.CHECKBOX
228			},
229			{
230				defaultValue: true,
231				help: 'Render the reasoning/thinking block content as formatted Markdown instead of plain text.',
232				key: SETTINGS_KEYS.RENDER_THINKING_AS_MARKDOWN,
233				label: 'Render thinking as Markdown',
234				type: SettingsFieldType.CHECKBOX
235			},
236			{
237				defaultValue: false,
238				help: 'Always display code blocks at their full natural height, overriding any height limits.',
239				key: SETTINGS_KEYS.FULL_HEIGHT_CODE_BLOCKS,
240				label: 'Use full height code blocks',
241				type: SettingsFieldType.CHECKBOX
242			},
243			{
244				defaultValue: false,
245				help: 'Disable automatic scrolling while messages stream so you can control the viewport position manually.',
246				key: SETTINGS_KEYS.DISABLE_AUTO_SCROLL,
247				label: 'Disable automatic scroll',
248				type: SettingsFieldType.CHECKBOX
249			},
250			{
251				defaultValue: false,
252				help: 'Always keep the sidebar visible on desktop instead of auto-hiding it.',
253				key: SETTINGS_KEYS.ALWAYS_SHOW_SIDEBAR_ON_DESKTOP,
254				label: 'Always show sidebar on desktop',
255				type: SettingsFieldType.CHECKBOX
256			},
257			{
258				defaultValue: true,
259				help: 'Show open chats as browser-style tabs above the conversation, one per open chat. When disabled, only one chat is shown at a time.',
260				key: SETTINGS_KEYS.CONVERSATION_TABS,
261				label: 'Conversation tabs',
262				type: SettingsFieldType.CHECKBOX
263			},
264			{
265				defaultValue: false,
266				help: 'Display full raw model identifiers (e.g. "ggml-org/GLM-4.7-Flash-GGUF:Q8_0") instead of parsed names with badges.',
267				key: SETTINGS_KEYS.SHOW_RAW_MODEL_NAMES,
268				label: 'Show raw model names',
269				type: SettingsFieldType.CHECKBOX
270			},
271			{
272				defaultValue: true,
273				help: 'Display quantization badges (e.g. Q8_0, Q4_K_M) next to model names throughout the interface.',
274				key: SETTINGS_KEYS.SHOW_MODEL_QUANTIZATION,
275				label: 'Show model quantization information',
276				type: SettingsFieldType.CHECKBOX
277			},
278			{
279				defaultValue: true,
280				help: 'Display model tags (e.g. "vision", "reasoning") next to model names throughout the interface.',
281				key: SETTINGS_KEYS.SHOW_MODEL_TAGS,
282				label: 'Show model tags',
283				type: SettingsFieldType.CHECKBOX
284			},
285			{
286				defaultValue: false,
287				help: 'Display the organization name in the model selector trigger button.',
288				key: SETTINGS_KEYS.SHOW_MODEL_ORG_NAME_IN_TRIGGER,
289				label: 'Show organization name in model selector trigger',
290				type: SettingsFieldType.CHECKBOX
291			},
292			{
293				defaultValue: false,
294				help: 'Display the current build version in the bottom-right corner of the interface.',
295				key: SETTINGS_KEYS.SHOW_BUILD_VERSION,
296				label: 'Show build version information',
297				type: SettingsFieldType.CHECKBOX
298			},
299			{
300				defaultValue: false,
301				help: 'Display the full file system path inside file and folder @-mention badges instead of just the file or folder name.',
302				key: SETTINGS_KEYS.SHOW_FULL_PATH_IN_MENTIONS,
303				label: 'Show full path in mentions',
304				type: SettingsFieldType.CHECKBOX
305			}
306		],
307		slug: SETTINGS_SECTION_SLUGS.DISPLAY,
308		title: SETTINGS_SECTION_TITLES.DISPLAY
309	},
310	// MCP Servers (non-UI config object)
311	{
312		icon: PencilRuler,
313		settings: [
314			{
315				defaultValue: '[]',
316				help: 'Configure MCP servers as a JSON list. Use the form in the MCP Client settings section to edit.',
317				key: SETTINGS_KEYS.MCP_SERVERS,
318				label: 'MCP servers',
319				standaloneField: false,
320				type: SettingsFieldType.INPUT
321			}
322		],
323		slug: SETTINGS_SECTION_SLUGS.TOOLS,
324		title: SETTINGS_SECTION_TITLES.TOOLS
325	},
326	// Tools
327	{
328		icon: ListRestart,
329		settings: [
330			{
331				defaultValue: 10,
332				help: 'Maximum number of tool execution cycles before stopping (prevents infinite loops).',
333				isPositiveInteger: true,
334				key: SETTINGS_KEYS.AGENTIC_MAX_TURNS,
335				label: 'Agentic turns',
336				type: SettingsFieldType.INPUT
337			},
338			{
339				defaultValue: DEFAULT_MCP_CONFIG.requestTimeoutSeconds,
340				help: 'Timeout for individual MCP tool calls.',
341				isPositiveInteger: true,
342				key: SETTINGS_KEYS.MCP_REQUEST_TIMEOUT_SECONDS,
343				label: 'MCP request timeout (seconds)',
344				type: SettingsFieldType.INPUT
345			},
346			{
347				defaultValue: FILE_GLOB_SEARCH_PICKERS.DEFAULT_SEARCH_DEPTH,
348				help: 'How many directory levels below the working directory the @-mention file search descends. Larger values surface deeply nested files but take longer on large trees.',
349				isPositiveInteger: true,
350				key: SETTINGS_KEYS.MENTION_SEARCH_MAX_DEPTH,
351				label: 'Mention search depth',
352				max: FILE_GLOB_SEARCH_PICKERS.MAX_SEARCH_DEPTH,
353				min: 1,
354				placeholder: `${FILE_GLOB_SEARCH_PICKERS.DEFAULT_SEARCH_DEPTH}`,
355				type: SettingsFieldType.INPUT
356			}
357		],
358		slug: SETTINGS_SECTION_SLUGS.AGENTIC,
359		title: SETTINGS_SECTION_TITLES.AGENTIC
360	},
361	// Import/Export
362	{
363		icon: Database,
364		settings: [],
365		slug: SETTINGS_SECTION_SLUGS.IMPORT_EXPORT,
366		title: SETTINGS_SECTION_TITLES.IMPORT_EXPORT
367	},
368	// Sampling
369	{
370		icon: Funnel,
371		settings: [
372			{
373				defaultValue: undefined,
374				help: 'Controls the randomness of the generated text by affecting the probability distribution of the output tokens. Higher = more random, lower = more focused.',
375				key: SETTINGS_KEYS.TEMPERATURE,
376				label: 'Temperature',
377				sync: {
378					paramType: SyncableParameterType.NUMBER,
379					serverKey: SETTINGS_KEYS.TEMPERATURE
380				},
381				type: SettingsFieldType.INPUT
382			},
383			{
384				defaultValue: undefined,
385				help: 'Addon for the temperature sampler. The added value to the range of dynamic temperature, which adjusts probabilities by entropy of tokens.',
386				key: SETTINGS_KEYS.DYNATEMP_RANGE,
387				label: 'Dynamic temperature range',
388				sync: {
389					paramType: SyncableParameterType.NUMBER,
390					serverKey: SETTINGS_KEYS.DYNATEMP_RANGE
391				},
392				type: SettingsFieldType.INPUT
393			},
394			{
395				defaultValue: undefined,
396				help: 'Addon for the temperature sampler. Smoothes out the probability redistribution based on the most probable token.',
397				key: SETTINGS_KEYS.DYNATEMP_EXPONENT,
398				label: 'Dynamic temperature exponent',
399				sync: {
400					paramType: SyncableParameterType.NUMBER,
401					serverKey: SETTINGS_KEYS.DYNATEMP_EXPONENT
402				},
403				type: SettingsFieldType.INPUT
404			},
405			{
406				defaultValue: undefined,
407				help: 'Keeps only k top tokens.',
408				key: SETTINGS_KEYS.TOP_K,
409				label: 'Top K',
410				sync: { paramType: SyncableParameterType.NUMBER, serverKey: SETTINGS_KEYS.TOP_K },
411				type: SettingsFieldType.INPUT
412			},
413			{
414				defaultValue: undefined,
415				help: 'Limits tokens to those that together have a cumulative probability of at least p',
416				key: SETTINGS_KEYS.TOP_P,
417				label: 'Top P',
418				sync: { paramType: SyncableParameterType.NUMBER, serverKey: SETTINGS_KEYS.TOP_P },
419				type: SettingsFieldType.INPUT
420			},
421			{
422				defaultValue: undefined,
423				help: 'Limits tokens based on the minimum probability for a token to be considered, relative to the probability of the most likely token.',
424				key: SETTINGS_KEYS.MIN_P,
425				label: 'Min P',
426				sync: { paramType: SyncableParameterType.NUMBER, serverKey: SETTINGS_KEYS.MIN_P },
427				type: SettingsFieldType.INPUT
428			},
429			{
430				defaultValue: undefined,
431				help: 'XTC sampler cuts out top tokens; this parameter controls the chance of cutting tokens at all. 0 disables XTC.',
432				key: SETTINGS_KEYS.XTC_PROBABILITY,
433				label: 'XTC probability',
434				sync: {
435					paramType: SyncableParameterType.NUMBER,
436					serverKey: SETTINGS_KEYS.XTC_PROBABILITY
437				},
438				type: SettingsFieldType.INPUT
439			},
440			{
441				defaultValue: undefined,
442				help: 'XTC sampler cuts out top tokens; this parameter controls the token probability that is required to cut that token.',
443				key: SETTINGS_KEYS.XTC_THRESHOLD,
444				label: 'XTC threshold',
445				sync: {
446					paramType: SyncableParameterType.NUMBER,
447					serverKey: SETTINGS_KEYS.XTC_THRESHOLD
448				},
449				type: SettingsFieldType.INPUT
450			},
451			{
452				defaultValue: undefined,
453				help: 'Sorts and limits tokens based on the difference between log-probability and entropy.',
454				key: SETTINGS_KEYS.TYP_P,
455				label: 'Typical P',
456				sync: { paramType: SyncableParameterType.NUMBER, serverKey: SETTINGS_KEYS.TYP_P },
457				type: SettingsFieldType.INPUT
458			},
459			{
460				defaultValue: undefined,
461				help: 'The maximum number of token per output. Use -1 for infinite (no limit).',
462				key: SETTINGS_KEYS.MAX_TOKENS,
463				label: 'Max tokens',
464				sync: {
465					paramType: SyncableParameterType.NUMBER,
466					serverKey: SETTINGS_KEYS.MAX_TOKENS
467				},
468				type: SettingsFieldType.INPUT
469			},
470			{
471				defaultValue: '',
472				help: 'The order at which samplers are applied, in simplified way. Default is "top_k;typ_p;top_p;min_p;temperature": top_k->typ_p->top_p->min_p->temperature',
473				key: SETTINGS_KEYS.SAMPLERS,
474				label: 'Samplers',
475				sync: { paramType: SyncableParameterType.STRING, serverKey: SETTINGS_KEYS.SAMPLERS },
476				type: SettingsFieldType.INPUT
477			},
478			{
479				defaultValue: false,
480				help: 'Enable backend-based samplers. When enabled, supported samplers run on the accelerator backend for faster sampling.',
481				key: SETTINGS_KEYS.BACKEND_SAMPLING,
482				label: 'Backend sampling',
483				type: SettingsFieldType.CHECKBOX
484			},
485			{
486				defaultValue: undefined,
487				help: 'Last n tokens to consider for penalizing repetition',
488				key: SETTINGS_KEYS.REPEAT_LAST_N,
489				label: 'Repeat last N',
490				sync: {
491					paramType: SyncableParameterType.NUMBER,
492					serverKey: SETTINGS_KEYS.REPEAT_LAST_N
493				},
494				type: SettingsFieldType.INPUT
495			},
496			{
497				defaultValue: undefined,
498				help: 'Controls the repetition of token sequences in the generated text',
499				key: SETTINGS_KEYS.REPEAT_PENALTY,
500				label: 'Repeat penalty',
501				sync: {
502					paramType: SyncableParameterType.NUMBER,
503					serverKey: SETTINGS_KEYS.REPEAT_PENALTY
504				},
505				type: SettingsFieldType.INPUT
506			},
507			{
508				defaultValue: undefined,
509				help: 'Limits tokens based on whether they appear in the output or not.',
510				key: SETTINGS_KEYS.PRESENCE_PENALTY,
511				label: 'Presence penalty',
512				sync: {
513					paramType: SyncableParameterType.NUMBER,
514					serverKey: SETTINGS_KEYS.PRESENCE_PENALTY
515				},
516				type: SettingsFieldType.INPUT
517			},
518			{
519				defaultValue: undefined,
520				help: 'Limits tokens based on how often they appear in the output.',
521				key: SETTINGS_KEYS.FREQUENCY_PENALTY,
522				label: 'Frequency penalty',
523				sync: {
524					paramType: SyncableParameterType.NUMBER,
525					serverKey: SETTINGS_KEYS.FREQUENCY_PENALTY
526				},
527				type: SettingsFieldType.INPUT
528			},
529			{
530				defaultValue: undefined,
531				help: 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets the DRY sampling multiplier.',
532				key: SETTINGS_KEYS.DRY_MULTIPLIER,
533				label: 'DRY multiplier',
534				sync: {
535					paramType: SyncableParameterType.NUMBER,
536					serverKey: SETTINGS_KEYS.DRY_MULTIPLIER
537				},
538				type: SettingsFieldType.INPUT
539			},
540			{
541				defaultValue: undefined,
542				help: 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets the DRY sampling base value.',
543				key: SETTINGS_KEYS.DRY_BASE,
544				label: 'DRY base',
545				sync: { paramType: SyncableParameterType.NUMBER, serverKey: SETTINGS_KEYS.DRY_BASE },
546				type: SettingsFieldType.INPUT
547			},
548			{
549				defaultValue: undefined,
550				help: 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets the allowed length for DRY sampling.',
551				key: SETTINGS_KEYS.DRY_ALLOWED_LENGTH,
552				label: 'DRY allowed length',
553				sync: {
554					paramType: SyncableParameterType.NUMBER,
555					serverKey: SETTINGS_KEYS.DRY_ALLOWED_LENGTH
556				},
557				type: SettingsFieldType.INPUT
558			},
559			{
560				defaultValue: undefined,
561				help: 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets DRY penalty for the last n tokens.',
562				key: SETTINGS_KEYS.DRY_PENALTY_LAST_N,
563				label: 'DRY penalty last N',
564				sync: {
565					paramType: SyncableParameterType.NUMBER,
566					serverKey: SETTINGS_KEYS.DRY_PENALTY_LAST_N
567				},
568				type: SettingsFieldType.INPUT
569			}
570		],
571		slug: SETTINGS_SECTION_SLUGS.SAMPLING_PENALTIES,
572		title: SETTINGS_SECTION_TITLES.SAMPLING_PENALTIES
573	},
574	// Developer
575	{
576		icon: Code,
577		settings: [
578			{
579				defaultValue: false,
580				help: 'After each response, re-submit the conversation to pre-fill the server KV cache. Makes the next turn faster since the prompt is already encoded while you read the response.',
581				key: SETTINGS_KEYS.PRE_ENCODE_CONVERSATION,
582				label: 'Pre-fill KV cache after response',
583				type: SettingsFieldType.CHECKBOX
584			},
585			{
586				defaultValue: false,
587				help: 'Send reasoning_format=none so the server returns thinking tokens inline instead of extracting them into a separate field.',
588				key: SETTINGS_KEYS.DISABLE_REASONING_PARSING,
589				label: 'Disable reasoning content parsing',
590				type: SettingsFieldType.CHECKBOX
591			},
592			{
593				defaultValue: false,
594				help: 'Strip thinking from previous messages before sending. When off, thinking is sent back via the reasoning_content field so the model sees its own chain-of-thought across turns.',
595				key: SETTINGS_KEYS.EXCLUDE_REASONING_FROM_CONTEXT,
596				label: 'Exclude reasoning from context',
597				type: SettingsFieldType.CHECKBOX
598			},
599			{
600				defaultValue: false,
601				help: 'Show toggle button to display messages as plain text instead of Markdown-formatted content',
602				key: SETTINGS_KEYS.SHOW_RAW_OUTPUT_SWITCH,
603				label: 'Enable raw output toggle',
604				type: SettingsFieldType.CHECKBOX
605			},
606			{
607				defaultValue: false,
608				help: 'Expose a run_javascript tool to the model. Code runs in a Web Worker inside a sandboxed iframe with an opaque origin, isolated from the WebUI and its API, with a hard timeout.',
609				key: SETTINGS_KEYS.JS_SANDBOX_ENABLED,
610				label: 'JavaScript sandbox tool',
611				type: SettingsFieldType.CHECKBOX
612			},
613			{
614				defaultValue: false,
615				dependsOn: SETTINGS_KEYS.JS_SANDBOX_ENABLED,
616				help: 'Pre-load nerdamer in the sandbox for symbolic computation: simplify, diff, integrate, solve, and more. Requires "JavaScript sandbox tool" to be enabled.',
617				key: SETTINGS_KEYS.SYMBOLIC_MATH_ENABLED,
618				label: 'Symbolic math (nerdamer)',
619				type: SettingsFieldType.CHECKBOX
620			},
621			{
622				defaultValue: '',
623				help: 'Custom JSON parameters to send to the API. Must be valid JSON format.',
624				key: SETTINGS_KEYS.CUSTOM_JSON,
625				label: 'Custom JSON',
626				type: SettingsFieldType.TEXTAREA
627			},
628			{
629				defaultValue: '',
630				help: 'CSS injected into the page at runtime. Set it here, or ship it server side via the --ui-config customCss field.',
631				key: SETTINGS_KEYS.CUSTOM_CSS,
632				label: 'Custom CSS',
633				type: SettingsFieldType.TEXTAREA
634			}
635		],
636		slug: SETTINGS_SECTION_SLUGS.DEVELOPER,
637		title: SETTINGS_SECTION_TITLES.DEVELOPER
638	}
639];
640
641function getAllSettings(): SettingsEntry[] {
642	const result: SettingsEntry[] = [];
643
644	for (const section of SETTINGS_REGISTRY) {
645		result.push(...section.settings);
646	}
647
648	return result;
649}
650
651/** Flat config object stored in localStorage. */
652export const SETTING_CONFIG_DEFAULT: Record<string, SettingsConfigValue> = Object.fromEntries(
653	getAllSettings().map((s) => [s.key, s.defaultValue])
654) as Record<string, SettingsConfigValue>;
655
656/** Help text for every setting (including non-UI). */
657export const SETTING_CONFIG_INFO: Record<string, string> = Object.fromEntries(
658	getAllSettings().map((s) => [s.key, s.help])
659) as Record<string, string>;
660
661/** Sidebar sections + field configs (as consumed by UI). */
662function toSettingsSection(section: SettingsSectionEntry): SettingsSection {
663	return {
664		fields: section.settings
665			.filter((s) => s.standaloneField !== false)
666			.map((s) => ({
667				dependsOn: s.dependsOn,
668				help: s.help,
669				isExperimental: s.isExperimental,
670				isPositiveInteger: s.isPositiveInteger,
671				isPrivate: s.isPrivate,
672				key: s.key,
673				label: s.label,
674				max: s.max,
675				min: s.min,
676				options: s.options as SettingsFieldConfig['options'],
677				placeholder: s.placeholder,
678				radioOptions: s.radioOptions,
679				type: s.type
680			})),
681		icon: section.icon,
682		slug: section.slug,
683		title: section.title
684	};
685}
686
687/** Sidebar sections in custom display order (the registry array order). */
688export const SETTINGS_CHAT_SECTIONS: SettingsSection[] = SETTINGS_REGISTRY.map(toSettingsSection);
689
690/** INPUT-type settings whose value is a number. */
691export const NUMERIC_FIELDS = getAllSettings()
692	.filter((s) => s.type === SettingsFieldType.INPUT && typeof s.defaultValue !== 'string')
693	.map((s) => s.key) as readonly string[];
694
695/** Numeric fields clamped to >= 1 and rounded. */
696export const POSITIVE_INTEGER_FIELDS = getAllSettings()
697	.filter((s) => s.isPositiveInteger)
698	.map((s) => s.key) as readonly string[];