Backup #219

ID219
Dateipfad/var/www/dev.campus.systemische-tools.de/src/Controller/SemanticExplorerController.php
Version4
Typ modified
Größe24.7 KB
Hash9f1d9c58aa654bffcf101a48f24c247d6b70ca8cdc390828f069df35423d073d
Datum2025-12-22 01:44:30
Geändert vonclaude-code-hook
GrundClaude Code Pre-Hook Backup vor Edit-Operation
Datei existiert Ja

Dateiinhalt

<?php

namespace Controller;

use Framework\Controller;
use Infrastructure\SemanticExplorerRepository;

/**
 * SemanticExplorerController - Nutzdaten Explorer
 *
 * Zeigt Dokumente und Chunks aus Nextcloud (documents, chunks Tabellen).
 * Für Endnutzer - Coaching-Materialien, PDFs, später Mails.
 */
class SemanticExplorerController extends Controller
{
    private SemanticExplorerRepository $repository;

    public function __construct()
    {
        $this->repository = new SemanticExplorerRepository();
    }

    /**
     * GET /semantic-explorer
     * Dashboard mit Statistiken
     */
    public function index(): void
    {
        $docStats = $this->repository->getDocumentStats();
        $chunkStats = $this->repository->getChunkStats();
        $documents = $this->repository->getDocuments();
        $recentChunks = $this->repository->getRecentChunks(5);

        $this->view('semantic-explorer.index', [
            'title' => 'Semantic Explorer',
            'docStats' => $docStats,
            'chunkStats' => $chunkStats,
            'documents' => $documents,
            'recentChunks' => $recentChunks,
        ]);
    }

    /**
     * GET /semantic-explorer/dokumente
     * Liste aller Dokumente
     */
    public function dokumente(): void
    {
        $status = $_GET['status'] ?? '';
        $search = $_GET['search'] ?? '';

        $documents = $this->repository->getDocumentsFiltered($status, $search);

        $this->view('semantic-explorer.dokumente.index', [
            'title' => 'Dokumente',
            'documents' => $documents,
            'currentStatus' => $status,
            'currentSearch' => $search,
        ]);
    }

    /**
     * GET /semantic-explorer/dokumente/{id}
     * Dokument-Details mit Chunks
     */
    public function dokumentShow(int $id): void
    {
        $document = $this->repository->getDocument($id);

        if ($document === null) {
            $this->notFound('Dokument nicht gefunden');
        }

        $chunks = $this->repository->getChunksForDocument($id);

        // Heading-Paths dekodieren
        foreach ($chunks as &$chunk) {
            $chunk['heading_path_decoded'] = json_decode($chunk['heading_path'] ?? '[]', true) ?: [];
            $chunk['metadata_decoded'] = json_decode($chunk['metadata'] ?? '{}', true) ?: [];
        }

        $this->view('semantic-explorer.dokumente.show', [
            'title' => $document['filename'],
            'document' => $document,
            'chunks' => $chunks,
        ]);
    }

    /**
     * GET /semantic-explorer/chunks
     * Liste aller Chunks
     */
    public function chunks(): void
    {
        $search = $_GET['search'] ?? '';
        $embedded = $_GET['embedded'] ?? '';
        $page = max(1, (int) ($_GET['page'] ?? 1));
        $limit = 50;
        $offset = ($page - 1) * $limit;

        $totalCount = $this->repository->getChunksCount($search, $embedded);
        $chunks = $this->repository->getChunksFiltered($search, $embedded, $limit, $offset);

        $this->view('semantic-explorer.chunks.index', [
            'title' => 'Chunks',
            'chunks' => $chunks,
            'currentSearch' => $search,
            'currentEmbedded' => $embedded,
            'currentPage' => $page,
            'totalCount' => $totalCount,
            'totalPages' => ceil($totalCount / $limit),
        ]);
    }

    /**
     * GET /semantic-explorer/chunks/{id}
     * Chunk-Details
     */
    public function chunkShow(int $id): void
    {
        $chunk = $this->repository->getChunk($id);

        if ($chunk === null) {
            http_response_code(404);
            echo '404 - Chunk nicht gefunden';

            return;
        }

        // JSON-Felder dekodieren
        $chunk['heading_path_decoded'] = json_decode($chunk['heading_path'] ?? '[]', true) ?: [];
        $chunk['metadata_decoded'] = json_decode($chunk['metadata'] ?? '{}', true) ?: [];

        // Nachbar-Chunks
        $prevChunk = $this->repository->getChunkByDocumentAndIndex(
            $chunk['document_id'],
            $chunk['chunk_index'] - 1
        );
        $nextChunk = $this->repository->getChunkByDocumentAndIndex(
            $chunk['document_id'],
            $chunk['chunk_index'] + 1
        );

        $this->view('semantic-explorer.chunks.show', [
            'title' => 'Chunk #' . $chunk['id'],
            'chunk' => $chunk,
            'prevChunk' => $prevChunk,
            'nextChunk' => $nextChunk,
        ]);
    }

