Protokoll #2159

ID2159
Zeitstempel2025-12-21 10:30:43.852733
Clientroot
IP145.224.96.190
Modellclaude-sonnet-4-20250514
Statuscompleted
Tokens7,208 (Input: 1,711, Output: 5,497)
Dauer88 ms
Request-Zeit2025-12-21 10:30:43.852733
Response-Zeit2025-12-21 10:30:43.941130

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": "### 0e.4 Partial: temperature-tokens.php\n\n```php\n\/\/ \/src\/View\/partials\/form\/temperature-tokens.php\n<?php\n\/\/ Werte aus Session\/Order oder User-Präferenzen\n$temperature = $temperature ?? ($userPrefs['default_temperature'] ?? 0.7);\n$maxTokens = $maxTokens ?? ($userPrefs['default_max_tokens'] ?? null);\n$preset = $preset ?? ($userPrefs['default_preset'] ?? 'balanced');\n\n\/\/ Modell-Info für Token-Stufen (wird per JS aktualisiert)\n$modelMaxTokens = $modelMaxTokens ?? 8192;\n$tokenSteps = [\n    (int) ($modelMaxTokens * 0.25),\n    (int) ($modelMaxTokens * 0.50),\n    (int) ($modelMaxTokens * 0.75),\n    $modelMaxTokens,\n];\n\n\/\/ Default max_tokens basierend auf Preset\nif ($maxTokens === null) {\n    $maxTokens = match($preset) {\n        'precise' => $tokenSteps[1],    \/\/ 50%\n        'balanced' => $tokenSteps[2],   \/\/ 75%\n        'creative' => $tokenSteps[3],   \/\/ 100%\n        default => $tokenSteps[2],\n    };\n}\n\n$variant = $variant ?? 'default';\n$class = $variant === 'inline' ? 'form-control--inline' : 'form-control';\n?>\n\n<div class=\"llm-settings <?= $class ?>\" data-model-max-tokens=\"<?= $modelMaxTokens ?>\">\n    <!-- Presets -->\n    <div class=\"preset-buttons\">\n        <button type=\"button\" class=\"preset-btn <?= $preset === 'precise' ? 'active' : '' ?>\"\n                data-preset=\"precise\" data-temperature=\"0.3\" data-tokens-percent=\"50\"\n                title=\"Fokussiert, weniger kreativ\">\n            Präzise\n        <\/button>\n        <button type=\"button\" class=\"preset-btn <?= $preset === 'balanced' ? 'active' : '' ?>\"\n                data-preset=\"balanced\" data-temperature=\"0.7\" data-tokens-percent=\"75\"\n                title=\"Standard für die meisten Aufgaben\">\n            Ausgewogen\n        <\/button>\n        <button type=\"button\" class=\"preset-btn <?= $preset === 'creative' ? 'active' : '' ?>\"\n                data-preset=\"creative\" data-temperature=\"0.9\" data-tokens-percent=\"100\"\n                title=\"Explorativ, längere Antworten\">\n            Kreativ\n        <\/button>\n    <\/div>\n\n    <!-- Temperature Slider -->\n    <div class=\"temperature-control\">\n        <label for=\"temperature\">\n            Temperature: <span class=\"temperature-value\"><?= number_format($temperature, 1) ?><\/span>\n        <\/label>\n        <input type=\"range\" name=\"temperature\" id=\"temperature\"\n               min=\"0\" max=\"1\" step=\"0.1\" value=\"<?= $temperature ?>\"\n               class=\"form-range\">\n    <\/div>\n\n    <!-- Max Tokens Select -->\n    <div class=\"max-tokens-control\">\n        <label for=\"max_tokens\">Max Tokens<\/label>\n        <select name=\"max_tokens\" id=\"max_tokens\" class=\"form-select\">\n            <?php foreach ($tokenSteps as $step): ?>\n            <option value=\"<?= $step ?>\" <?= (int)$maxTokens === $step ? 'selected' : '' ?>>\n                <?= number_format($step) ?>\n            <\/option>\n            <?php endforeach; ?>\n        <\/select>\n    <\/div>\n\n    <!-- Hidden: Preset für Form-Submit -->\n    <input type=\"hidden\" name=\"preset\" id=\"preset\" value=\"<?= $preset ?>\">\n<\/div>\n```",
        "new_string": "### 0e.4 Partial: temperature-tokens.php\n\n```php\n\/\/ \/src\/View\/partials\/form\/temperature-tokens.php\n<?php\n\/**\n * Benötigte Variablen (via Controller):\n * - $presets: array aus LlmPresetRepository::getActive()\n * - $userPrefs: array aus UserPreferencesService\n * - $modelMaxTokens: int aus aktuellem Modell (llm_models.max_output_tokens)\n *\n * Optionale Variablen:\n * - $temperature: float (Session\/Order-Wert)\n * - $maxTokens: int (Session\/Order-Wert)\n * - $presetId: int (Session\/Order-Wert)\n * - $variant: 'default' | 'inline'\n *\/\n\n\/\/ Presets aus DB\n$presets = $presets ?? [];\n\n\/\/ Aktiver Preset (Session → User-Präferenz → erster aktiver)\n$activePresetId = $presetId\n    ?? ($userPrefs['default_preset_id'] ?? null)\n    ?? ($presets[0]['id'] ?? null);\n\n$activePreset = null;\nforeach ($presets as $p) {\n    if ((int)$p['id'] === (int)$activePresetId) {\n        $activePreset = $p;\n        break;\n    }\n}\n\n\/\/ Werte aus Session\/Order oder Preset\n$temperature = $temperature ?? ($activePreset['temperature'] ?? 0.7);\n$modelMaxTokens = $modelMaxTokens ?? 8192;\n\n\/\/ Token-Stufen berechnen\n$tokenSteps = [\n    (int) ($modelMaxTokens * 0.25),\n    (int) ($modelMaxTokens * 0.50),\n    (int) ($modelMaxTokens * 0.75),\n    $modelMaxTokens,\n];\n\n\/\/ Max Tokens aus Session oder Preset-Prozent\nif (!isset($maxTokens) && $activePreset) {\n    $maxTokens = (int) ($modelMaxTokens * $activePreset['tokens_percent'] \/ 100);\n}\n$maxTokens = $maxTokens ?? $tokenSteps[2];\n\n$variant = $variant ?? 'default';\n$class = $variant === 'inline' ? 'form-control--inline' : 'form-control';\n?>\n\n<div class=\"llm-settings <?= $class ?>\" data-model-max-tokens=\"<?= $modelMaxTokens ?>\">\n    <!-- Presets aus DB -->\n    <div class=\"preset-buttons\">\n        <?php foreach ($presets as $p): ?>\n        <button type=\"button\"\n                class=\"preset-btn <?= (int)$activePresetId === (int)$p['id'] ? 'active' : '' ?>\"\n                data-preset-id=\"<?= $p['id'] ?>\"\n                data-preset-slug=\"<?= htmlspecialchars($p['slug']) ?>\"\n                data-temperature=\"<?= $p['temperature'] ?>\"\n                data-tokens-percent=\"<?= $p['tokens_percent'] ?>\"\n                title=\"<?= htmlspecialchars($p['description']) ?>\">\n            <?= htmlspecialchars($p['display_name']) ?>\n        <\/button>\n        <?php endforeach; ?>\n    <\/div>\n\n    <!-- Temperature Slider -->\n    <div class=\"temperature-control\">\n        <label for=\"temperature\">\n            Temperature: <span class=\"temperature-value\"><?= number_format($temperature, 1) ?><\/span>\n        <\/label>\n        <input type=\"range\" name=\"temperature\" id=\"temperature\"\n               min=\"0\" max=\"1\" step=\"0.1\" value=\"<?= $temperature ?>\"\n               class=\"form-range\">\n    <\/div>\n\n    <!-- Max Tokens Select -->\n    <div class=\"max-tokens-control\">\n        <label for=\"max_tokens\">Max Tokens<\/label>\n        <select name=\"max_tokens\" id=\"max_tokens\" class=\"form-select\">\n            <?php foreach ($tokenSteps as $step): ?>\n            <option value=\"<?= $step ?>\" <?= (int)$maxTokens === $step ? 'selected' : '' ?>>\n                <?= number_format($step) ?>\n            <\/option>\n            <?php endforeach; ?>\n        <\/select>\n    <\/div>\n\n    <!-- Hidden: Preset-ID für Form-Submit -->\n    <input type=\"hidden\" name=\"preset_id\" id=\"preset_id\" value=\"<?= $activePresetId ?>\">\n<\/div>\n```"
    }
}

