All checks were successful
Build AppImage / build (push) Successful in 7m52s
- update.rs: Umstellung auf Package-Registry-Manifest mit SHA256-Verify, Basic-Auth, dev/APPIMAGE/Nix-Wrapper-Modus. Liest binary_filename im Nix-Modus (AppImage laeuft auf NixOS nicht) - Nix-Wrapper-Paket (nix/default.nix): LD_LIBRARY_PATH-korrekter Launcher + Installer-Script, User-Home-Binary (writable fuer Auto-Update) - CI laedt jetzt AppImage UND natives Binary + update.json v2 (binary_filename/binary_sha256) in die Package Registry - Svelte: Store-basierter Update-Trigger, manueller Check im Settings-Panel, "Kein Update"-Dialog-Variante, expectedSha256-Param - install.sh: One-Click-Installer fuer NixOS (curl | bash) - sha2-Dep fuer Integritaets-Check des Downloads Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
504 lines
11 KiB
Svelte
504 lines
11 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from 'svelte';
|
|
import { invoke } from '@tauri-apps/api/core';
|
|
import { currentModel, agentMode, type AgentMode } from '$lib/stores/app';
|
|
import { updateCheckManual } from '$lib/stores/updateTrigger';
|
|
|
|
interface ModelInfo {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
}
|
|
|
|
let availableModels: ModelInfo[] = [];
|
|
let selectedModel = '';
|
|
let loading = true;
|
|
let saving = false;
|
|
let appVersion = '';
|
|
|
|
// 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 },
|
|
};
|
|
|
|
// Agent-Modi
|
|
interface AgentModeInfo {
|
|
id: AgentMode;
|
|
name: string;
|
|
icon: string;
|
|
description: string;
|
|
}
|
|
|
|
const agentModes: AgentModeInfo[] = [
|
|
{
|
|
id: 'solo',
|
|
name: 'Solo',
|
|
icon: '🎯',
|
|
description: 'Main Agent macht alles selbst. Schnell für einfache Aufgaben.'
|
|
},
|
|
{
|
|
id: 'handlanger',
|
|
name: 'Handlanger',
|
|
icon: '👷',
|
|
description: 'Main denkt, Sub-Agents führen exakt aus. Gut für koordinierte Aufgaben.'
|
|
},
|
|
{
|
|
id: 'experten',
|
|
name: 'Experten',
|
|
icon: '🧠',
|
|
description: 'Jeder Agent denkt selbst. Ideal für komplexe, parallelisierbare Aufgaben.'
|
|
},
|
|
{
|
|
id: 'auto',
|
|
name: 'Auto',
|
|
icon: '🔄',
|
|
description: 'Modus wird automatisch basierend auf Aufgaben-Komplexität gewählt.'
|
|
}
|
|
];
|
|
|
|
async function loadSettings() {
|
|
try {
|
|
availableModels = await invoke('get_available_models');
|
|
const current: string = await invoke('get_current_model');
|
|
selectedModel = current;
|
|
$currentModel = current;
|
|
|
|
// Agent-Modus laden
|
|
const currentMode: string = await invoke('get_agent_mode');
|
|
$agentMode = currentMode as AgentMode;
|
|
|
|
// Version laden
|
|
appVersion = await invoke('get_current_version');
|
|
} catch (err) {
|
|
console.error('Fehler beim Laden:', err);
|
|
}
|
|
loading = false;
|
|
}
|
|
|
|
function triggerUpdateCheck() {
|
|
updateCheckManual.set(true);
|
|
}
|
|
|
|
async function changeMode(modeId: AgentMode) {
|
|
if (modeId === $agentMode) return;
|
|
|
|
try {
|
|
await invoke('set_agent_mode', { mode: modeId });
|
|
$agentMode = modeId;
|
|
} catch (err) {
|
|
console.error('Fehler beim Modus-Wechsel:', err);
|
|
}
|
|
}
|
|
|
|
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>
|
|
|
|
<!-- Agent-Modus -->
|
|
<section class="settings-section">
|
|
<h3>🤝 Agent-Modus</h3>
|
|
<p class="section-hint">Wie sollen komplexe Aufgaben bearbeitet werden?</p>
|
|
|
|
<div class="mode-list">
|
|
{#each agentModes as mode}
|
|
<button
|
|
class="mode-card"
|
|
class:selected={$agentMode === mode.id}
|
|
on:click={() => changeMode(mode.id)}
|
|
>
|
|
<div class="mode-header">
|
|
<span class="mode-icon">{mode.icon}</span>
|
|
<span class="mode-name">{mode.name}</span>
|
|
{#if $agentMode === mode.id}
|
|
<span class="mode-active">✓ Aktiv</span>
|
|
{/if}
|
|
</div>
|
|
<div class="mode-description">{mode.description}</div>
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
|
|
<div class="mode-info">
|
|
{#if $agentMode === 'solo'}
|
|
<p>💡 <strong>Solo</strong> ist ideal für schnelle, einfache Aufgaben wie Typo-Fixes oder Code-Erklärungen.</p>
|
|
{:else if $agentMode === 'handlanger'}
|
|
<p>💡 <strong>Handlanger</strong> spart Context: Sub-Agents bekommen nur die nötigen Infos und liefern kompakte Zusammenfassungen.</p>
|
|
{:else if $agentMode === 'experten'}
|
|
<p>💡 <strong>Experten</strong> für komplexe Features: Research, Implement, Test und Review arbeiten parallel.</p>
|
|
{:else}
|
|
<p>💡 <strong>Auto</strong> analysiert die Aufgabe und wählt den passenden Modus automatisch.</p>
|
|
{/if}
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Weitere Einstellungen -->
|
|
<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>
|
|
|
|
<section class="settings-section">
|
|
<h3>🔄 Version & Updates</h3>
|
|
<div class="setting-row">
|
|
<span class="setting-label">Aktuelle Version</span>
|
|
<span class="setting-value">{appVersion || '—'}</span>
|
|
</div>
|
|
<div class="update-actions">
|
|
<button class="update-btn" on:click={triggerUpdateCheck}>
|
|
Nach Updates suchen
|
|
</button>
|
|
{#if appVersion === 'dev'}
|
|
<p class="dev-hint">
|
|
Entwicklungs-Build — Auto-Update ist deaktiviert.
|
|
Nur Pipeline-Builds können aktualisiert werden.
|
|
</p>
|
|
{/if}
|
|
</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;
|
|
}
|
|
|
|
/* Agent-Modus Karten */
|
|
.mode-list {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: var(--spacing-sm);
|
|
}
|
|
|
|
.mode-card {
|
|
text-align: left;
|
|
padding: var(--spacing-sm);
|
|
background: var(--bg-secondary);
|
|
border: 2px solid var(--bg-tertiary);
|
|
border-radius: var(--radius-md);
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.mode-card:hover {
|
|
background: var(--bg-tertiary);
|
|
border-color: var(--text-secondary);
|
|
}
|
|
|
|
.mode-card.selected {
|
|
border-color: var(--accent);
|
|
background: rgba(233, 69, 96, 0.1);
|
|
}
|
|
|
|
.mode-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-xs);
|
|
margin-bottom: var(--spacing-xs);
|
|
}
|
|
|
|
.mode-icon {
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.mode-name {
|
|
font-weight: 600;
|
|
font-size: 0.85rem;
|
|
flex: 1;
|
|
}
|
|
|
|
.mode-active {
|
|
font-size: 0.6rem;
|
|
padding: 1px 4px;
|
|
background: var(--accent);
|
|
color: white;
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
|
|
.mode-description {
|
|
font-size: 0.7rem;
|
|
color: var(--text-secondary);
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.mode-info {
|
|
margin-top: var(--spacing-md);
|
|
padding: var(--spacing-sm);
|
|
background: rgba(59, 130, 246, 0.1);
|
|
border: 1px solid rgba(59, 130, 246, 0.2);
|
|
border-radius: var(--radius-sm);
|
|
font-size: 0.75rem;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.mode-info p {
|
|
margin: 0;
|
|
}
|
|
|
|
.mode-info strong {
|
|
color: var(--accent);
|
|
}
|
|
|
|
/* Update-Sektion */
|
|
.update-actions {
|
|
margin-top: var(--spacing-sm);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--spacing-sm);
|
|
}
|
|
|
|
.update-btn {
|
|
align-self: flex-start;
|
|
padding: var(--spacing-sm) var(--spacing-md);
|
|
background: var(--accent);
|
|
color: white;
|
|
border: none;
|
|
border-radius: var(--radius-md);
|
|
font-size: 0.85rem;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.15s ease;
|
|
}
|
|
|
|
.update-btn:hover {
|
|
background: var(--accent-hover);
|
|
}
|
|
|
|
.dev-hint {
|
|
margin: 0;
|
|
padding: var(--spacing-sm);
|
|
font-size: 0.75rem;
|
|
color: var(--text-secondary);
|
|
background: rgba(234, 179, 8, 0.08);
|
|
border: 1px solid rgba(234, 179, 8, 0.2);
|
|
border-radius: var(--radius-sm);
|
|
line-height: 1.4;
|
|
}
|
|
</style>
|