- Neues SettingsPanel mit Modell-Auswahl (Haiku/Sonnet/Opus) - Modell wird in SQLite persistiert (claude_model Setting) - Bridge unterstützt set-model und get-models Commands - Modell kann zur Laufzeit gewechselt werden - Preisanzeige pro Modell im Settings-Panel - Aktuelles Modell wird beim App-Start geladen Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
277 lines
5.8 KiB
Svelte
277 lines
5.8 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from 'svelte';
|
|
import { invoke } from '@tauri-apps/api/core';
|
|
import { currentModel } from '$lib/stores/app';
|
|
|
|
interface ModelInfo {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
}
|
|
|
|
let availableModels: ModelInfo[] = [];
|
|
let selectedModel = '';
|
|
let loading = true;
|
|
let saving = false;
|
|
|
|
// Modell-Icons
|
|
const modelIcons: Record<string, string> = {
|
|
haiku: '🐦',
|
|
sonnet: '📝',
|
|
opus: '🎭',
|
|
};
|
|
|
|
// Preise pro 1M Token (ungefähre Werte)
|
|
const modelPrices: Record<string, { input: number; output: number }> = {
|
|
haiku: { input: 0.25, output: 1.25 },
|
|
sonnet: { input: 3, output: 15 },
|
|
opus: { input: 15, output: 75 },
|
|
};
|
|
|
|
async function loadSettings() {
|
|
try {
|
|
availableModels = await invoke('get_available_models');
|
|
const current: string = await invoke('get_current_model');
|
|
selectedModel = current;
|
|
$currentModel = current;
|
|
} catch (err) {
|
|
console.error('Fehler beim Laden:', err);
|
|
}
|
|
loading = false;
|
|
}
|
|
|
|
async function changeModel(modelId: string) {
|
|
if (modelId === selectedModel) return;
|
|
saving = true;
|
|
|
|
try {
|
|
await invoke('set_model', { model: modelId });
|
|
selectedModel = modelId;
|
|
$currentModel = modelId;
|
|
} catch (err) {
|
|
console.error('Fehler beim Speichern:', err);
|
|
}
|
|
|
|
saving = false;
|
|
}
|
|
|
|
onMount(() => {
|
|
loadSettings();
|
|
});
|
|
|
|
function formatPrice(price: number): string {
|
|
return `$${price.toFixed(2)}`;
|
|
}
|
|
</script>
|
|
|
|
<div class="settings-panel">
|
|
<div class="panel-header">
|
|
<h2>⚙️ Einstellungen</h2>
|
|
</div>
|
|
|
|
{#if loading}
|
|
<div class="loading">Lade Einstellungen...</div>
|
|
{:else}
|
|
<div class="settings-content">
|
|
<!-- Modell-Auswahl -->
|
|
<section class="settings-section">
|
|
<h3>🤖 Claude-Modell</h3>
|
|
<p class="section-hint">Wähle das Modell für deine Anfragen</p>
|
|
|
|
<div class="model-list">
|
|
{#each availableModels as model}
|
|
<button
|
|
class="model-card"
|
|
class:selected={selectedModel === model.id}
|
|
class:saving={saving && selectedModel !== model.id}
|
|
on:click={() => changeModel(model.id)}
|
|
disabled={saving}
|
|
>
|
|
<div class="model-header">
|
|
<span class="model-icon">{modelIcons[model.id] || '🤖'}</span>
|
|
<span class="model-name">{model.name}</span>
|
|
{#if selectedModel === model.id}
|
|
<span class="model-active">✓ Aktiv</span>
|
|
{/if}
|
|
</div>
|
|
<div class="model-description">{model.description}</div>
|
|
{#if modelPrices[model.id]}
|
|
<div class="model-pricing">
|
|
<span>Input: {formatPrice(modelPrices[model.id].input)}/1M</span>
|
|
<span>Output: {formatPrice(modelPrices[model.id].output)}/1M</span>
|
|
</div>
|
|
{/if}
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Weitere Einstellungen (Platzhalter) -->
|
|
<section class="settings-section">
|
|
<h3>🎨 Darstellung</h3>
|
|
<div class="setting-row">
|
|
<span class="setting-label">Theme</span>
|
|
<span class="setting-value">AWL Dark (fest)</span>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="settings-section">
|
|
<h3>📁 Pfade</h3>
|
|
<div class="setting-row">
|
|
<span class="setting-label">Arbeitsverzeichnis</span>
|
|
<span class="setting-value placeholder">Wird aus Session geladen</span>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
.settings-panel {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
}
|
|
|
|
.panel-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: var(--spacing-sm) var(--spacing-md);
|
|
background: var(--bg-secondary);
|
|
border-bottom: 1px solid var(--bg-tertiary);
|
|
}
|
|
|
|
.panel-header h2 {
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.loading {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 100%;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.settings-content {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: var(--spacing-md);
|
|
}
|
|
|
|
.settings-section {
|
|
margin-bottom: var(--spacing-lg);
|
|
}
|
|
|
|
.settings-section h3 {
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
margin-bottom: var(--spacing-xs);
|
|
}
|
|
|
|
.section-hint {
|
|
font-size: 0.75rem;
|
|
color: var(--text-secondary);
|
|
margin-bottom: var(--spacing-md);
|
|
}
|
|
|
|
/* Modell-Karten */
|
|
.model-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--spacing-sm);
|
|
}
|
|
|
|
.model-card {
|
|
width: 100%;
|
|
text-align: left;
|
|
padding: var(--spacing-md);
|
|
background: var(--bg-secondary);
|
|
border: 2px solid var(--bg-tertiary);
|
|
border-radius: var(--radius-md);
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.model-card:hover:not(:disabled) {
|
|
background: var(--bg-tertiary);
|
|
border-color: var(--text-secondary);
|
|
}
|
|
|
|
.model-card.selected {
|
|
border-color: var(--accent);
|
|
background: rgba(233, 69, 96, 0.1);
|
|
}
|
|
|
|
.model-card.saving {
|
|
opacity: 0.5;
|
|
cursor: wait;
|
|
}
|
|
|
|
.model-card:disabled {
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.model-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm);
|
|
margin-bottom: var(--spacing-xs);
|
|
}
|
|
|
|
.model-icon {
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
.model-name {
|
|
font-weight: 600;
|
|
flex: 1;
|
|
}
|
|
|
|
.model-active {
|
|
font-size: 0.7rem;
|
|
padding: 2px 6px;
|
|
background: var(--accent);
|
|
color: white;
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
|
|
.model-description {
|
|
font-size: 0.8rem;
|
|
color: var(--text-secondary);
|
|
margin-bottom: var(--spacing-xs);
|
|
}
|
|
|
|
.model-pricing {
|
|
display: flex;
|
|
gap: var(--spacing-md);
|
|
font-size: 0.7rem;
|
|
color: var(--text-secondary);
|
|
font-family: var(--font-mono);
|
|
}
|
|
|
|
/* Allgemeine Einstellungs-Zeilen */
|
|
.setting-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: var(--spacing-sm) 0;
|
|
border-bottom: 1px solid var(--bg-tertiary);
|
|
}
|
|
|
|
.setting-label {
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.setting-value {
|
|
font-size: 0.85rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.setting-value.placeholder {
|
|
font-style: italic;
|
|
opacity: 0.6;
|
|
}
|
|
</style>
|