    /**
     * GET /semantic-explorer/suche
     * Semantische Suche in Nutzdaten
     */
    public function suche(): void
    {
        $query = $_GET['q'] ?? '';
        $limit = min(20, max(1, (int) ($_GET['limit'] ?? 10)));

        $results = [];

        if ($query !== '') {
            // Vektor-Suche via Qdrant
            $results = $this->vectorSearch($query, $limit);
        }

        $this->view('semantic-explorer.suche', [
            'title' => 'Semantische Suche',
            'query' => $query,
            'results' => $results,
            'limit' => $limit,
        ]);
    }

    /**
     * Vektor-Suche in documents Collection
     */
    private function vectorSearch(string $query, int $limit): array
    {
        // Embedding generieren
        $embedding = $this->getEmbedding($query);
        if (empty($embedding)) {
            return [];
        }

        // Qdrant suchen
        $response = $this->qdrantSearch($embedding, $limit);
        if (empty($response)) {
            return [];
        }

        // Chunk-Details aus DB laden
        $results = [];
        foreach ($response as $point) {
            $chunkId = $point['payload']['chunk_id'] ?? null;
            if ($chunkId === null) {
                continue;
            }

            $chunk = $this->repository->getChunkById($chunkId);

            if ($chunk !== null) {
                $chunk['score'] = $point['score'];
                $chunk['heading_path_decoded'] = json_decode($chunk['heading_path'] ?? '[]', true) ?: [];
                $results[] = $chunk;
            }
        }

        return $results;
    }

