Protokoll #2134

ID2134
Zeitstempel2025-12-21 09:53:43.731051
Clientroot
IP145.224.96.190
Modellclaude-sonnet-4-20250514
Statuscompleted
Tokens7,226 (Input: 1,531, Output: 5,695)
Dauer87 ms
Request-Zeit2025-12-21 09:53:43.731051
Response-Zeit2025-12-21 09:53:43.818239

Request

{
    "event": "PreToolUse",
    "tool_name": "Edit",
    "tool_input": {
        "file_path": "\/var\/www\/dev.campus.systemische-tools.de\/docs\/plan-form-components-refactoring.md",
        "old_string": "# Plan: Einheitliche Form-Komponenten (Chat & Content Studio)\n\n## Status: PLANUNG\n**Erstellt:** 2025-12-21\n**Ziel:** DRY, KISS, SRP - Shared Partials für alle wiederverwendbaren Form-Elemente\n\n---\n\n## 1. IST-Analyse",
        "new_string": "# Plan: Einheitliche Form-Komponenten (Chat & Content Studio)\n\n## Status: PLANUNG\n**Erstellt:** 2025-12-21\n**Ziel:** DRY, KISS, SRP - Shared Partials für alle wiederverwendbaren Form-Elemente\n\n---\n\n## 0. LLM-Verwaltung (Grundlage für Modell-Dropdown)\n\n### 0.1 Architektur-Entscheidung\n\n**Gewählt: Variante B - Dedizierte LLM-Verwaltung**\n\nStatt dynamischer API-Abfrage bei jedem Seitenaufruf:\n- Zentrale Admin-Seite `\/ressourcen\/llm`\n- Datenbank-Tabelle als Single Source of Truth\n- Sync-Buttons für Provider (Anthropic, Ollama, weitere)\n- Sprechende Namen und Zusatzmetadaten\n\n### 0.2 Datenbank-Tabelle `llm_models`\n\n```sql\nCREATE TABLE llm_models (\n    id INT AUTO_INCREMENT PRIMARY KEY,\n    provider ENUM('anthropic', 'ollama', 'openai', 'google', 'mistral', 'custom') NOT NULL,\n    model_id VARCHAR(100) NOT NULL,        -- API-ID: \"claude-opus-4-5-20251101\"\n    display_name VARCHAR(100) NOT NULL,    -- Anzeige: \"Claude Opus 4.5\"\n    description TEXT,                      -- Kurzbeschreibung\n    context_window INT,                    -- Max. Tokens Input: 200000\n    max_output_tokens INT,                 -- Max. Tokens Output: 8192\n    input_price_per_mtok DECIMAL(10,4),    -- Preis Input $\/MTok\n    output_price_per_mtok DECIMAL(10,4),   -- Preis Output $\/MTok\n    capabilities JSON,                     -- {\"vision\": true, \"function_calling\": true}\n    is_local BOOLEAN DEFAULT FALSE,        -- Lokal (Ollama) vs. Cloud\n    is_active BOOLEAN DEFAULT TRUE,        -- In Dropdowns anzeigen?\n    sort_order INT DEFAULT 0,              -- Reihenfolge in Dropdowns\n    last_synced_at DATETIME,               -- Letzte Synchronisation\n    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n    UNIQUE KEY unique_provider_model (provider, model_id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;\n```\n\n### 0.3 Admin-Seite `\/ressourcen\/llm`\n\n**URL:** `\/ressourcen\/llm`\n**Controller:** `RessourcenController::llmIndex()`\n\n**Funktionen:**\n\n| Button | Aktion |\n|--------|--------|\n| \"Anthropic synchronisieren\" | `GET api.anthropic.com\/v1\/models` → DB aktualisieren |\n| \"Ollama synchronisieren\" | `ollama list` → DB aktualisieren |\n| \"Neuer Provider\" | Manuell weiteren Provider hinzufügen |\n\n**Tabellen-Ansicht:**\n\n| Provider | Model-ID | Display-Name | Context | Preis In\/Out | Lokal | Aktiv | Aktionen |\n|----------|----------|--------------|---------|--------------|-------|-------|----------|\n| Anthropic | claude-opus-4-5-20251101 | Claude Opus 4.5 | 200k | $15\/$75 | - | ✓ | Bearbeiten |\n| Ollama | mistral:latest | Mistral 7B | 32k | - | ✓ | ✓ | Bearbeiten |\n\n**Bearbeiten-Dialog:**\n- Display-Name ändern\n- Beschreibung hinzufügen\n- Context-Window \/ Max-Output korrigieren\n- Preise eintragen (für Kostenberechnung)\n- Aktivieren\/Deaktivieren\n- Sortierung ändern\n\n### 0.4 Sync-Logik\n\n**Anthropic Sync:**\n```php\n\/\/ GET https:\/\/api.anthropic.com\/v1\/models\n\/\/ Response: {\"data\": [{\"id\": \"claude-opus-4-5-20251101\", ...}]}\n\nforeach ($apiModels as $model) {\n    \/\/ INSERT ... ON DUPLICATE KEY UPDATE\n    \/\/ Neue Modelle: is_active = true, display_name = model_id (initial)\n    \/\/ Existierende: last_synced_at aktualisieren\n    \/\/ Fehlende: NICHT löschen, nur last_synced_at bleibt alt\n}\n```\n\n**Ollama Sync:**\n```php\n\/\/ $ ollama list\n\/\/ NAME              ID           SIZE    MODIFIED\n\/\/ mistral:latest    abc123...    4.1 GB  2 days ago\n\n$output = shell_exec('ollama list');\n\/\/ Parsen und in DB einfügen\n```\n\n### 0.5 Dropdown-Query\n\n```php\n\/\/ ModelService::getActiveModels()\npublic function getActiveModels(): array\n{\n    return $this->db->query(\"\n        SELECT model_id, display_name, provider, is_local, context_window\n        FROM llm_models\n        WHERE is_active = 1\n        ORDER BY is_local ASC, sort_order ASC, display_name ASC\n    \")->fetchAll();\n}\n```\n\n### 0.6 Partial nutzt DB-Daten\n\n```php\n\/\/ \/src\/View\/partials\/form\/model-select.php\n<?php\n$models = $models ?? [];\n$selected = $selected ?? '';\n$variant = $variant ?? 'default';\n$class = $variant === 'inline' ? 'form-select--inline' : 'form-select';\n?>\n<select name=\"model\" id=\"model\" class=\"<?= $class ?>\">\n    <optgroup label=\"Cloud\">\n        <?php foreach ($models as $m): ?>\n        <?php if (!$m['is_local']): ?>\n        <option value=\"<?= $m['model_id'] ?>\" <?= $selected === $m['model_id'] ? 'selected' : '' ?>>\n            <?= htmlspecialchars($m['display_name']) ?>\n        <\/option>\n        <?php endif; ?>\n        <?php endforeach; ?>\n    <\/optgroup>\n    <optgroup label=\"Lokal\">\n        <?php foreach ($models as $m): ?>\n        <?php if ($m['is_local']): ?>\n        <option value=\"<?= $m['model_id'] ?>\" <?= $selected === $m['model_id'] ? 'selected' : '' ?>>\n            <?= htmlspecialchars($m['display_name']) ?>\n        <\/option>\n        <?php endif; ?>\n        <?php endforeach; ?>\n    <\/optgroup>\n<\/select>\n```\n\n### 0.7 Migrations-Schritte (LLM-Verwaltung)\n\n1. [ ] Tabelle `llm_models` in `ki_dev` erstellen\n2. [ ] `LlmModelRepository` erstellen\n3. [ ] `LlmSyncService` erstellen (Anthropic + Ollama)\n4. [ ] Route `\/ressourcen\/llm` anlegen\n5. [ ] `RessourcenController::llmIndex()` implementieren\n6. [ ] View `\/ressourcen\/llm\/index.php` erstellen\n7. [ ] Sync-Buttons implementieren\n8. [ ] Bearbeiten-Funktionalität\n9. [ ] Initial-Sync durchführen (bestehende Modelle importieren)\n10. [ ] `ModelConfig.php` durch `LlmModelRepository` ersetzen\n11. [ ] Partial `model-select.php` erstellen\n12. [ ] Chat und Content auf Partial umstellen\n\n---\n\n## 1. IST-Analyse"
    }
}

