Protokoll #2253

ID2253
Zeitstempel2025-12-21 12:25:08.250322
Clientroot
IP145.224.96.190
Modellclaude-sonnet-4-20250514
Statuscompleted
Tokens6,352 (Input: 1,334, Output: 5,018)
Dauer103 ms
Request-Zeit2025-12-21 12:25:08.250322
Response-Zeit2025-12-21 12:25:08.352851

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": "### 0b.6 Repository-Interface\n\n```php\n\/\/ CollectionRepository.php\ninterface CollectionRepositoryInterface\n{\n    \/\/ Für Dropdowns\n    public function getActive(): array;\n    public function getSearchable(): array;\n\n    \/\/ Für Admin\n    public function findAll(): array;\n    public function findById(int $id): ?array;\n    public function findByCollectionId(string $collectionId): ?array;\n\n    \/\/ Für Sync\n    public function upsert(array $data): void;\n    public function updateMetadata(string $collectionId, array $metadata): void;\n\n    \/\/ Validierung\n    public function isActive(string $collectionId): bool;\n    public function isSearchable(string $collectionId): bool;\n}\n```",
        "new_string": "### 0b.6 Repository-Interface\n\n```php\n\/\/ CollectionRepository.php\ninterface CollectionRepositoryInterface\n{\n    \/\/ Für Dropdowns\n    public function getActive(): array;\n    public function getSearchable(): array;\n\n    \/\/ Für Admin\n    public function findAll(): array;\n    public function findById(int $id): ?array;\n    public function findByCollectionId(string $collectionId): ?array;\n\n    \/\/ Für Sync\n    public function upsert(array $data): void;\n    public function updateMetadata(string $collectionId, array $metadata): void;\n\n    \/\/ Validierung\n    public function isActive(string $collectionId): bool;\n    public function isSearchable(string $collectionId): bool;\n\n    \/\/ ══════════════════════════════════════════════════════════════════\n    \/\/ KRITISCH: Sichere Löschung mit Referenz-Prüfung\n    \/\/ ══════════════════════════════════════════════════════════════════\n\n    \/**\n     * Prüft ob Collection noch referenziert wird\n     * @return array ['has_references' => bool, 'references' => [...]]\n     *\/\n    public function checkReferences(string $collectionId): array;\n\n    \/**\n     * Löscht Collection NUR wenn keine Referenzen existieren\n     * @throws ConstraintViolationException wenn referenziert\n     *\/\n    public function safeDelete(string $collectionId): void;\n\n    \/**\n     * Deaktiviert Collection (statt Löschung) - sicherer Weg\n     *\/\n    public function deactivate(string $collectionId): void;\n}\n```\n\n```php\n\/\/ CollectionRepository Implementation\nclass CollectionRepository implements CollectionRepositoryInterface\n{\n    public function checkReferences(string $collectionId): array\n    {\n        $references = [];\n\n        \/\/ Chat-Sessions prüfen (JSON-Array durchsuchen)\n        $chatCount = $this->db->fetchColumn(\"\n            SELECT COUNT(*) FROM ki_content.chat_sessions\n            WHERE JSON_CONTAINS(collections, ?, '$')\n        \", [json_encode($collectionId)]);\n\n        if ($chatCount > 0) {\n            $references[] = ['type' => 'chat_sessions', 'count' => $chatCount];\n        }\n\n        \/\/ Content-Orders prüfen\n        $orderCount = $this->db->fetchColumn(\"\n            SELECT COUNT(*) FROM ki_content.content_orders\n            WHERE JSON_CONTAINS(collections, ?, '$')\n        \", [json_encode($collectionId)]);\n\n        if ($orderCount > 0) {\n            $references[] = ['type' => 'content_orders', 'count' => $orderCount];\n        }\n\n        \/\/ User-Preferences prüfen\n        $prefCount = $this->db->fetchColumn(\"\n            SELECT COUNT(*) FROM ki_dev.user_preferences\n            WHERE JSON_CONTAINS(default_collections, ?, '$')\n        \", [json_encode($collectionId)]);\n\n        if ($prefCount > 0) {\n            $references[] = ['type' => 'user_preferences', 'count' => $prefCount];\n        }\n\n        return [\n            'has_references' => count($references) > 0,\n            'references' => $references,\n        ];\n    }\n\n    public function safeDelete(string $collectionId): void\n    {\n        $check = $this->checkReferences($collectionId);\n\n        if ($check['has_references']) {\n            $details = array_map(\n                fn($r) => \"{$r['type']}: {$r['count']}\",\n                $check['references']\n            );\n            throw new ConstraintViolationException(\n                \"Collection '$collectionId' is still referenced: \" . implode(', ', $details)\n            );\n        }\n\n        $this->db->delete('rag_collections', ['collection_id' => $collectionId]);\n    }\n\n    public function deactivate(string $collectionId): void\n    {\n        \/\/ Sicherer als Löschen - Collection bleibt für Historie\n        $this->db->update('rag_collections', [\n            'is_active' => false,\n            'is_searchable' => false,\n        ], ['collection_id' => $collectionId]);\n    }\n}\n```\n\n**Admin-UI Empfehlung:**\n```\n┌─────────────────────────────────────────────────┐\n│ Collection \"documents\" löschen?                  │\n├─────────────────────────────────────────────────┤\n│ ⚠️ Diese Collection wird noch verwendet:        │\n│   • 23 Chat-Sessions                            │\n│   • 5 Content-Orders                            │\n│   • 1 User-Preference                           │\n│                                                 │\n│ [Deaktivieren (sicher)]  [Abbrechen]           │\n└─────────────────────────────────────────────────┘\n```"
    }
}

