Files
vessel/frontend/src/lib/components/settings/ModelParametersPanel.svelte
vikingowl f4febf8973 fix: initialize custom parameters from model defaults
- Fetch actual model defaults from Ollama's /api/show endpoint
- Parse YAML-like parameters field (e.g., "temperature 0.7")
- Cache model defaults to avoid repeated API calls
- Initialize sliders with model's actual values when enabling custom params
- Show asterisk indicator when parameter differs from model default
- Reset button now restores to model defaults, not hardcoded values
2026-01-02 20:52:47 +01:00

164 lines
5.4 KiB
Svelte

<script lang="ts">
/**
* ModelParametersPanel - Collapsible panel for adjusting model generation parameters
* Shows sliders for temperature, top_k, top_p, and context length
*/
import { settingsState } from '$lib/stores/settings.svelte';
import { modelsState, type ModelDefaults } from '$lib/stores/models.svelte';
import {
PARAMETER_RANGES,
PARAMETER_LABELS,
PARAMETER_DESCRIPTIONS,
DEFAULT_MODEL_PARAMETERS,
type ModelParameters
} from '$lib/types/settings';
// Parameter keys for iteration
const parameterKeys: (keyof ModelParameters)[] = ['temperature', 'top_k', 'top_p', 'num_ctx'];
// Track model defaults for the selected model
let modelDefaults = $state<ModelDefaults>({});
// Fetch model defaults when panel opens or model changes
$effect(() => {
if (settingsState.isPanelOpen && modelsState.selectedId) {
modelsState.fetchModelDefaults(modelsState.selectedId).then((defaults) => {
modelDefaults = defaults;
});
}
});
/**
* Get the default value for a parameter (from model or hardcoded fallback)
*/
function getDefaultValue(key: keyof ModelParameters): number {
const modelValue = modelDefaults[key];
return modelValue ?? DEFAULT_MODEL_PARAMETERS[key];
}
/**
* Format a parameter value for display
*/
function formatValue(key: keyof ModelParameters, value: number): string {
switch (key) {
case 'temperature':
case 'top_p':
return value.toFixed(2);
case 'top_k':
return value.toString();
case 'num_ctx':
return value >= 1000 ? `${(value / 1000).toFixed(1)}K` : value.toString();
default:
return value.toString();
}
}
/**
* Get current value for a parameter
*/
function getValue(key: keyof ModelParameters): number {
return settingsState.modelParameters[key];
}
/**
* Handle slider change
*/
function handleSliderChange(key: keyof ModelParameters, event: Event): void {
const target = event.target as HTMLInputElement;
settingsState.updateParameter(key, parseFloat(target.value));
}
</script>
{#if settingsState.isPanelOpen}
<div
class="rounded-lg border border-theme/50 bg-theme-secondary/50 p-4"
role="region"
aria-label="Model parameters"
>
<!-- Header -->
<div class="mb-4 flex items-center justify-between">
<h3 class="text-sm font-medium text-theme-secondary">Model Parameters</h3>
<button
type="button"
onclick={() => settingsState.closePanel()}
class="rounded p-1 text-theme-muted hover:bg-theme-tertiary hover:text-theme-secondary"
aria-label="Close settings panel"
>
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<!-- Enable custom parameters toggle -->
<div class="mb-4 flex items-center justify-between">
<label class="flex items-center gap-2 text-sm text-theme-secondary">
<span>Use custom parameters</span>
</label>
<button
type="button"
role="switch"
aria-checked={settingsState.useCustomParameters}
onclick={() => settingsState.toggleCustomParameters(modelDefaults)}
class="relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 focus:ring-offset-theme-secondary {settingsState.useCustomParameters ? 'bg-sky-600' : 'bg-theme-tertiary'}"
>
<span
class="pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out {settingsState.useCustomParameters ? 'translate-x-4' : 'translate-x-0'}"
></span>
</button>
</div>
<!-- Parameters (disabled when not using custom) -->
<div class="space-y-4" class:opacity-50={!settingsState.useCustomParameters}>
{#each parameterKeys as key}
{@const range = PARAMETER_RANGES[key]}
{@const value = getValue(key)}
{@const isDefault = value === getDefaultValue(key)}
<div>
<div class="mb-1 flex items-center justify-between">
<label for="param-{key}" class="text-xs font-medium text-theme-secondary">
{PARAMETER_LABELS[key]}
{#if !isDefault}
<span class="ml-1 text-sky-400">*</span>
{/if}
</label>
<span class="font-mono text-xs text-theme-muted">
{formatValue(key, value)}
</span>
</div>
<input
type="range"
id="param-{key}"
min={range.min}
max={range.max}
step={range.step}
value={value}
oninput={(e) => handleSliderChange(key, e)}
disabled={!settingsState.useCustomParameters}
class="h-1.5 w-full cursor-pointer appearance-none rounded-lg bg-theme-tertiary disabled:cursor-not-allowed [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-sky-500 disabled:[&::-webkit-slider-thumb]:bg-theme-muted"
title={PARAMETER_DESCRIPTIONS[key]}
/>
<p class="mt-0.5 text-[10px] text-theme-muted">
{PARAMETER_DESCRIPTIONS[key]}
</p>
</div>
{/each}
</div>
<!-- Reset button -->
<div class="mt-4 flex justify-end">
<button
type="button"
onclick={() => settingsState.resetToDefaults(modelDefaults)}
class="rounded px-2 py-1 text-xs text-theme-muted hover:bg-theme-tertiary hover:text-theme-secondary"
>
Reset to defaults
</button>
</div>
</div>
{/if}