Response

{
    "tool_response": {
        "filePath": "\/var\/www\/dev.campus.systemische-tools.de\/docs\/plan-form-components-refactoring.md",
        "oldString": "# Plan: Einheitliche Form-Komponenten (Chat & Content Studio)\n\n## Status: PLANUNG\n**Erstellt:** 2025-12-21\n**Ziel:** DRY, KISS, SRP - Shared Partials für alle wiederverwendbaren Form-Elemente\n\n---\n\n## 1. IST-Analyse",
        "newString": "# Plan: Einheitliche Form-Komponenten (Chat & Content Studio)\n\n## Status: PLANUNG\n**Erstellt:** 2025-12-21\n**Ziel:** DRY, KISS, SRP - Shared Partials für alle wiederverwendbaren Form-Elemente\n\n---\n\n## 0. LLM-Verwaltung (Grundlage für Modell-Dropdown)\n\n### 0.1 Architektur-Entscheidung\n\n**Gewählt: Variante B - Dedizierte LLM-Verwaltung**\n\nStatt dynamischer API-Abfrage bei jedem Seitenaufruf:\n- Zentrale Admin-Seite `\/ressourcen\/llm`\n- Datenbank-Tabelle als Single Source of Truth\n- Sync-Buttons für Provider (Anthropic, Ollama, weitere)\n- Sprechende Namen und Zusatzmetadaten\n\n### 0.2 Datenbank-Tabelle `llm_models`\n\n```sql\nCREATE TABLE llm_models (\n    id INT AUTO_INCREMENT PRIMARY KEY,\n    provider ENUM('anthropic', 'ollama', 'openai', 'google', 'mistral', 'custom') NOT NULL,\n    model_id VARCHAR(100) NOT NULL,        -- API-ID: \"claude-opus-4-5-20251101\"\n    display_name VARCHAR(100) NOT NULL,    -- Anzeige: \"Claude Opus 4.5\"\n    description TEXT,                      -- Kurzbeschreibung\n    context_window INT,                    -- Max. Tokens Input: 200000\n    max_output_tokens INT,                 -- Max. Tokens Output: 8192\n    input_price_per_mtok DECIMAL(10,4),    -- Preis Input $\/MTok\n    output_price_per_mtok DECIMAL(10,4),   -- Preis Output $\/MTok\n    capabilities JSON,                     -- {\"vision\": true, \"function_calling\": true}\n    is_local BOOLEAN DEFAULT FALSE,        -- Lokal (Ollama) vs. Cloud\n    is_active BOOLEAN DEFAULT TRUE,        -- In Dropdowns anzeigen?\n    sort_order INT DEFAULT 0,              -- Reihenfolge in Dropdowns\n    last_synced_at DATETIME,               -- Letzte Synchronisation\n    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n    UNIQUE KEY unique_provider_model (provider, model_id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;\n```\n\n### 0.3 Admin-Seite `\/ressourcen\/llm`\n\n**URL:** `\/ressourcen\/llm`\n**Controller:** `RessourcenController::llmIndex()`\n\n**Funktionen:**\n\n| Button | Aktion |\n|--------|--------|\n| \"Anthropic synchronisieren\" | `GET api.anthropic.com\/v1\/models` → DB aktualisieren |\n| \"Ollama synchronisieren\" | `ollama list` → DB aktualisieren |\n| \"Neuer Provider\" | Manuell weiteren Provider hinzufügen |\n\n**Tabellen-Ansicht:**\n\n| Provider | Model-ID | Display-Name | Context | Preis In\/Out | Lokal | Aktiv | Aktionen |\n|----------|----------|--------------|---------|--------------|-------|-------|----------|\n| Anthropic | claude-opus-4-5-20251101 | Claude Opus 4.5 | 200k | $15\/$75 | - | ✓ | Bearbeiten |\n| Ollama | mistral:latest | Mistral 7B | 32k | - | ✓ | ✓ | Bearbeiten |\n\n**Bearbeiten-Dialog:**\n- Display-Name ändern\n- Beschreibung hinzufügen\n- Context-Window \/ Max-Output korrigieren\n- Preise eintragen (für Kostenberechnung)\n- Aktivieren\/Deaktivieren\n- Sortierung ändern\n\n### 0.4 Sync-Logik\n\n**Anthropic Sync:**\n```php\n\/\/ GET https:\/\/api.anthropic.com\/v1\/models\n\/\/ Response: {\"data\": [{\"id\": \"claude-opus-4-5-20251101\", ...}]}\n\nforeach ($apiModels as $model) {\n    \/\/ INSERT ... ON DUPLICATE KEY UPDATE\n    \/\/ Neue Modelle: is_active = true, display_name = model_id (initial)\n    \/\/ Existierende: last_synced_at aktualisieren\n    \/\/ Fehlende: NICHT löschen, nur last_synced_at bleibt alt\n}\n```\n\n**Ollama Sync:**\n```php\n\/\/ $ ollama list\n\/\/ NAME              ID           SIZE    MODIFIED\n\/\/ mistral:latest    abc123...    4.1 GB  2 days ago\n\n$output = shell_exec('ollama list');\n\/\/ Parsen und in DB einfügen\n```\n\n### 0.5 Dropdown-Query\n\n```php\n\/\/ ModelService::getActiveModels()\npublic function getActiveModels(): array\n{\n    return $this->db->query(\"\n        SELECT model_id, display_name, provider, is_local, context_window\n        FROM llm_models\n        WHERE is_active = 1\n        ORDER BY is_local ASC, sort_order ASC, display_name ASC\n    \")->fetchAll();\n}\n```\n\n### 0.6 Partial nutzt DB-Daten\n\n```php\n\/\/ \/src\/View\/partials\/form\/model-select.php\n<?php\n$models = $models ?? [];\n$selected = $selected ?? '';\n$variant = $variant ?? 'default';\n$class = $variant === 'inline' ? 'form-select--inline' : 'form-select';\n?>\n<select name=\"model\" id=\"model\" class=\"<?= $class ?>\">\n    <optgroup label=\"Cloud\">\n        <?php foreach ($models as $m): ?>\n        <?php if (!$m['is_local']): ?>\n        <option value=\"<?= $m['model_id'] ?>\" <?= $selected === $m['model_id'] ? 'selected' : '' ?>>\n            <?= htmlspecialchars($m['display_name']) ?>\n        <\/option>\n        <?php endif; ?>\n        <?php endforeach; ?>\n    <\/optgroup>\n    <optgroup label=\"Lokal\">\n        <?php foreach ($models as $m): ?>\n        <?php if ($m['is_local']): ?>\n        <option value=\"<?= $m['model_id'] ?>\" <?= $selected === $m['model_id'] ? 'selected' : '' ?>>\n            <?= htmlspecialchars($m['display_name']) ?>\n        <\/option>\n        <?php endif; ?>\n        <?php endforeach; ?>\n    <\/optgroup>\n<\/select>\n```\n\n### 0.7 Migrations-Schritte (LLM-Verwaltung)\n\n1. [ ] Tabelle `llm_models` in `ki_dev` erstellen\n2. [ ] `LlmModelRepository` erstellen\n3. [ ] `LlmSyncService` erstellen (Anthropic + Ollama)\n4. [ ] Route `\/ressourcen\/llm` anlegen\n5. [ ] `RessourcenController::llmIndex()` implementieren\n6. [ ] View `\/ressourcen\/llm\/index.php` erstellen\n7. [ ] Sync-Buttons implementieren\n8. [ ] Bearbeiten-Funktionalität\n9. [ ] Initial-Sync durchführen (bestehende Modelle importieren)\n10. [ ] `ModelConfig.php` durch `LlmModelRepository` ersetzen\n11. [ ] Partial `model-select.php` erstellen\n12. [ ] Chat und Content auf Partial umstellen\n\n---\n\n## 1. IST-Analyse",
        "originalFile": "# Plan: Einheitliche Form-Komponenten (Chat & Content Studio)\n\n## Status: PLANUNG\n**Erstellt:** 2025-12-21\n**Ziel:** DRY, KISS, SRP - Shared Partials für alle wiederverwendbaren Form-Elemente\n\n---\n\n## 1. IST-Analyse\n\n### 1.1 Betroffene Views\n\n| View | Pfad | Verwendung |\n|------|------|------------|\n| Chat | `\/src\/View\/chat\/index.php` | Session-basierter Chat mit Einstellungen |\n| Content New | `\/src\/View\/content\/new.php` | Neuer Content-Auftrag erstellen |\n| Content Show | `\/src\/View\/content\/show.php` | Content-Details mit Generierungs-Optionen |\n\n### 1.2 Gemeinsame Elemente (aktuell inkonsistent)\n\n| Element | Chat | Content New | Content Show |\n|---------|------|-------------|--------------|\n| Modell-Dropdown | ✓ (optgroups) | ✓ (flach) | ✓ (inline) |\n| Collections | ✓ (multi-select) | ✓ (checkboxes) | ✓ (single-select) |\n| Context Limit | ✓ | ✓ | ✓ |\n| Author Profile | ✓ | ✓ | - |\n| System Prompt | ✓ | - | - |\n| Temperature | ✓ | - | - |\n| Max Tokens | ✓ | - | - |\n| Contract | - | ✓ | - |\n| Structure | - | ✓ | - |\n\n### 1.3 Detaillierte Inkonsistenzen\n\n#### Modell-Dropdown\n```\nChat:\n- <select name=\"model\"> (keine Klasse)\n- <optgroup label=\"Anthropic\"> \/ <optgroup label=\"Ollama (lokal)\">\n- Default aus $defaultModel\n\nContent New:\n- <select id=\"model\" name=\"model\" class=\"form-select\">\n- Flache Liste ohne Gruppierung\n- Default hardcoded: 'claude-sonnet-4-20250514'\n\nContent Show:\n- <select name=\"model\" class=\"form-select--inline\">\n- Flache Liste\n- Default aus $orderModel\n```\n\n#### Collections\n```\nChat:\n- <select name=\"collections[]\" multiple class=\"collections-select\">\n- Label-Mapping via match(): 'documents' → 'Dokumente'\n\nContent New:\n- <input type=\"checkbox\" name=\"collections[]\">\n- Kein Label-Mapping, zeigt rohe Namen\n\nContent Show:\n- <select name=\"collection\" class=\"form-select--inline\"> (SINGLE, nicht multi!)\n- Kein Label-Mapping\n```\n\n#### Context Limit\n```\nChat:\n- <select name=\"context_limit\"> (keine Klasse, kein ID)\n\nContent New:\n- <select id=\"context_limit\" name=\"context_limit\" class=\"form-select\">\n- Mit Label \"Kontext-Quellen\"\n\nContent Show:\n- <select name=\"context_limit\" class=\"form-select--inline\">\n```\n\n#### Author Profile\n```\nChat:\n- Variable: $authorProfiles\n- Default: value=\"0\", Text: \"Kein Profil\"\n\nContent New:\n- Variable: $profiles\n- Default: value=\"\", Text: \"-- Kein Profil --\"\n```\n\n---\n\n## 2. SOLL-Architektur\n\n### 2.1 Verzeichnisstruktur\n\n```\n\/src\/View\/\n├── partials\/\n│   └── form\/\n│       ├── model-select.php      # Modell-Dropdown (mit Optgroups)\n│       ├── collections.php       # Collections (einheitlich)\n│       ├── context-limit.php     # Context Limit\n│       ├── author-profile.php    # Autorenprofil\n│       ├── system-prompt.php     # System Prompt\n│       ├── temperature.php       # Temperature Slider\n│       ├── max-tokens.php        # Max Tokens\n│       ├── contract.php          # Contract\n│       └── structure.php         # Struktur\n├── chat\/\n│   └── index.php                 # Nutzt Partials\n└── content\/\n    ├── new.php                   # Nutzt Partials\n    └── show.php                  # Nutzt Partials\n```\n\n### 2.2 Partial-Interface (Konvention)\n\nJedes Partial akzeptiert ein `$options`-Array:\n\n```php\n<?php\n\/**\n * @param array $options [\n *     'models'    => array,      \/\/ ModelConfig::getAll()\n *     'selected'  => string,     \/\/ Aktuell gewählter Wert\n *     'name'      => string,     \/\/ Form-Name (default: 'model')\n *     'id'        => string,     \/\/ HTML-ID (optional)\n *     'class'     => string,     \/\/ CSS-Klasse (default: 'form-select')\n *     'variant'   => string,     \/\/ 'default' | 'inline' | 'compact'\n *     'label'     => string,     \/\/ Label-Text (optional)\n *     'required'  => bool,       \/\/ Required-Attribut\n * ]\n *\/\n```\n\n### 2.3 Einheitliche Variablennamen\n\n| Element | Variable (Controller) | Variable (View) |\n|---------|----------------------|-----------------|\n| Modelle | `ModelConfig::getAll()` | `$models` |\n| Default-Modell | `ModelConfig::DEFAULT_MODEL` | `$defaultModel` |\n| Collections | `$qdrantService->listCollections()` | `$collections` |\n| Author Profiles | `findByType('author_profile')` | `$authorProfiles` |\n| System Prompts | `findByType('system_prompt')` | `$systemPrompts` |\n| Contracts | `findByType('contract')` | `$contracts` |\n| Structures | `findByType('structure')` | `$structures` |\n\n### 2.4 Collection Label-Mapping (zentral)\n\n```php\n\/\/ \/src\/Infrastructure\/AI\/CollectionConfig.php\nfinal class CollectionConfig\n{\n    public const LABELS = [\n        'documents' => 'Dokumente',\n        'dokumentation_chunks' => 'Doku-Chunks',\n        'mail' => 'E-Mails',\n        'entities' => 'Entitäten',\n    ];\n\n    public static function getLabel(string $collection): string\n    {\n        return self::LABELS[$collection] ?? ucfirst($collection);\n    }\n}\n```\n\n---\n\n## 3. Partial-Spezifikationen\n\n### 3.1 model-select.php\n\n**Input:**\n```php\n$options = [\n    'models' => $models,           \/\/ Required: ModelConfig::getAll()\n    'selected' => $currentModel,   \/\/ Optional: aktueller Wert\n    'name' => 'model',             \/\/ Optional: default 'model'\n    'variant' => 'default',        \/\/ Optional: 'default' | 'inline'\n    'show_optgroups' => true,      \/\/ Optional: Anthropic\/Ollama trennen\n];\n```\n\n**Output:**\n```html\n<select name=\"model\" id=\"model\" class=\"form-select\">\n    <optgroup label=\"Anthropic\">\n        <option value=\"claude-opus-4-5-20251101\">Claude Opus 4.5<\/option>\n        <option value=\"claude-sonnet-4-20250514\" selected>Claude Sonnet 4<\/option>\n    <\/optgroup>\n    <optgroup label=\"Ollama (lokal)\">\n        <option value=\"ollama:mistral:latest\">Mistral (lokal)<\/option>\n        ...\n    <\/optgroup>\n<\/select>\n```\n\n### 3.2 collections.php\n\n**Input:**\n```php\n$options = [\n    'collections' => $collections,     \/\/ Required: Liste\n    'selected' => $selectedCollections, \/\/ Optional: array oder string\n    'name' => 'collections[]',         \/\/ Optional\n    'variant' => 'default',            \/\/ 'default' (multi-select) | 'single' | 'checkbox'\n];\n```\n\n**Output (default = multi-select):**\n```html\n<select name=\"collections[]\" multiple class=\"form-select collections-select\">\n    <option value=\"documents\" selected>Dokumente<\/option>\n    <option value=\"mail\">E-Mails<\/option>\n<\/select>\n```\n\n### 3.3 context-limit.php\n\n**Input:**\n```php\n$options = [\n    'selected' => 5,           \/\/ Optional: default 5\n    'name' => 'context_limit', \/\/ Optional\n    'variant' => 'default',    \/\/ 'default' | 'inline'\n    'limits' => [3, 5, 10, 15], \/\/ Optional: custom limits\n];\n```\n\n**Output:**\n```html\n<select name=\"context_limit\" id=\"context_limit\" class=\"form-select\">\n    <option value=\"3\">3 Quellen<\/option>\n    <option value=\"5\" selected>5 Quellen<\/option>\n    <option value=\"10\">10 Quellen<\/option>\n    <option value=\"15\">15 Quellen<\/option>\n<\/select>\n```\n\n### 3.4 author-profile.php\n\n**Input:**\n```php\n$options = [\n    'profiles' => $authorProfiles,  \/\/ Required\n    'selected' => $currentProfileId, \/\/ Optional\n    'name' => 'author_profile_id',  \/\/ Optional\n    'allow_empty' => true,          \/\/ Optional: \"Kein Profil\" Option\n];\n```\n\n**Output:**\n```html\n<select name=\"author_profile_id\" id=\"author_profile_id\" class=\"form-select\">\n    <option value=\"\">Kein Profil<\/option>\n    <option value=\"1\">Akademisch<\/option>\n    <option value=\"2\" selected>Didaktisch<\/option>\n<\/select>\n```\n\n### 3.5 system-prompt.php\n\n**Input:**\n```php\n$options = [\n    'prompts' => $systemPrompts,    \/\/ Required\n    'selected' => $currentPromptId, \/\/ Optional\n    'name' => 'system_prompt_id',   \/\/ Optional\n];\n```\n\n### 3.6 temperature.php\n\n**Input:**\n```php\n$options = [\n    'value' => 0.7,            \/\/ Optional: default 0.7\n    'name' => 'temperature',   \/\/ Optional\n    'min' => 0,                \/\/ Optional\n    'max' => 1,                \/\/ Optional\n    'step' => 0.1,             \/\/ Optional\n    'show_value' => true,      \/\/ Optional: Wert anzeigen\n];\n```\n\n**Output:**\n```html\n<div class=\"temperature-control\">\n    <label for=\"temperature\">Temp: <span id=\"temperature-value\">0.7<\/span><\/label>\n    <input type=\"range\" name=\"temperature\" id=\"temperature\"\n           min=\"0\" max=\"1\" step=\"0.1\" value=\"0.7\" class=\"form-range\">\n<\/div>\n```\n\n### 3.7 max-tokens.php\n\n**Input:**\n```php\n$options = [\n    'selected' => 4096,        \/\/ Optional\n    'name' => 'max_tokens',    \/\/ Optional\n    'tokens' => [1024, 2048, 4096, 8192], \/\/ Optional\n];\n```\n\n### 3.8 contract.php \/ structure.php\n\nAnalog zu author-profile.php.\n\n---\n\n## 4. CSS-Klassen (einheitlich)\n\n```css\n\/* Basis *\/\n.form-select { \/* Standard Select *\/ }\n.form-select--inline { \/* Kompakt für Inline-Nutzung *\/ }\n.form-input { \/* Standard Input *\/ }\n.form-range { \/* Range Slider *\/ }\n.form-checkbox { \/* Checkbox *\/ }\n\n\/* Spezifisch *\/\n.collections-select { \/* Multi-Select für Collections *\/ }\n.temperature-control { \/* Wrapper für Temp-Slider *\/ }\n\n\/* Varianten *\/\n.form-group { \/* Wrapper mit Label *\/ }\n.form-group--inline { \/* Inline-Variante *\/ }\n```\n\n---\n\n## 5. Verwendung in Views\n\n### 5.1 Chat (nach Refactoring)\n\n```php\n<div class=\"chat-options-row\">\n    <?php include VIEW_PATH . '\/partials\/form\/model-select.php'; ?>\n    <?php include VIEW_PATH . '\/partials\/form\/collections.php'; ?>\n    <?php include VIEW_PATH . '\/partials\/form\/context-limit.php'; ?>\n    <?php include VIEW_PATH . '\/partials\/form\/author-profile.php'; ?>\n    <?php include VIEW_PATH . '\/partials\/form\/system-prompt.php'; ?>\n<\/div>\n<div class=\"chat-options-row chat-options-row--advanced\">\n    <?php include VIEW_PATH . '\/partials\/form\/temperature.php'; ?>\n    <?php include VIEW_PATH . '\/partials\/form\/max-tokens.php'; ?>\n<\/div>\n```\n\n### 5.2 Content New (nach Refactoring)\n\n```php\n<fieldset>\n    <legend>KI-Einstellungen<\/legend>\n    <div class=\"form-row\">\n        <?php $options = ['variant' => 'default', 'label' => 'Sprachmodell']; ?>\n        <?php include VIEW_PATH . '\/partials\/form\/model-select.php'; ?>\n        <?php include VIEW_PATH . '\/partials\/form\/context-limit.php'; ?>\n    <\/div>\n    <?php $options = ['variant' => 'checkbox']; ?>\n ... [TRUNCATED-b845d55f0ceb6581]",
        "structuredPatch": [
            {
                "oldStart": 6,
                "oldLines": 6,
                "newStart": 6,
                "newLines": 159,
                "lines": [
                    " ",
                    " ---",
                    " ",
                    "+## 0. LLM-Verwaltung (Grundlage für Modell-Dropdown)",
                    "+",
                    "+### 0.1 Architektur-Entscheidung",
                    "+",
                    "+**Gewählt: Variante B - Dedizierte LLM-Verwaltung**",
                    "+",
                    "+Statt dynamischer API-Abfrage bei jedem Seitenaufruf:",
                    "+- Zentrale Admin-Seite `\/ressourcen\/llm`",
                    "+- Datenbank-Tabelle als Single Source of Truth",
                    "+- Sync-Buttons für Provider (Anthropic, Ollama, weitere)",
                    "+- Sprechende Namen und Zusatzmetadaten",
                    "+",
                    "+### 0.2 Datenbank-Tabelle `llm_models`",
                    "+",
                    "+```sql",
                    "+CREATE TABLE llm_models (",
                    "+    id INT AUTO_INCREMENT PRIMARY KEY,",
                    "+    provider ENUM('anthropic', 'ollama', 'openai', 'google', 'mistral', 'custom') NOT NULL,",
                    "+    model_id VARCHAR(100) NOT NULL,        -- API-ID: \"claude-opus-4-5-20251101\"",
                    "+    display_name VARCHAR(100) NOT NULL,    -- Anzeige: \"Claude Opus 4.5\"",
                    "+    description TEXT,                      -- Kurzbeschreibung",
                    "+    context_window INT,                    -- Max. Tokens Input: 200000",
                    "+    max_output_tokens INT,                 -- Max. Tokens Output: 8192",
                    "+    input_price_per_mtok DECIMAL(10,4),    -- Preis Input $\/MTok",
                    "+    output_price_per_mtok DECIMAL(10,4),   -- Preis Output $\/MTok",
                    "+    capabilities JSON,                     -- {\"vision\": true, \"function_calling\": true}",
                    "+    is_local BOOLEAN DEFAULT FALSE,        -- Lokal (Ollama) vs. Cloud",
                    "+    is_active BOOLEAN DEFAULT TRUE,        -- In Dropdowns anzeigen?",
                    "+    sort_order INT DEFAULT 0,              -- Reihenfolge in Dropdowns",
                    "+    last_synced_at DATETIME,               -- Letzte Synchronisation",
                    "+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,",
                    "+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,",
                    "+    UNIQUE KEY unique_provider_model (provider, model_id)",
                    "+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;",
                    "+```",
                    "+",
                    "+### 0.3 Admin-Seite `\/ressourcen\/llm`",
                    "+",
                    "+**URL:** `\/ressourcen\/llm`",
                    "+**Controller:** `RessourcenController::llmIndex()`",
                    "+",
                    "+**Funktionen:**",
                    "+",
                    "+| Button | Aktion |",
                    "+|--------|--------|",
                    "+| \"Anthropic synchronisieren\" | `GET api.anthropic.com\/v1\/models` → DB aktualisieren |",
                    "+| \"Ollama synchronisieren\" | `ollama list` → DB aktualisieren |",
                    "+| \"Neuer Provider\" | Manuell weiteren Provider hinzufügen |",
                    "+",
                    "+**Tabellen-Ansicht:**",
                    "+",
                    "+| Provider | Model-ID | Display-Name | Context | Preis In\/Out | Lokal | Aktiv | Aktionen |",
                    "+|----------|----------|--------------|---------|--------------|-------|-------|----------|",
                    "+| Anthropic | claude-opus-4-5-20251101 | Claude Opus 4.5 | 200k | $15\/$75 | - | ✓ | Bearbeiten |",
                    "+| Ollama | mistral:latest | Mistral 7B | 32k | - | ✓ | ✓ | Bearbeiten |",
                    "+",
                    "+**Bearbeiten-Dialog:**",
                    "+- Display-Name ändern",
                    "+- Beschreibung hinzufügen",
                    "+- Context-Window \/ Max-Output korrigieren",
                    "+- Preise eintragen (für Kostenberechnung)",
                    "+- Aktivieren\/Deaktivieren",
                    "+- Sortierung ändern",
                    "+",
                    "+### 0.4 Sync-Logik",
                    "+",
                    "+**Anthropic Sync:**",
                    "+```php",
                    "+\/\/ GET https:\/\/api.anthropic.com\/v1\/models",
                    "+\/\/ Response: {\"data\": [{\"id\": \"claude-opus-4-5-20251101\", ...}]}",
                    "+",
                    "+foreach ($apiModels as $model) {",
                    "+    \/\/ INSERT ... ON DUPLICATE KEY UPDATE",
                    "+    \/\/ Neue Modelle: is_active = true, display_name = model_id (initial)",
                    "+    \/\/ Existierende: last_synced_at aktualisieren",
                    "+    \/\/ Fehlende: NICHT löschen, nur last_synced_at bleibt alt",
                    "+}",
                    "+```",
                    "+",
                    "+**Ollama Sync:**",
                    "+```php",
                    "+\/\/ $ ollama list",
                    "+\/\/ NAME              ID           SIZE    MODIFIED",
                    "+\/\/ mistral:latest    abc123...    4.1 GB  2 days ago",
                    "+",
                    "+$output = shell_exec('ollama list');",
                    "+\/\/ Parsen und in DB einfügen",
                    "+```",
                    "+",
                    "+### 0.5 Dropdown-Query",
                    "+",
                    "+```php",
                    "+\/\/ ModelService::getActiveModels()",
                    "+public function getActiveModels(): array",
                    "+{",
                    "+    return $this->db->query(\"",
                    "+        SELECT model_id, display_name, provider, is_local, context_window",
                    "+        FROM llm_models",
                    "+        WHERE is_active = 1",
                    "+        ORDER BY is_local ASC, sort_order ASC, display_name ASC",
                    "+    \")->fetchAll();",
                    "+}",
                    "+```",
                    "+",
                    "+### 0.6 Partial nutzt DB-Daten",
                    "+",
                    "+```php",
                    "+\/\/ \/src\/View\/partials\/form\/model-select.php",
                    "+<?php",
                    "+$models = $models ?? [];",
                    "+$selected = $selected ?? '';",
                    "+$variant = $variant ?? 'default';",
                    "+$class = $variant === 'inline' ? 'form-select--inline' : 'form-select';",
                    "+?>",
                    "+<select name=\"model\" id=\"model\" class=\"<?= $class ?>\">",
                    "+    <optgroup label=\"Cloud\">",
                    "+        <?php foreach ($models as $m): ?>",
                    "+        <?php if (!$m['is_local']): ?>",
                    "+        <option value=\"<?= $m['model_id'] ?>\" <?= $selected === $m['model_id'] ? 'selected' : '' ?>>",
                    "+            <?= htmlspecialchars($m['display_name']) ?>",
                    "+        <\/option>",
                    "+        <?php endif; ?>",
                    "+        <?php endforeach; ?>",
                    "+    <\/optgroup>",
                    "+    <optgroup label=\"Lokal\">",
                    "+        <?php foreach ($models as $m): ?>",
                    "+        <?php if ($m['is_local']): ?>",
                    "+        <option value=\"<?= $m['model_id'] ?>\" <?= $selected === $m['model_id'] ? 'selected' : '' ?>>",
                    "+            <?= htmlspecialchars($m['display_name']) ?>",
                    "+        <\/option>",
                    "+        <?php endif; ?>",
                    "+        <?php endforeach; ?>",
                    "+    <\/optgroup>",
                    "+<\/select>",
                    "+```",
                    "+",
                    "+### 0.7 Migrations-Schritte (LLM-Verwaltung)",
                    "+",
                    "+1. [ ] Tabelle `llm_models` in `ki_dev` erstellen",
                    "+2. [ ] `LlmModelRepository` erstellen",
                    "+3. [ ] `LlmSyncService` erstellen (Anthropic + Ollama)",
                    "+4. [ ] Route `\/ressourcen\/llm` anlegen",
                    "+5. [ ] `RessourcenController::llmIndex()` implementieren",
                    "+6. [ ] View `\/ressourcen\/llm\/index.php` erstellen",
                    "+7. [ ] Sync-Buttons implementieren",
                    "+8. [ ] Bearbeiten-Funktionalität",
                    "+9. [ ] Initial-Sync durchführen (bestehende Modelle importieren)",
                    "+10. [ ] `ModelConfig.php` durch `LlmModelRepository` ersetzen",
                    "+11. [ ] Partial `model-select.php` erstellen",
                    "+12. [ ] Chat und Content auf Partial umstellen",
                    "+",
                    "+---",
                    "+",
                    " ## 1. IST-Analyse",
                    " ",
                    " ### 1.1 Betroffene Views"
                ]
            }
        ],
        "userModified": false,
        "replaceAll": false
    }
}
← Vorheriger Zur Liste Nächster →