Response

{
    "tool_response": {
        "filePath": "\/var\/www\/dev.campus.systemische-tools.de\/docs\/plan-form-components-refactoring.md",
        "oldString": "### 0b.6 Repository-Interface\n\n```php\n\/\/ CollectionRepository.php\ninterface CollectionRepositoryInterface\n{\n    \/\/ Für Dropdowns\n    public function getActive(): array;\n    public function getSearchable(): array;\n\n    \/\/ Für Admin\n    public function findAll(): array;\n    public function findById(int $id): ?array;\n    public function findByCollectionId(string $collectionId): ?array;\n\n    \/\/ Für Sync\n    public function upsert(array $data): void;\n    public function updateMetadata(string $collectionId, array $metadata): void;\n\n    \/\/ Validierung\n    public function isActive(string $collectionId): bool;\n    public function isSearchable(string $collectionId): bool;\n}\n```",
        "newString": "### 0b.6 Repository-Interface\n\n```php\n\/\/ CollectionRepository.php\ninterface CollectionRepositoryInterface\n{\n    \/\/ Für Dropdowns\n    public function getActive(): array;\n    public function getSearchable(): array;\n\n    \/\/ Für Admin\n    public function findAll(): array;\n    public function findById(int $id): ?array;\n    public function findByCollectionId(string $collectionId): ?array;\n\n    \/\/ Für Sync\n    public function upsert(array $data): void;\n    public function updateMetadata(string $collectionId, array $metadata): void;\n\n    \/\/ Validierung\n    public function isActive(string $collectionId): bool;\n    public function isSearchable(string $collectionId): bool;\n\n    \/\/ ══════════════════════════════════════════════════════════════════\n    \/\/ KRITISCH: Sichere Löschung mit Referenz-Prüfung\n    \/\/ ══════════════════════════════════════════════════════════════════\n\n    \/**\n     * Prüft ob Collection noch referenziert wird\n     * @return array ['has_references' => bool, 'references' => [...]]\n     *\/\n    public function checkReferences(string $collectionId): array;\n\n    \/**\n     * Löscht Collection NUR wenn keine Referenzen existieren\n     * @throws ConstraintViolationException wenn referenziert\n     *\/\n    public function safeDelete(string $collectionId): void;\n\n    \/**\n     * Deaktiviert Collection (statt Löschung) - sicherer Weg\n     *\/\n    public function deactivate(string $collectionId): void;\n}\n```\n\n```php\n\/\/ CollectionRepository Implementation\nclass CollectionRepository implements CollectionRepositoryInterface\n{\n    public function checkReferences(string $collectionId): array\n    {\n        $references = [];\n\n        \/\/ Chat-Sessions prüfen (JSON-Array durchsuchen)\n        $chatCount = $this->db->fetchColumn(\"\n            SELECT COUNT(*) FROM ki_content.chat_sessions\n            WHERE JSON_CONTAINS(collections, ?, '$')\n        \", [json_encode($collectionId)]);\n\n        if ($chatCount > 0) {\n            $references[] = ['type' => 'chat_sessions', 'count' => $chatCount];\n        }\n\n        \/\/ Content-Orders prüfen\n        $orderCount = $this->db->fetchColumn(\"\n            SELECT COUNT(*) FROM ki_content.content_orders\n            WHERE JSON_CONTAINS(collections, ?, '$')\n        \", [json_encode($collectionId)]);\n\n        if ($orderCount > 0) {\n            $references[] = ['type' => 'content_orders', 'count' => $orderCount];\n        }\n\n        \/\/ User-Preferences prüfen\n        $prefCount = $this->db->fetchColumn(\"\n            SELECT COUNT(*) FROM ki_dev.user_preferences\n            WHERE JSON_CONTAINS(default_collections, ?, '$')\n        \", [json_encode($collectionId)]);\n\n        if ($prefCount > 0) {\n            $references[] = ['type' => 'user_preferences', 'count' => $prefCount];\n        }\n\n        return [\n            'has_references' => count($references) > 0,\n            'references' => $references,\n        ];\n    }\n\n    public function safeDelete(string $collectionId): void\n    {\n        $check = $this->checkReferences($collectionId);\n\n        if ($check['has_references']) {\n            $details = array_map(\n                fn($r) => \"{$r['type']}: {$r['count']}\",\n                $check['references']\n            );\n            throw new ConstraintViolationException(\n                \"Collection '$collectionId' is still referenced: \" . implode(', ', $details)\n            );\n        }\n\n        $this->db->delete('rag_collections', ['collection_id' => $collectionId]);\n    }\n\n    public function deactivate(string $collectionId): void\n    {\n        \/\/ Sicherer als Löschen - Collection bleibt für Historie\n        $this->db->update('rag_collections', [\n            'is_active' => false,\n            'is_searchable' => false,\n        ], ['collection_id' => $collectionId]);\n    }\n}\n```\n\n**Admin-UI Empfehlung:**\n```\n┌─────────────────────────────────────────────────┐\n│ Collection \"documents\" löschen?                  │\n├─────────────────────────────────────────────────┤\n│ ⚠️ Diese Collection wird noch verwendet:        │\n│   • 23 Chat-Sessions                            │\n│   • 5 Content-Orders                            │\n│   • 1 User-Preference                           │\n│                                                 │\n│ [Deaktivieren (sicher)]  [Abbrechen]           │\n└─────────────────────────────────────────────────┘\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## Projekt-Kontext (für externe Supervision)\n\n### Was ist das System?\n\n**Campus.systemische-tools.de** ist ein KI-gestütztes Content-Management-System mit zwei Hauptmodulen:\n\n| Modul | Beschreibung | Hauptfunktionen |\n|-------|--------------|-----------------|\n| **Chat-Interface** | RAG-basierte Konversation mit verschiedenen LLMs | Session-Management, Multi-Collection-Suche, Streaming-Responses |\n| **Content Studio** | Automatisierte Content-Generierung mit Qualitätssicherung | Briefing → Generierung → Kritik → Revision → Freigabe |\n\n### Warum dieses Refactoring?\n\n**Problem:**\n- Chat und Content Studio wurden unabhängig entwickelt\n- Gleiche UI-Elemente (Modell-Dropdown, Collections, etc.) existieren in unterschiedlichen Implementierungen\n- Inkonsistente UX: Modell-Dropdown mit\/ohne Optgroups, Collections als Multi-Select\/Checkboxes\/Single-Select\n\n**Symptome:**\n- Doppelter Code in Views\n- Unterschiedliche Variablennamen (`$profiles` vs `$authorProfiles`)\n- Keine zentrale Verwaltung für LLM-Modelle und Collections\n- Keine persistenten User-Präferenzen\n\n**Ziel:**\n- Einheitliche Partials für alle Form-Elemente\n- Zentrale Admin-Seiten für LLM-Modelle und Collections\n- User-Präferenzen mit 3-stufiger Werte-Hierarchie\n- Contracts und Structures in beiden Systemen nutzbar (Multi-Select)\n\n### Technologie-Stack\n\n| Schicht | Technologie | Details |\n|---------|-------------|---------|\n| **Backend** | PHP 8.2 | MVC-Pattern in `\/src\/` |\n| **Frontend** | HTML + HTMX | Reactive ohne SPA-Framework |\n| **Datenbank** | MariaDB 10.11 | Zwei Datenbanken (siehe unten) |\n| **Vector-DB** | Qdrant | RAG-Embeddings, Collections |\n| **LLM-APIs** | Anthropic Claude, Ollama | Cloud + lokal |\n| **Pipeline** | Python 3.11 | Content-Generierung, Embedding |\n| **Styling** | CSS Custom Properties | Theme-Support |\n\n### Datenbank-Architektur\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│                        MariaDB                                   │\n├─────────────────────────────┬───────────────────────────────────┤\n│         ki_dev              │           ki_content              │\n│   (Infrastruktur\/Config)    │        (Laufzeitdaten)            │\n├─────────────────────────────┼───────────────────────────────────┤\n│ • llm_models        [NEU]   │ • chat_sessions      [ERWEITERT]  │\n│ • llm_presets       [NEU]   │ • chat_messages                   │\n│ • rag_collections   [NEU]   │ • content_orders     [MIGRIERT]   │\n│ • user_preferences  [NEU]   │ • content_versions                │\n│ • content_config            │ • content_critiques               │\n│ • tasks                     │ • content_sources                 │\n│ • contracts                 │                                   │\n│ • dokumentation             │                                   │\n└─────────────────────────────┴───────────────────────────────────┘\n```\n\n### Externe Abhängigkeiten\n\n| Dienst | Verwendung | Ausfallverhalten |\n|--------|------------|------------------|\n| **Anthropic API** | Claude-Modelle, Chat-Completion | Fallback auf Ollama |\n| **Ollama (lokal)** | Lokale Modelle, Embeddings | Pflichtkomponente |\n| **Qdrant (lokal)** | Vektor-Suche, Collection-Metadaten | Pflichtkomponente |\n\n### Architektur-Prinzipien (angewendet)\n\n| Prinzip | Anwendung in diesem Plan |\n|---------|--------------------------|\n| **DRY** | Partials als Single Source für Form-Elemente |\n| **SRP** | Jedes Partial = eine Aufgabe |\n| **KISS** | Context Limit hardcoded, keine Over-Engineering |\n| **YAGNI** | Section 8 definiert klare Grenzen |\n| **MVC** | Controller → View (Partials) → Repository |\n\n### Wichtige Konventionen\n\n1. **MCP-Server:** Alle DB-Zugriffe via MCP-Tools (kein direktes SQL)\n2. **Kein Git:** Server ohne Versionskontrolle\n3. **Dev-First:** Entwicklung auf dev.campus, dann Sync zu prod\n4. **Quality-Gate:** `\/opt\/scripts\/php-check.sh` muss vor Sync bestehen\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,          -- ... [TRUNCATED-b900d8462682d986]",
        "structuredPatch": [
            {
                "oldStart": 382,
                "oldLines": 9,
                "newStart": 382,
                "newLines": 116,
                "lines": [
                    "     \/\/ Validierung",
                    "     public function isActive(string $collectionId): bool;",
                    "     public function isSearchable(string $collectionId): bool;",
                    "+",
                    "+    \/\/ ══════════════════════════════════════════════════════════════════",
                    "+    \/\/ KRITISCH: Sichere Löschung mit Referenz-Prüfung",
                    "+    \/\/ ══════════════════════════════════════════════════════════════════",
                    "+",
                    "+    \/**",
                    "+     * Prüft ob Collection noch referenziert wird",
                    "+     * @return array ['has_references' => bool, 'references' => [...]]",
                    "+     *\/",
                    "+    public function checkReferences(string $collectionId): array;",
                    "+",
                    "+    \/**",
                    "+     * Löscht Collection NUR wenn keine Referenzen existieren",
                    "+     * @throws ConstraintViolationException wenn referenziert",
                    "+     *\/",
                    "+    public function safeDelete(string $collectionId): void;",
                    "+",
                    "+    \/**",
                    "+     * Deaktiviert Collection (statt Löschung) - sicherer Weg",
                    "+     *\/",
                    "+    public function deactivate(string $collectionId): void;",
                    " }",
                    " ```",
                    " ",
                    "+```php",
                    "+\/\/ CollectionRepository Implementation",
                    "+class CollectionRepository implements CollectionRepositoryInterface",
                    "+{",
                    "+    public function checkReferences(string $collectionId): array",
                    "+    {",
                    "+        $references = [];",
                    "+",
                    "+        \/\/ Chat-Sessions prüfen (JSON-Array durchsuchen)",
                    "+        $chatCount = $this->db->fetchColumn(\"",
                    "+            SELECT COUNT(*) FROM ki_content.chat_sessions",
                    "+            WHERE JSON_CONTAINS(collections, ?, '$')",
                    "+        \", [json_encode($collectionId)]);",
                    "+",
                    "+        if ($chatCount > 0) {",
                    "+            $references[] = ['type' => 'chat_sessions', 'count' => $chatCount];",
                    "+        }",
                    "+",
                    "+        \/\/ Content-Orders prüfen",
                    "+        $orderCount = $this->db->fetchColumn(\"",
                    "+            SELECT COUNT(*) FROM ki_content.content_orders",
                    "+            WHERE JSON_CONTAINS(collections, ?, '$')",
                    "+        \", [json_encode($collectionId)]);",
                    "+",
                    "+        if ($orderCount > 0) {",
                    "+            $references[] = ['type' => 'content_orders', 'count' => $orderCount];",
                    "+        }",
                    "+",
                    "+        \/\/ User-Preferences prüfen",
                    "+        $prefCount = $this->db->fetchColumn(\"",
                    "+            SELECT COUNT(*) FROM ki_dev.user_preferences",
                    "+            WHERE JSON_CONTAINS(default_collections, ?, '$')",
                    "+        \", [json_encode($collectionId)]);",
                    "+",
                    "+        if ($prefCount > 0) {",
                    "+            $references[] = ['type' => 'user_preferences', 'count' => $prefCount];",
                    "+        }",
                    "+",
                    "+        return [",
                    "+            'has_references' => count($references) > 0,",
                    "+            'references' => $references,",
                    "+        ];",
                    "+    }",
                    "+",
                    "+    public function safeDelete(string $collectionId): void",
                    "+    {",
                    "+        $check = $this->checkReferences($collectionId);",
                    "+",
                    "+        if ($check['has_references']) {",
                    "+            $details = array_map(",
                    "+                fn($r) => \"{$r['type']}: {$r['count']}\",",
                    "+                $check['references']",
                    "+            );",
                    "+            throw new ConstraintViolationException(",
                    "+                \"Collection '$collectionId' is still referenced: \" . implode(', ', $details)",
                    "+            );",
                    "+        }",
                    "+",
                    "+        $this->db->delete('rag_collections', ['collection_id' => $collectionId]);",
                    "+    }",
                    "+",
                    "+    public function deactivate(string $collectionId): void",
                    "+    {",
                    "+        \/\/ Sicherer als Löschen - Collection bleibt für Historie",
                    "+        $this->db->update('rag_collections', [",
                    "+            'is_active' => false,",
                    "+            'is_searchable' => false,",
                    "+        ], ['collection_id' => $collectionId]);",
                    "+    }",
                    "+}",
                    "+```",
                    "+",
                    "+**Admin-UI Empfehlung:**",
                    "+```",
                    "+┌─────────────────────────────────────────────────┐",
                    "+│ Collection \"documents\" löschen?                  │",
                    "+├─────────────────────────────────────────────────┤",
                    "+│ ⚠️ Diese Collection wird noch verwendet:        │",
                    "+│   • 23 Chat-Sessions                            │",
                    "+│   • 5 Content-Orders                            │",
                    "+│   • 1 User-Preference                           │",
                    "+│                                                 │",
                    "+│ [Deaktivieren (sicher)]  [Abbrechen]           │",
                    "+└─────────────────────────────────────────────────┘",
                    "+```",
                    "+",
                    " ### 0b.7 Dropdown-Query",
                    " ",
                    " ```php"
                ]
            }
        ],
        "userModified": false,
        "replaceAll": false
    }
}
← Vorheriger Zur Liste Nächster →