2 Commits

Author SHA1 Message Date
41bee19f6b chore: bump version to 0.4.2
Some checks failed
Create Release / release (push) Has been cancelled
2026-01-02 20:54:55 +01:00
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
5 changed files with 157 additions and 13 deletions

View File

@@ -18,7 +18,7 @@ import (
)
// Version is set at build time via -ldflags, or defaults to dev
var Version = "0.4.1"
var Version = "0.4.2"
func getEnvOrDefault(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {

View File

@@ -1,6 +1,6 @@
{
"name": "vessel",
"version": "0.4.1",
"version": "0.4.2",
"private": true,
"type": "module",
"scripts": {

View File

@@ -5,6 +5,7 @@
*/
import { settingsState } from '$lib/stores/settings.svelte';
import { modelsState, type ModelDefaults } from '$lib/stores/models.svelte';
import {
PARAMETER_RANGES,
PARAMETER_LABELS,
@@ -16,6 +17,26 @@
// 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
*/
@@ -79,7 +100,7 @@
type="button"
role="switch"
aria-checked={settingsState.useCustomParameters}
onclick={() => settingsState.toggleCustomParameters()}
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
@@ -93,7 +114,7 @@
{#each parameterKeys as key}
{@const range = PARAMETER_RANGES[key]}
{@const value = getValue(key)}
{@const isDefault = value === DEFAULT_MODEL_PARAMETERS[key]}
{@const isDefault = value === getDefaultValue(key)}
<div>
<div class="mb-1 flex items-center justify-between">
@@ -132,7 +153,7 @@
<div class="mt-4 flex justify-end">
<button
type="button"
onclick={() => settingsState.resetToDefaults()}
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

View File

@@ -65,6 +65,14 @@ export interface ModelUpdateStatus {
localModifiedAt: string;
}
/** Model default parameters from Ollama */
export interface ModelDefaults {
temperature?: number;
top_k?: number;
top_p?: number;
num_ctx?: number;
}
/** Models state class with reactive properties */
export class ModelsState {
// Core state
@@ -81,6 +89,10 @@ export class ModelsState {
private capabilitiesCache = $state<Map<string, OllamaCapability[]>>(new Map());
private capabilitiesFetching = new Set<string>();
// Model defaults cache: modelName -> default parameters
private modelDefaultsCache = $state<Map<string, ModelDefaults>>(new Map());
private modelDefaultsFetching = new Set<string>();
// Derived: Currently selected model
selected = $derived.by(() => {
if (!this.selectedId) return null;
@@ -429,6 +441,99 @@ export class ModelsState {
}
return count;
}
// =========================================================================
// Model Defaults
// =========================================================================
/**
* Parse model parameters from the Ollama show response
* The parameters field contains lines like "temperature 0.7" or "num_ctx 4096"
*/
private parseModelParameters(parametersStr: string): ModelDefaults {
const defaults: ModelDefaults = {};
if (!parametersStr) return defaults;
const lines = parametersStr.split('\n');
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) continue;
// Parse "key value" format
const spaceIndex = trimmed.indexOf(' ');
if (spaceIndex === -1) continue;
const key = trimmed.substring(0, spaceIndex).toLowerCase();
const value = trimmed.substring(spaceIndex + 1).trim();
switch (key) {
case 'temperature':
defaults.temperature = parseFloat(value);
break;
case 'top_k':
defaults.top_k = parseInt(value, 10);
break;
case 'top_p':
defaults.top_p = parseFloat(value);
break;
case 'num_ctx':
defaults.num_ctx = parseInt(value, 10);
break;
}
}
return defaults;
}
/**
* Fetch model defaults from Ollama /api/show
*/
async fetchModelDefaults(modelName: string): Promise<ModelDefaults> {
// Check cache first
const cached = this.modelDefaultsCache.get(modelName);
if (cached) return cached;
// Avoid duplicate fetches
if (this.modelDefaultsFetching.has(modelName)) {
await new Promise((r) => setTimeout(r, 100));
return this.modelDefaultsCache.get(modelName) ?? {};
}
this.modelDefaultsFetching.add(modelName);
try {
const response = await ollamaClient.showModel(modelName);
const defaults = this.parseModelParameters(response.parameters);
// Update cache reactively
const newCache = new Map(this.modelDefaultsCache);
newCache.set(modelName, defaults);
this.modelDefaultsCache = newCache;
return defaults;
} catch (err) {
console.warn(`Failed to fetch defaults for ${modelName}:`, err);
return {};
} finally {
this.modelDefaultsFetching.delete(modelName);
}
}
/**
* Get cached model defaults (returns empty if not fetched)
*/
getModelDefaults(modelName: string): ModelDefaults {
return this.modelDefaultsCache.get(modelName) ?? {};
}
/**
* Get defaults for selected model
*/
get selectedModelDefaults(): ModelDefaults {
if (!this.selectedId) return {};
return this.modelDefaultsCache.get(this.selectedId) ?? {};
}
}
/** Singleton models state instance */

View File

@@ -10,6 +10,7 @@ import {
DEFAULT_CHAT_SETTINGS,
PARAMETER_RANGES
} from '$lib/types/settings';
import type { ModelDefaults } from './models.svelte';
const STORAGE_KEY = 'vessel-settings';
@@ -79,12 +80,30 @@ export class SettingsState {
/**
* Toggle whether to use custom parameters
* When enabling, optionally initialize from model defaults
*/
toggleCustomParameters(): void {
toggleCustomParameters(modelDefaults?: ModelDefaults): void {
this.useCustomParameters = !this.useCustomParameters;
// When enabling custom parameters, initialize from model defaults if provided
if (this.useCustomParameters && modelDefaults) {
this.initializeFromModelDefaults(modelDefaults);
}
this.saveToStorage();
}
/**
* Initialize parameters from model defaults
* Falls back to hardcoded defaults for any missing values
*/
initializeFromModelDefaults(modelDefaults: ModelDefaults): void {
this.temperature = modelDefaults.temperature ?? DEFAULT_MODEL_PARAMETERS.temperature;
this.top_k = modelDefaults.top_k ?? DEFAULT_MODEL_PARAMETERS.top_k;
this.top_p = modelDefaults.top_p ?? DEFAULT_MODEL_PARAMETERS.top_p;
this.num_ctx = modelDefaults.num_ctx ?? DEFAULT_MODEL_PARAMETERS.num_ctx;
}
/**
* Update a single parameter
*/
@@ -112,14 +131,13 @@ export class SettingsState {
}
/**
* Reset all parameters to defaults
* Reset all parameters to model defaults (or hardcoded defaults if not available)
*/
resetToDefaults(): void {
this.temperature = DEFAULT_MODEL_PARAMETERS.temperature;
this.top_k = DEFAULT_MODEL_PARAMETERS.top_k;
this.top_p = DEFAULT_MODEL_PARAMETERS.top_p;
this.num_ctx = DEFAULT_MODEL_PARAMETERS.num_ctx;
this.useCustomParameters = false;
resetToDefaults(modelDefaults?: ModelDefaults): void {
this.temperature = modelDefaults?.temperature ?? DEFAULT_MODEL_PARAMETERS.temperature;
this.top_k = modelDefaults?.top_k ?? DEFAULT_MODEL_PARAMETERS.top_k;
this.top_p = modelDefaults?.top_p ?? DEFAULT_MODEL_PARAMETERS.top_p;
this.num_ctx = modelDefaults?.num_ctx ?? DEFAULT_MODEL_PARAMETERS.num_ctx;
this.saveToStorage();
}