    /**
     * Embedding via Ollama
     */
    private function getEmbedding(string $text): array
    {
        $ch = curl_init('http://localhost:11434/api/embeddings');
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
            CURLOPT_POSTFIELDS => json_encode([
                'model' => 'mxbai-embed-large',
                'prompt' => $text,
            ]),
        ]);

        $response = curl_exec($ch);
        curl_close($ch);

        $data = json_decode($response, true);

        return $data['embedding'] ?? [];
    }

    /**
     * Qdrant-Suche
     */
    private function qdrantSearch(array $embedding, int $limit): array
    {
        $ch = curl_init('http://localhost:6333/collections/documents/points/search');
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
            CURLOPT_POSTFIELDS => json_encode([
                'vector' => $embedding,
                'limit' => $limit,
                'with_payload' => true,
            ]),
        ]);

        $response = curl_exec($ch);
        curl_close($ch);

        $data = json_decode($response, true);

        return $data['result'] ?? [];
    }

    /**
     * GET /semantic-explorer/entitaeten
     * Liste aller Entitaeten
     */
    public function entitaeten(): void
    {
        $type = $_GET['type'] ?? '';
        $search = $_GET['search'] ?? '';

        $entities = $this->repository->getEntitiesFiltered($type, $search);
        $stats = $this->repository->getEntityStats();

        $this->view('semantic-explorer.entitaeten.index', [
            'title' => 'Entitaeten',
            'entities' => $entities,
            'stats' => $stats,
            'currentType' => $type,
            'currentSearch' => $search,
        ]);
    }

    /**
     * GET /semantic-explorer/entitaeten/{id}
     * Entitaet-Details
     */
    public function entitaetShow(int $id): void
    {
        $entity = $this->repository->getEntity($id);

        if ($entity === null) {
            http_response_code(404);
            echo '404 - Entitaet nicht gefunden';

            return;
        }

        $synonyms = $this->repository->getEntitySynonyms($id);
        $outgoingRelations = $this->repository->getOutgoingRelations($id);
        $incomingRelations = $this->repository->getIncomingRelations($id);
        $chunks = $this->repository->getChunksForEntity($id);
        $classifications = $this->repository->getEntityClassifications($id);

        $this->view('semantic-explorer.entitaeten.show', [
            'title' => $entity['name'],
            'entity' => $entity,
            'synonyms' => $synonyms,
            'outgoingRelations' => $outgoingRelations,
            'incomingRelations' => $incomingRelations,
            'chunks' => $chunks,
            'classifications' => $classifications,
        ]);
    }

    /**
     * GET /semantic-explorer/relationen
     * Beziehungen zwischen Entitaeten
     */
    public function relationen(): void
    {
        $type = $_GET['type'] ?? '';

        $relations = $this->repository->getRelationsFiltered($type);
        $relationTypes = $this->repository->getRelationTypes();
        $stats = $this->repository->getRelationStats();

        $this->view('semantic-explorer.relationen', [
            'title' => 'Relationen',
            'relations' => $relations,
            'relationTypes' => $relationTypes,
            'stats' => $stats,
            'currentType' => $type,
        ]);
    }

    /**
     * GET /semantic-explorer/taxonomie
     * Hierarchische Kategorisierung
     */
    public function taxonomie(): void
    {
        $terms = $this->repository->getTaxonomyTerms();
        $hierarchy = $this->buildTaxonomyTree($terms);
        $stats = $this->repository->getTaxonomyStats();

        $this->view('semantic-explorer.taxonomie', [
            'title' => 'Taxonomie',
            'terms' => $terms,
            'hierarchy' => $hierarchy,
            'stats' => $stats,
        ]);
    }

    /**
     * Baut Baum aus flacher Liste
     */
    private function buildTaxonomyTree(array $items, ?int $parentId = null): array
    {
        $tree = [];
        foreach ($items as $item) {
            if ($item['parent_id'] == $parentId) {
                $item['children'] = $this->buildTaxonomyTree($items, $item['id']);
                $tree[] = $item;
            }
        }

        return $tree;
    }

    /**
     * GET /semantic-explorer/ontologie
     * Konzept-Klassen
     */
    public function ontologie(): void
    {
        $classes = $this->repository->getOntologyClasses();

        // Properties dekodieren
        foreach ($classes as &$class) {
            $class['properties_decoded'] = json_decode($class['properties'] ?? '{}', true) ?: [];
        }

        $stats = $this->repository->getOntologyStats();

        $this->view('semantic-explorer.ontologie', [
            'title' => 'Ontologie',
            'classes' => $classes,
            'stats' => $stats,
        ]);
    }

    /**
     * GET /semantic-explorer/semantik
     * Semantische Analyse pro Chunk
     */
    public function semantik(): void
    {
        $sentiment = $_GET['sentiment'] ?? '';
        $page = max(1, (int) ($_GET['page'] ?? 1));
        $limit = 50;
        $offset = ($page - 1) * $limit;

        $totalCount = $this->repository->getSemanticsCount($sentiment);
        $semantics = $this->repository->getSemanticsFiltered($sentiment, $limit, $offset);

        // JSON dekodieren
        foreach ($semantics as &$s) {
            $s['keywords_decoded'] = json_decode($s['keywords'] ?? '[]', true) ?: [];
            $s['topics_decoded'] = json_decode($s['topics'] ?? '[]', true) ?: [];
        }

        $stats = $this->repository->getSemanticStats();

        $this->view('semantic-explorer.semantik', [
            'title' => 'Semantik',
            'semantics' => $semantics,
            'stats' => $stats,
            'currentSentiment' => $sentiment,
            'currentPage' => $page,
            'totalCount' => $totalCount,
            'totalPages' => ceil($totalCount / $limit),
        ]);
    }

    // =========================================================================
    // ENTITIES - CRUD
    // =========================================================================

    /**
     * GET /semantic-explorer/entitaeten/new
     */
    public function entitaetNew(): void
    {
        $this->view('semantic-explorer.entitaeten.new', [
            'title' => 'Neue Entitaet',
            'types' => $this->repository->getEntityTypes(),
        ]);
    }

    /**
     * POST /semantic-explorer/entitaeten
     */
    public function entitaetStore(): void
    {
        $input = json_decode(file_get_contents('php://input'), true);

        $name = trim($input['name'] ?? '');
        $type = trim($input['type'] ?? '');
        $description = trim($input['description'] ?? '') ?: null;

        if ($name === '' || $type === '') {
            $this->json(['success' => false, 'error' => 'Name und Typ sind erforderlich'], 400);

            return;
        }

        try {
            $id = $this->repository->createEntity($name, $type, $description);
            $this->json(['success' => true, 'id' => $id]);
        } catch (\Exception $e) {
            $this->json(['success' => false, 'error' => $e->getMessage()], 500);
        }
    }

    /**
     * GET /semantic-explorer/entitaeten/{id}/edit
     */
    public function entitaetEdit(int $id): void
    {
        $entity = $this->repository->getEntity($id);

        if ($entity === null) {
            http_response_code(404);
            echo '404 - Entitaet nicht gefunden';

            return;
        }

        $this->view('semantic-explorer.entitaeten.edit', [
            'title' => 'Entitaet bearbeiten',
            'entity' => $entity,
            'types' => $this->repository->getEntityTypes(),
        ]);
    }

    /**
     * POST /semantic-explorer/entitaeten/{id}
     */
    public function entitaetUpdate(int $id): void
    {
        $input = json_decode(file_get_contents('php://input'), true);

        $name = trim($input['name'] ?? '');
        $type = trim($input['type'] ?? '');
        $description = trim($input['description'] ?? '') ?: null;

        if ($name === '' || $type === '') {
            $this->json(['success' => false, 'error' => 'Name und Typ sind erforderlich'], 400);

            return;

... (334 weitere Zeilen)

Vollständig herunterladen

Aktionen

Herunterladen

Andere Versionen dieser Datei

ID Version Typ Größe Datum
1991 56 modified 9.3 KB 2025-12-28 03:05
1982 55 modified 9.1 KB 2025-12-28 02:54
1699 54 modified 8.8 KB 2025-12-27 12:18
1107 53 modified 8.8 KB 2025-12-25 09:22
1106 52 modified 8.8 KB 2025-12-25 09:22
1105 51 modified 8.8 KB 2025-12-25 09:22
1104 50 modified 8.8 KB 2025-12-25 09:22
1099 49 modified 8.8 KB 2025-12-25 09:20
1098 48 modified 8.8 KB 2025-12-25 09:20
1097 47 modified 8.6 KB 2025-12-25 09:20
1082 46 modified 8.6 KB 2025-12-25 02:29
1081 45 modified 8.4 KB 2025-12-25 02:29
1071 44 modified 8.3 KB 2025-12-25 02:26
1070 43 modified 8.3 KB 2025-12-25 02:26
1069 42 modified 8.3 KB 2025-12-25 02:26
1068 41 modified 8.3 KB 2025-12-25 02:26
1067 40 modified 8.3 KB 2025-12-25 02:26
1066 39 modified 8.3 KB 2025-12-25 02:26
1065 38 modified 8.3 KB 2025-12-25 02:25
1064 37 modified 8.3 KB 2025-12-25 02:25
1063 36 modified 7.8 KB 2025-12-25 02:25
912 35 modified 7.7 KB 2025-12-23 16:41
910 34 modified 7.1 KB 2025-12-23 16:40
698 33 modified 7.2 KB 2025-12-23 07:53
676 32 modified 7.2 KB 2025-12-23 07:00
641 31 modified 7.2 KB 2025-12-23 04:47
612 30 modified 7.3 KB 2025-12-23 04:42
586 29 modified 7.2 KB 2025-12-23 04:24
339 28 modified 7.1 KB 2025-12-22 08:11
338 27 modified 7.0 KB 2025-12-22 08:11
337 26 modified 7.0 KB 2025-12-22 08:11
313 25 modified 7.0 KB 2025-12-22 08:05
312 24 modified 7.0 KB 2025-12-22 08:05
311 23 modified 7.0 KB 2025-12-22 08:05
310 22 modified 7.0 KB 2025-12-22 08:05
289 21 modified 7.1 KB 2025-12-22 08:00
288 20 modified 7.1 KB 2025-12-22 08:00
287 19 modified 7.1 KB 2025-12-22 08:00
286 18 modified 7.1 KB 2025-12-22 08:00
265 17 modified 8.5 KB 2025-12-22 02:07
264 16 modified 8.4 KB 2025-12-22 02:07
263 15 modified 14.7 KB 2025-12-22 02:06
262 14 modified 16.2 KB 2025-12-22 02:05
261 13 modified 19.2 KB 2025-12-22 02:04
260 12 modified 19.8 KB 2025-12-22 02:04
259 11 modified 22.8 KB 2025-12-22 02:02
258 10 modified 24.4 KB 2025-12-22 02:02
224 9 modified 24.4 KB 2025-12-22 01:44
223 8 modified 24.5 KB 2025-12-22 01:44
222 7 modified 24.5 KB 2025-12-22 01:44
221 6 modified 24.6 KB 2025-12-22 01:44
220 5 modified 24.6 KB 2025-12-22 01:44
219 4 modified 24.7 KB 2025-12-22 01:44
218 3 modified 24.7 KB 2025-12-22 01:44
46 2 modified 12.2 KB 2025-12-20 17:44
10 1 modified 23.5 KB 2025-12-20 16:35

← Zurück zur Übersicht