{
"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
}
}