feat(llm-playground): add model metadata system and SSD documentation

- Add MODEL_METADATA config for Ollama models with descriptions and modality
- Update default model to gemma3:4b
- Show model descriptions in ModelSelector and ComparisonSelector
- Add docs/OLLAMA_MODELS.md with instructions for adding new models
- Document external 4TB SSD setup in MAC_MINI_SERVER.md
- Add gemma3:12b, gemma3:27b, qwen2.5-coder:14b to model registry
This commit is contained in:
Till-JS 2026-02-01 00:24:34 +01:00
parent a341aa1b13
commit 213740411b
6 changed files with 310 additions and 14 deletions

View file

@ -42,6 +42,7 @@
class="flex cursor-pointer items-center gap-2 rounded p-2 transition-colors hover:bg-zinc-800"
class:opacity-50={isDisabled}
class:cursor-not-allowed={isDisabled}
title={model.description || ''}
>
<input
type="checkbox"
@ -50,9 +51,16 @@
disabled={isDisabled}
class="rounded"
/>
<span class="truncate text-sm" style="color: var(--color-text);">
{getModelDisplayName(model.id)}
</span>
<div class="min-w-0 flex-1">
<span class="block truncate text-sm" style="color: var(--color-text);">
{getModelDisplayName(model.id)}
</span>
{#if model.description}
<span class="block truncate text-xs" style="color: var(--color-text-muted);">
{model.description}
</span>
{/if}
</div>
</label>
{/each}
</div>

View file

@ -1,11 +1,19 @@
<script lang="ts">
import { modelsStore } from '$lib/stores/models.svelte';
import { modelsStore, MODEL_METADATA } from '$lib/stores/models.svelte';
import { settingsStore } from '$lib/stores/settings.svelte';
import { onMount } from 'svelte';
onMount(() => {
modelsStore.loadModels();
});
// Get description for currently selected model
const selectedModelDescription = $derived(() => {
const modelName = settingsStore.model.includes('/')
? settingsStore.model.split('/').slice(1).join('/')
: settingsStore.model;
return MODEL_METADATA[modelName]?.description;
});
</script>
<div>
@ -48,8 +56,11 @@
</optgroup>
{/each}
</select>
<p class="mt-1.5 text-xs" style="color: var(--color-text-muted);">
{modelsStore.models.length} models available
</p>
<div class="mt-1.5 text-xs" style="color: var(--color-text-muted);">
{#if selectedModelDescription()}
<p class="mb-0.5">{selectedModelDescription()}</p>
{/if}
<p>{modelsStore.models.length} models available</p>
</div>
{/if}
</div>

View file

@ -1,11 +1,75 @@
import type { Model, ModelWithModality, Modality, Provider } from '$lib/types';
import { getModels } from '$lib/api/llm';
// Detect modality from model ID
/**
* Model metadata configuration
* Add new models here when installing them on the server
* See: docs/OLLAMA_MODELS.md for instructions
*/
export const MODEL_METADATA: Record<string, { description: string; modality: Modality }> = {
// Text Models - General Purpose
'gemma3:4b': {
description: 'Fast general-purpose model (~53 t/s)',
modality: 'text',
},
'gemma3:12b': {
description: 'Balanced quality & speed (~30 t/s)',
modality: 'text',
},
'gemma3:27b': {
description: 'Best quality, slower (~15 t/s)',
modality: 'text',
},
'phi3.5:latest': {
description: 'Microsoft Phi-3.5 - compact & efficient',
modality: 'text',
},
'ministral-3:3b': {
description: 'Mistral Mini - fast for simple tasks',
modality: 'text',
},
// Vision Models
'llava:7b': {
description: 'Image understanding & description',
modality: 'vision',
},
'qwen3-vl:4b': {
description: 'Qwen Vision-Language model',
modality: 'vision',
},
'deepseek-ocr:latest': {
description: 'OCR & document understanding',
modality: 'vision',
},
// Code Models
'qwen2.5-coder:7b': {
description: 'Code generation & completion (7B)',
modality: 'code',
},
'qwen2.5-coder:14b': {
description: 'Advanced code generation (14B)',
modality: 'code',
},
};
/**
* Detect modality from model ID
* First checks MODEL_METADATA, then falls back to pattern matching
*/
function detectModality(modelId: string): Modality {
const id = modelId.toLowerCase();
// Vision models
// Extract model name from provider prefix (e.g., "ollama/gemma3:4b" -> "gemma3:4b")
const modelName = id.includes('/') ? id.split('/').slice(1).join('/') : id;
// Check metadata first
if (MODEL_METADATA[modelName]) {
return MODEL_METADATA[modelName].modality;
}
// Vision models (pattern matching fallback)
if (
id.includes('llava') ||
id.includes('vision') ||
@ -16,7 +80,7 @@ function detectModality(modelId: string): Modality {
return 'vision';
}
// Code models
// Code models (pattern matching fallback)
if (id.includes('coder') || id.includes('codellama') || id.includes('starcoder')) {
return 'code';
}
@ -25,6 +89,14 @@ function detectModality(modelId: string): Modality {
return 'text';
}
/**
* Get model description from metadata
*/
function getModelDescription(modelId: string): string | undefined {
const modelName = modelId.includes('/') ? modelId.split('/').slice(1).join('/') : modelId;
return MODEL_METADATA[modelName]?.description;
}
interface GroupedModels {
provider: Provider;
label: string;
@ -41,6 +113,7 @@ function createModelsStore() {
models.map((model) => ({
...model,
modality: detectModality(model.id),
description: getModelDescription(model.id),
}))
);

View file

@ -4,7 +4,7 @@ import { browser } from '$app/environment';
const STORAGE_KEY = 'llm-playground-settings';
const defaultSettings: Settings = {
model: 'ollama/llama3.2:3b',
model: 'ollama/gemma3:4b',
temperature: 0.7,
maxTokens: 2048,
topP: 1.0,