Response

{
    "tool_response": {
        "filePath": "\/var\/www\/dev.campus.systemische-tools.de\/docs\/plan-form-components-refactoring.md",
        "oldString": "### 0e.4 Partial: temperature-tokens.php\n\n```php\n\/\/ \/src\/View\/partials\/form\/temperature-tokens.php\n<?php\n\/\/ Werte aus Session\/Order oder User-Präferenzen\n$temperature = $temperature ?? ($userPrefs['default_temperature'] ?? 0.7);\n$maxTokens = $maxTokens ?? ($userPrefs['default_max_tokens'] ?? null);\n$preset = $preset ?? ($userPrefs['default_preset'] ?? 'balanced');\n\n\/\/ Modell-Info für Token-Stufen (wird per JS aktualisiert)\n$modelMaxTokens = $modelMaxTokens ?? 8192;\n$tokenSteps = [\n    (int) ($modelMaxTokens * 0.25),\n    (int) ($modelMaxTokens * 0.50),\n    (int) ($modelMaxTokens * 0.75),\n    $modelMaxTokens,\n];\n\n\/\/ Default max_tokens basierend auf Preset\nif ($maxTokens === null) {\n    $maxTokens = match($preset) {\n        'precise' => $tokenSteps[1],    \/\/ 50%\n        'balanced' => $tokenSteps[2],   \/\/ 75%\n        'creative' => $tokenSteps[3],   \/\/ 100%\n        default => $tokenSteps[2],\n    };\n}\n\n$variant = $variant ?? 'default';\n$class = $variant === 'inline' ? 'form-control--inline' : 'form-control';\n?>\n\n<div class=\"llm-settings <?= $class ?>\" data-model-max-tokens=\"<?= $modelMaxTokens ?>\">\n    <!-- Presets -->\n    <div class=\"preset-buttons\">\n        <button type=\"button\" class=\"preset-btn <?= $preset === 'precise' ? 'active' : '' ?>\"\n                data-preset=\"precise\" data-temperature=\"0.3\" data-tokens-percent=\"50\"\n                title=\"Fokussiert, weniger kreativ\">\n            Präzise\n        <\/button>\n        <button type=\"button\" class=\"preset-btn <?= $preset === 'balanced' ? 'active' : '' ?>\"\n                data-preset=\"balanced\" data-temperature=\"0.7\" data-tokens-percent=\"75\"\n                title=\"Standard für die meisten Aufgaben\">\n            Ausgewogen\n        <\/button>\n        <button type=\"button\" class=\"preset-btn <?= $preset === 'creative' ? 'active' : '' ?>\"\n                data-preset=\"creative\" data-temperature=\"0.9\" data-tokens-percent=\"100\"\n                title=\"Explorativ, längere Antworten\">\n            Kreativ\n        <\/button>\n    <\/div>\n\n    <!-- Temperature Slider -->\n    <div class=\"temperature-control\">\n        <label for=\"temperature\">\n            Temperature: <span class=\"temperature-value\"><?= number_format($temperature, 1) ?><\/span>\n        <\/label>\n        <input type=\"range\" name=\"temperature\" id=\"temperature\"\n               min=\"0\" max=\"1\" step=\"0.1\" value=\"<?= $temperature ?>\"\n               class=\"form-range\">\n    <\/div>\n\n    <!-- Max Tokens Select -->\n    <div class=\"max-tokens-control\">\n        <label for=\"max_tokens\">Max Tokens<\/label>\n        <select name=\"max_tokens\" id=\"max_tokens\" class=\"form-select\">\n            <?php foreach ($tokenSteps as $step): ?>\n            <option value=\"<?= $step ?>\" <?= (int)$maxTokens === $step ? 'selected' : '' ?>>\n                <?= number_format($step) ?>\n            <\/option>\n            <?php endforeach; ?>\n        <\/select>\n    <\/div>\n\n    <!-- Hidden: Preset für Form-Submit -->\n    <input type=\"hidden\" name=\"preset\" id=\"preset\" value=\"<?= $preset ?>\">\n<\/div>\n```",
        "newString": "### 0e.4 Partial: temperature-tokens.php\n\n```php\n\/\/ \/src\/View\/partials\/form\/temperature-tokens.php\n<?php\n\/**\n * Benötigte Variablen (via Controller):\n * - $presets: array aus LlmPresetRepository::getActive()\n * - $userPrefs: array aus UserPreferencesService\n * - $modelMaxTokens: int aus aktuellem Modell (llm_models.max_output_tokens)\n *\n * Optionale Variablen:\n * - $temperature: float (Session\/Order-Wert)\n * - $maxTokens: int (Session\/Order-Wert)\n * - $presetId: int (Session\/Order-Wert)\n * - $variant: 'default' | 'inline'\n *\/\n\n\/\/ Presets aus DB\n$presets = $presets ?? [];\n\n\/\/ Aktiver Preset (Session → User-Präferenz → erster aktiver)\n$activePresetId = $presetId\n    ?? ($userPrefs['default_preset_id'] ?? null)\n    ?? ($presets[0]['id'] ?? null);\n\n$activePreset = null;\nforeach ($presets as $p) {\n    if ((int)$p['id'] === (int)$activePresetId) {\n        $activePreset = $p;\n        break;\n    }\n}\n\n\/\/ Werte aus Session\/Order oder Preset\n$temperature = $temperature ?? ($activePreset['temperature'] ?? 0.7);\n$modelMaxTokens = $modelMaxTokens ?? 8192;\n\n\/\/ Token-Stufen berechnen\n$tokenSteps = [\n    (int) ($modelMaxTokens * 0.25),\n    (int) ($modelMaxTokens * 0.50),\n    (int) ($modelMaxTokens * 0.75),\n    $modelMaxTokens,\n];\n\n\/\/ Max Tokens aus Session oder Preset-Prozent\nif (!isset($maxTokens) && $activePreset) {\n    $maxTokens = (int) ($modelMaxTokens * $activePreset['tokens_percent'] \/ 100);\n}\n$maxTokens = $maxTokens ?? $tokenSteps[2];\n\n$variant = $variant ?? 'default';\n$class = $variant === 'inline' ? 'form-control--inline' : 'form-control';\n?>\n\n<div class=\"llm-settings <?= $class ?>\" data-model-max-tokens=\"<?= $modelMaxTokens ?>\">\n    <!-- Presets aus DB -->\n    <div class=\"preset-buttons\">\n        <?php foreach ($presets as $p): ?>\n        <button type=\"button\"\n                class=\"preset-btn <?= (int)$activePresetId === (int)$p['id'] ? 'active' : '' ?>\"\n                data-preset-id=\"<?= $p['id'] ?>\"\n                data-preset-slug=\"<?= htmlspecialchars($p['slug']) ?>\"\n                data-temperature=\"<?= $p['temperature'] ?>\"\n                data-tokens-percent=\"<?= $p['tokens_percent'] ?>\"\n                title=\"<?= htmlspecialchars($p['description']) ?>\">\n            <?= htmlspecialchars($p['display_name']) ?>\n        <\/button>\n        <?php endforeach; ?>\n    <\/div>\n\n    <!-- Temperature Slider -->\n    <div class=\"temperature-control\">\n        <label for=\"temperature\">\n            Temperature: <span class=\"temperature-value\"><?= number_format($temperature, 1) ?><\/span>\n        <\/label>\n        <input type=\"range\" name=\"temperature\" id=\"temperature\"\n               min=\"0\" max=\"1\" step=\"0.1\" value=\"<?= $temperature ?>\"\n               class=\"form-range\">\n    <\/div>\n\n    <!-- Max Tokens Select -->\n    <div class=\"max-tokens-control\">\n        <label for=\"max_tokens\">Max Tokens<\/label>\n        <select name=\"max_tokens\" id=\"max_tokens\" class=\"form-select\">\n            <?php foreach ($tokenSteps as $step): ?>\n            <option value=\"<?= $step ?>\" <?= (int)$maxTokens === $step ? 'selected' : '' ?>>\n                <?= number_format($step) ?>\n            <\/option>\n            <?php endforeach; ?>\n        <\/select>\n    <\/div>\n\n    <!-- Hidden: Preset-ID für Form-Submit -->\n    <input type=\"hidden\" name=\"preset_id\" id=\"preset_id\" value=\"<?= $activePresetId ?>\">\n<\/div>\n```",
        "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## 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## 0b. Collection-Verwaltung (Grundlage für Collection-Dropdown)\n\n### 0b.1 Architektur-Entscheidung\n\n**Gewählt: DB-Tabelle analog zu LLMs**\n\n- Zentrale Admin-Seite `\/ressourcen\/collections`\n- Datenbank-Tabelle als Single Source of Truth\n- Sync mit Qdrant (Metadaten abrufen)\n- Sprechende Namen und Beschreibungen\n- **Multi-Select** als einheitliche Darstellung\n\n### 0b.2 Datenbank-Tabelle `rag_collections`\n\n```sql\nCREATE TABLE rag_collections (\n    id INT AUTO_INCREMENT PRIMARY KEY,\n    collection_id VARCHAR(100) NOT NULL UNIQUE,  -- Qdrant-Name: \"documents\"\n    display_name VARCHAR(100) NOT NULL,          -- Anzeige: \"Dokumente\"\n    description TEXT,                            -- \"PDF-Dokumente aus Nextcloud\"\n\n    -- Qdrant-Metadaten (via Sync)\n    vector_size INT,                             -- 1024\n    distance_metric VARCHAR(20),                 -- \"Cosine\"\n    points_count INT DEFAULT 0,                  -- Anzahl Vektoren\n\n    -- Konfiguration\n    embedding_model VARCHAR(100),                -- \"mxbai-embed-large\"\n    chunk_size INT,                              -- 2000\n    chunk_overlap INT,                           -- 200\n\n    -- Verwaltung\n    source_type ENUM('nextcloud', 'mail', 'manual', 'system') DEFAULT 'manual',\n    source_path VARCHAR(500),                    -- \"\/var\/www\/nextcloud\/data\/...\"\n    is_active BOOLEAN DEFAULT TRUE,              -- In Dropdowns anzeigen?\n    is_searchable BOOLEAN DEFAULT TRUE,          -- Für RAG verfügbar?\n    sort_order INT DEFAULT 0,\n\n    -- Timestamps\n    last_synced_at DATETIME,\n    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;\n```\n\n### 0b.3 Impact-Analyse: Wer nutzt Collections?\n\n| System\/Seite | Aktueller Zugriff | Neuer Zugriff | Änderung |\n|--------------|-------------------|---------------|----------|\n| **Chat** | `$qdrantService->listCollections()` | `CollectionRepository::getActive()` | Query aus DB |\n| **Content Studio** | `$qdrantService->listCollections()` | `CollectionRepository::getActive()` | Query aus DB |\n| **Content Show** | `$qdrantService->listCollections()` | `CollectionRepository::getActive()` | Query aus DB |\n| **Pipeline (Python)** | `config.py QDRANT_COLLECTIONS` | DB-Query oder Config-Sync | Python liest DB |\n| **Semantic Explorer** | Direkt Qdrant | `CollectionRepository` + Qdrant | Metadaten aus DB |\n| **System Explorer** | - | Statistiken aus DB | Neu |\n| **API \/api\/v1\/search** | Qdrant direkt | Validierung gegen DB | Nur aktive Collections |\n| **Embedding-Service** | `config.py` | DB oder Sync | Konsistenz |\n\n### 0b.4 Admin-Seite `\/ressourcen\/collections`\n\n**URL:** `\/ressourcen\/collections`\n**Controller:** `RessourcenController::collectionsIndex()`\n\n**Funktionen:**\n\n| Button | Aktion |\n|--------|--------|\n| \"Qdrant synchronisieren\" | Collections + Metadaten von Qdrant abrufen |\n| \"Neue Collection\" | Manuell Collection registrieren |\n\n**Tabellen-Ansicht:**\n\n| Collection-ID | Display-Name | Vektoren | Größe | Quelle | Aktiv | Suchbar | Aktionen |\n|---------------|--------------|----------|-------|--------|-------|---------|----------|\n| documents | Dokumente | 1.247 | 1024d | Nextcloud | ✓ | ✓ | Bearbeiten |\n| dokumentation_chunks | Doku-Chunks | 892 | 1024d | System | ✓ | ✓ | Bearbeiten |\n| mail | E-Mails | 3.421 | 1024d | Mail | ✓ | ✓ | Bearbeiten |\n| entities | Entitäten | 156 | 1024d | System | ✓ | - | Bearbeiten |\n\n**Bearbeiten-Dialog:**\n- Display-Name ändern\n- Beschreibung hinzufügen\n- Quelle\/Pfad dokumentieren\n- Aktivieren\/Deaktivieren (Dropdown)\n- Suchbar ja\/nein (RAG-Verfügbarkeit)\n- Sortierung ändern\n\n### 0b.5 Sync-Logik\n\n**Qdrant Sync:**\n```php\n\/\/ QdrantService::getCollectionsInfo()\npublic function syncCollections(): array\n{\n    $qdrantCollections = $this->client->getCollections();\n\n    foreach ($qdrantCollections as $col) {\n        $info = $this->client->getCollectionInfo($col['name']);\n\n        \/\/ INSERT ... ON DUPLICATE KEY UPDATE\n        $this->repository->upsert([\n            'collection_id' => $col['name'],\n            'display_name' => $col['name'],  \/\/ Initial = ID\n            'vector_size' => $info['config']['params']['size'],\n            'distance_metric' => $info['config']['params']['distance'],\n            'points_count' => $info['points_count'],\n            'last_synced_at' => now(),\n        ]);\n    }\n}\n```\n\n### 0b.6 Repository-Interface\n\n```php\n\/\/ CollectionRepository.php\ninterface CollectionRepositoryInterfac... [TRUNCATED-f76878f1937669a3]",
        "structuredPatch": [
            {
                "oldStart": 847,
                "oldLines": 13,
                "newStart": 847,
                "newLines": 40,
                "lines": [
                    " ```php",
                    " \/\/ \/src\/View\/partials\/form\/temperature-tokens.php",
                    " <?php",
                    "-\/\/ Werte aus Session\/Order oder User-Präferenzen",
                    "-$temperature = $temperature ?? ($userPrefs['default_temperature'] ?? 0.7);",
                    "-$maxTokens = $maxTokens ?? ($userPrefs['default_max_tokens'] ?? null);",
                    "-$preset = $preset ?? ($userPrefs['default_preset'] ?? 'balanced');",
                    "+\/**",
                    "+ * Benötigte Variablen (via Controller):",
                    "+ * - $presets: array aus LlmPresetRepository::getActive()",
                    "+ * - $userPrefs: array aus UserPreferencesService",
                    "+ * - $modelMaxTokens: int aus aktuellem Modell (llm_models.max_output_tokens)",
                    "+ *",
                    "+ * Optionale Variablen:",
                    "+ * - $temperature: float (Session\/Order-Wert)",
                    "+ * - $maxTokens: int (Session\/Order-Wert)",
                    "+ * - $presetId: int (Session\/Order-Wert)",
                    "+ * - $variant: 'default' | 'inline'",
                    "+ *\/",
                    " ",
                    "-\/\/ Modell-Info für Token-Stufen (wird per JS aktualisiert)",
                    "+\/\/ Presets aus DB",
                    "+$presets = $presets ?? [];",
                    "+",
                    "+\/\/ Aktiver Preset (Session → User-Präferenz → erster aktiver)",
                    "+$activePresetId = $presetId",
                    "+    ?? ($userPrefs['default_preset_id'] ?? null)",
                    "+    ?? ($presets[0]['id'] ?? null);",
                    "+",
                    "+$activePreset = null;",
                    "+foreach ($presets as $p) {",
                    "+    if ((int)$p['id'] === (int)$activePresetId) {",
                    "+        $activePreset = $p;",
                    "+        break;",
                    "+    }",
                    "+}",
                    "+",
                    "+\/\/ Werte aus Session\/Order oder Preset",
                    "+$temperature = $temperature ?? ($activePreset['temperature'] ?? 0.7);",
                    " $modelMaxTokens = $modelMaxTokens ?? 8192;",
                    "+",
                    "+\/\/ Token-Stufen berechnen",
                    " $tokenSteps = [",
                    "     (int) ($modelMaxTokens * 0.25),",
                    "     (int) ($modelMaxTokens * 0.50),"
                ]
            },
            {
                "oldStart": 861,
                "oldLines": 38,
                "newStart": 888,
                "newLines": 30,
                "lines": [
                    "     $modelMaxTokens,",
                    " ];",
                    " ",
                    "-\/\/ Default max_tokens basierend auf Preset",
                    "-if ($maxTokens === null) {",
                    "-    $maxTokens = match($preset) {",
                    "-        'precise' => $tokenSteps[1],    \/\/ 50%",
                    "-        'balanced' => $tokenSteps[2],   \/\/ 75%",
                    "-        'creative' => $tokenSteps[3],   \/\/ 100%",
                    "-        default => $tokenSteps[2],",
                    "-    };",
                    "+\/\/ Max Tokens aus Session oder Preset-Prozent",
                    "+if (!isset($maxTokens) && $activePreset) {",
                    "+    $maxTokens = (int) ($modelMaxTokens * $activePreset['tokens_percent'] \/ 100);",
                    " }",
                    "+$maxTokens = $maxTokens ?? $tokenSteps[2];",
                    " ",
                    " $variant = $variant ?? 'default';",
                    " $class = $variant === 'inline' ? 'form-control--inline' : 'form-control';",
                    " ?>",
                    " ",
                    " <div class=\"llm-settings <?= $class ?>\" data-model-max-tokens=\"<?= $modelMaxTokens ?>\">",
                    "-    <!-- Presets -->",
                    "+    <!-- Presets aus DB -->",
                    "     <div class=\"preset-buttons\">",
                    "-        <button type=\"button\" class=\"preset-btn <?= $preset === 'precise' ? 'active' : '' ?>\"",
                    "-                data-preset=\"precise\" data-temperature=\"0.3\" data-tokens-percent=\"50\"",
                    "-                title=\"Fokussiert, weniger kreativ\">",
                    "-            Präzise",
                    "+        <?php foreach ($presets as $p): ?>",
                    "+        <button type=\"button\"",
                    "+                class=\"preset-btn <?= (int)$activePresetId === (int)$p['id'] ? 'active' : '' ?>\"",
                    "+                data-preset-id=\"<?= $p['id'] ?>\"",
                    "+                data-preset-slug=\"<?= htmlspecialchars($p['slug']) ?>\"",
                    "+                data-temperature=\"<?= $p['temperature'] ?>\"",
                    "+                data-tokens-percent=\"<?= $p['tokens_percent'] ?>\"",
                    "+                title=\"<?= htmlspecialchars($p['description']) ?>\">",
                    "+            <?= htmlspecialchars($p['display_name']) ?>",
                    "         <\/button>",
                    "-        <button type=\"button\" class=\"preset-btn <?= $preset === 'balanced' ? 'active' : '' ?>\"",
                    "-                data-preset=\"balanced\" data-temperature=\"0.7\" data-tokens-percent=\"75\"",
                    "-                title=\"Standard für die meisten Aufgaben\">",
                    "-            Ausgewogen",
                    "-        <\/button>",
                    "-        <button type=\"button\" class=\"preset-btn <?= $preset === 'creative' ? 'active' : '' ?>\"",
                    "-                data-preset=\"creative\" data-temperature=\"0.9\" data-tokens-percent=\"100\"",
                    "-                title=\"Explorativ, längere Antworten\">",
                    "-            Kreativ",
                    "-        <\/button>",
                    "+        <?php endforeach; ?>",
                    "     <\/div>",
                    " ",
                    "     <!-- Temperature Slider -->"
                ]
            },
            {
                "oldStart": 917,
                "oldLines": 8,
                "newStart": 936,
                "newLines": 8,
                "lines": [
                    "         <\/select>",
                    "     <\/div>",
                    " ",
                    "-    <!-- Hidden: Preset für Form-Submit -->",
                    "-    <input type=\"hidden\" name=\"preset\" id=\"preset\" value=\"<?= $preset ?>\">",
                    "+    <!-- Hidden: Preset-ID für Form-Submit -->",
                    "+    <input type=\"hidden\" name=\"preset_id\" id=\"preset_id\" value=\"<?= $activePresetId ?>\">",
                    " <\/div>",
                    " ```",
                    " "
                ]
            }
        ],
        "userModified": false,
        "replaceAll": false
    }
}
← Vorheriger Zur Liste Nächster →