|
1 | 1 | <script lang="ts"> |
2 | | - import { Settings, Filter, Hand, MessageSquare, Plus, Beaker } from '@lucide/svelte'; |
| 2 | + import { Settings, Filter, AlertTriangle, Brain, Cog } from '@lucide/svelte'; |
3 | 3 | import { ChatSettingsFooter, ChatSettingsSection } from '$lib/components/app'; |
4 | 4 | import { Checkbox } from '$lib/components/ui/checkbox'; |
5 | 5 | import * as Dialog from '$lib/components/ui/dialog'; |
6 | 6 | import { Input } from '$lib/components/ui/input'; |
7 | 7 | import { ScrollArea } from '$lib/components/ui/scroll-area'; |
8 | 8 | import { Textarea } from '$lib/components/ui/textarea'; |
9 | | - import { SETTING_CONFIG_DEFAULT } from '$lib/constants/settings-config'; |
| 9 | + import { SETTING_CONFIG_DEFAULT, SETTING_CONFIG_INFO } from '$lib/constants/settings-config'; |
10 | 10 | import { |
11 | 11 | config, |
12 | 12 | updateMultipleConfig, |
|
32 | 32 | const defaultConfig = SETTING_CONFIG_DEFAULT; |
33 | 33 |
|
34 | 34 | function handleSave() { |
35 | | - updateMultipleConfig(localConfig); |
| 35 | + // Validate custom JSON if provided |
| 36 | + if (localConfig.custom && typeof localConfig.custom === 'string' && localConfig.custom.trim()) { |
| 37 | + try { |
| 38 | + JSON.parse(localConfig.custom); |
| 39 | + } catch (error) { |
| 40 | + alert('Invalid JSON in custom parameters. Please check the format and try again.'); |
| 41 | + return; |
| 42 | + } |
| 43 | + } |
| 44 | +
|
| 45 | + // Convert numeric strings to numbers for numeric fields |
| 46 | + const processedConfig = { ...localConfig }; |
| 47 | + const numericFields = [ |
| 48 | + 'temperature', 'top_k', 'top_p', 'min_p', 'max_tokens', 'pasteLongTextToFileLen', |
| 49 | + 'dynatemp_range', 'dynatemp_exponent', 'typical_p', 'xtc_probability', 'xtc_threshold', |
| 50 | + 'repeat_last_n', 'repeat_penalty', 'presence_penalty', 'frequency_penalty', |
| 51 | + 'dry_multiplier', 'dry_base', 'dry_allowed_length', 'dry_penalty_last_n' |
| 52 | + ]; |
| 53 | +
|
| 54 | + for (const field of numericFields) { |
| 55 | + if (processedConfig[field] !== undefined && processedConfig[field] !== '') { |
| 56 | + const numValue = Number(processedConfig[field]); |
| 57 | + if (!isNaN(numValue)) { |
| 58 | + processedConfig[field] = numValue; |
| 59 | + } else { |
| 60 | + alert(`Invalid numeric value for ${field}. Please enter a valid number.`); |
| 61 | + return; |
| 62 | + } |
| 63 | + } |
| 64 | + } |
36 | 65 |
|
| 66 | + updateMultipleConfig(processedConfig); |
37 | 67 | onOpenChange?.(false); |
38 | 68 | } |
39 | 69 |
|
|
64 | 94 | label: 'System Message (will be disabled if left empty)', |
65 | 95 | type: 'textarea' |
66 | 96 | }, |
67 | | - { key: 'temperature', label: 'Temperature', type: 'input' }, |
68 | | - { key: 'top_k', label: 'Top K', type: 'input' }, |
69 | | - { key: 'top_p', label: 'Top P', type: 'input' }, |
70 | | - { key: 'min_p', label: 'Min P', type: 'input' }, |
71 | | - { key: 'max_tokens', label: 'Max Tokens', type: 'input' }, |
72 | | - { key: 'pasteLongTextToFileLen', label: 'Paste length to file', type: 'input' }, |
73 | | - { key: 'pdfAsImage', label: 'Parse PDF as image instead of text', type: 'checkbox' } |
| 97 | + { |
| 98 | + key: 'showTokensPerSecond', |
| 99 | + label: 'Show tokens per second', |
| 100 | + type: 'checkbox' |
| 101 | + }, |
| 102 | + { |
| 103 | + key: 'pasteLongTextToFileLen', |
| 104 | + label: 'Paste long text to file length', |
| 105 | + type: 'input' |
| 106 | + }, |
| 107 | + { |
| 108 | + key: 'pdfAsImage', |
| 109 | + label: 'Parse PDF as image', |
| 110 | + type: 'checkbox' |
| 111 | + } |
74 | 112 | ] |
75 | 113 | }, |
76 | 114 | { |
77 | 115 | title: 'Samplers', |
78 | 116 | icon: Filter, |
79 | 117 | fields: [ |
80 | | - { key: 'samplers', label: 'Samplers queue', type: 'input' }, |
81 | | - { key: 'dynatemp_range', label: 'Dynamic Temperature Range', type: 'input' }, |
82 | | - { key: 'dynatemp_exponent', label: 'Dynamic Temperature Exponent', type: 'input' }, |
83 | | - { key: 'typical_p', label: 'Typical P', type: 'input' }, |
84 | | - { key: 'xtc_probability', label: 'XTC Probability', type: 'input' }, |
85 | | - { key: 'xtc_threshold', label: 'XTC Threshold', type: 'input' } |
| 118 | + { |
| 119 | + key: 'samplers', |
| 120 | + label: 'Samplers', |
| 121 | + type: 'input' |
| 122 | + } |
86 | 123 | ] |
87 | 124 | }, |
88 | 125 | { |
89 | 126 | title: 'Penalties', |
90 | | - icon: Hand, |
| 127 | + icon: AlertTriangle, |
91 | 128 | fields: [ |
92 | | - { key: 'repeat_last_n', label: 'Repeat Last N', type: 'input' }, |
93 | | - { key: 'repeat_penalty', label: 'Repeat Penalty', type: 'input' }, |
94 | | - { key: 'presence_penalty', label: 'Presence Penalty', type: 'input' }, |
95 | | - { key: 'frequency_penalty', label: 'Frequency Penalty', type: 'input' }, |
96 | | - { key: 'dry_multiplier', label: 'DRY Multiplier', type: 'input' }, |
97 | | - { key: 'dry_base', label: 'DRY Base', type: 'input' }, |
98 | | - { key: 'dry_allowed_length', label: 'DRY Allowed Length', type: 'input' }, |
99 | | - { key: 'dry_penalty_last_n', label: 'DRY Penalty Last N', type: 'input' } |
| 129 | + { |
| 130 | + key: 'repeat_last_n', |
| 131 | + label: 'Repeat last N', |
| 132 | + type: 'input' |
| 133 | + }, |
| 134 | + { |
| 135 | + key: 'repeat_penalty', |
| 136 | + label: 'Repeat penalty', |
| 137 | + type: 'input' |
| 138 | + }, |
| 139 | + { |
| 140 | + key: 'presence_penalty', |
| 141 | + label: 'Presence penalty', |
| 142 | + type: 'input' |
| 143 | + }, |
| 144 | + { |
| 145 | + key: 'frequency_penalty', |
| 146 | + label: 'Frequency penalty', |
| 147 | + type: 'input' |
| 148 | + }, |
| 149 | + { |
| 150 | + key: 'dry_multiplier', |
| 151 | + label: 'DRY multiplier', |
| 152 | + type: 'input' |
| 153 | + }, |
| 154 | + { |
| 155 | + key: 'dry_base', |
| 156 | + label: 'DRY base', |
| 157 | + type: 'input' |
| 158 | + }, |
| 159 | + { |
| 160 | + key: 'dry_allowed_length', |
| 161 | + label: 'DRY allowed length', |
| 162 | + type: 'input' |
| 163 | + }, |
| 164 | + { |
| 165 | + key: 'dry_penalty_last_n', |
| 166 | + label: 'DRY penalty last N', |
| 167 | + type: 'input' |
| 168 | + } |
100 | 169 | ] |
101 | 170 | }, |
102 | 171 | { |
103 | 172 | title: 'Reasoning', |
104 | | - icon: MessageSquare, |
| 173 | + icon: Brain, |
105 | 174 | fields: [ |
106 | 175 | { |
107 | 176 | key: 'showThoughtInProgress', |
108 | | - label: 'Expand thought process by default when generating messages', |
| 177 | + label: 'Show thought in progress', |
109 | 178 | type: 'checkbox' |
110 | 179 | }, |
111 | 180 | { |
112 | 181 | key: 'excludeThoughtOnReq', |
113 | | - label: 'Exclude thought process when sending requests to API (Recommended for DeepSeek-R1)', |
| 182 | + label: 'Exclude thought on request', |
114 | 183 | type: 'checkbox' |
115 | 184 | } |
116 | 185 | ] |
117 | 186 | }, |
118 | 187 | { |
119 | 188 | title: 'Advanced', |
120 | | - icon: Plus, |
121 | | - fields: [ |
122 | | - { key: 'showTokensPerSecond', label: 'Show tokens per second', type: 'checkbox' }, |
123 | | - { key: 'custom', label: 'Custom parameters (JSON format)', type: 'textarea' } |
124 | | - ] |
125 | | - }, |
126 | | - { |
127 | | - title: 'Experimental', |
128 | | - icon: Beaker, |
| 189 | + icon: Cog, |
129 | 190 | fields: [ |
130 | 191 | { |
131 | | - key: 'pyInterpreterEnabled', |
132 | | - label: 'Enable Python interpreter', |
133 | | - type: 'checkbox', |
134 | | - help: 'This feature uses Pyodide to run Python code inside a Markdown code block. You will see a "Run" button on the code block, near the "Copy" button.' |
| 192 | + key: 'temperature', |
| 193 | + label: 'Temperature', |
| 194 | + type: 'input' |
| 195 | + }, |
| 196 | + { |
| 197 | + key: 'dynatemp_range', |
| 198 | + label: 'Dynamic temperature range', |
| 199 | + type: 'input' |
| 200 | + }, |
| 201 | + { |
| 202 | + key: 'dynatemp_exponent', |
| 203 | + label: 'Dynamic temperature exponent', |
| 204 | + type: 'input' |
| 205 | + }, |
| 206 | + { |
| 207 | + key: 'top_k', |
| 208 | + label: 'Top K', |
| 209 | + type: 'input' |
| 210 | + }, |
| 211 | + { |
| 212 | + key: 'top_p', |
| 213 | + label: 'Top P', |
| 214 | + type: 'input' |
| 215 | + }, |
| 216 | + { |
| 217 | + key: 'min_p', |
| 218 | + label: 'Min P', |
| 219 | + type: 'input' |
| 220 | + }, |
| 221 | + { |
| 222 | + key: 'xtc_probability', |
| 223 | + label: 'XTC probability', |
| 224 | + type: 'input' |
| 225 | + }, |
| 226 | + { |
| 227 | + key: 'xtc_threshold', |
| 228 | + label: 'XTC threshold', |
| 229 | + type: 'input' |
| 230 | + }, |
| 231 | + { |
| 232 | + key: 'typical_p', |
| 233 | + label: 'Typical P', |
| 234 | + type: 'input' |
| 235 | + }, |
| 236 | + { |
| 237 | + key: 'max_tokens', |
| 238 | + label: 'Max tokens', |
| 239 | + type: 'input' |
| 240 | + }, |
| 241 | + { |
| 242 | + key: 'custom', |
| 243 | + label: 'Custom JSON', |
| 244 | + type: 'textarea' |
135 | 245 | } |
136 | 246 | ] |
137 | 247 | } |
| 248 | + // TODO: Experimental features section will be implemented after initial release |
| 249 | + // This includes Python interpreter (Pyodide integration) and other experimental features |
| 250 | + // { |
| 251 | + // title: 'Experimental', |
| 252 | + // icon: Beaker, |
| 253 | + // fields: [ |
| 254 | + // { |
| 255 | + // key: 'pyInterpreterEnabled', |
| 256 | + // label: 'Enable Python interpreter', |
| 257 | + // type: 'checkbox' |
| 258 | + // } |
| 259 | + // ] |
| 260 | + // } |
138 | 261 | ]; |
139 | 262 |
|
140 | 263 | let currentSection = $derived( |
|
166 | 289 |
|
167 | 290 | <ScrollArea class="flex-1"> |
168 | 291 | <div class="space-y-6 p-6"> |
| 292 | + |
169 | 293 | <ChatSettingsSection title={currentSection.title} icon={currentSection.icon}> |
170 | 294 | {#each currentSection.fields as field} |
171 | 295 | <div class="space-y-2"> |
|
182 | 306 | placeholder={`Default: ${defaultConfig[field.key] || 'none'}`} |
183 | 307 | class="max-w-md" |
184 | 308 | /> |
185 | | - {#if field.help} |
| 309 | + {#if field.help || SETTING_CONFIG_INFO[field.key]} |
186 | 310 | <p class="text-muted-foreground mt-1 text-xs"> |
187 | | - {field.help} |
| 311 | + {field.help || SETTING_CONFIG_INFO[field.key]} |
188 | 312 | </p> |
189 | 313 | {/if} |
190 | 314 | {:else if field.type === 'textarea'} |
|
200 | 324 | placeholder={`Default: ${defaultConfig[field.key] || 'none'}`} |
201 | 325 | class="min-h-[100px] max-w-2xl" |
202 | 326 | /> |
203 | | - {#if field.help} |
| 327 | + {#if field.help || SETTING_CONFIG_INFO[field.key]} |
204 | 328 | <p class="text-muted-foreground mt-1 text-xs"> |
205 | | - {field.help} |
| 329 | + {field.help || SETTING_CONFIG_INFO[field.key]} |
206 | 330 | </p> |
207 | 331 | {/if} |
208 | 332 | {:else if field.type === 'checkbox'} |
|
225 | 349 | {field.label} |
226 | 350 | </label> |
227 | 351 |
|
228 | | - {#if field.help} |
| 352 | + {#if field.help || SETTING_CONFIG_INFO[field.key]} |
229 | 353 | <p class="text-muted-foreground text-xs"> |
230 | | - {field.help} |
| 354 | + {field.help || SETTING_CONFIG_INFO[field.key]} |
231 | 355 | </p> |
232 | 356 | {:else if field.key === 'pdfAsImage' && !supportsVision()} |
233 | 357 | <p class="text-muted-foreground text-xs"> |
|
0 commit comments