SemanticExplorerController.php

Code Hygiene Score: 95

Keine Issues gefunden.

Dependencies 14

Klassen 1

Funktionen 11

Versionen 56

Code

<?php

declare(strict_types=1);

namespace Controller;

// @responsibility: HTTP-Endpunkte für Semantic Explorer (Nutzdaten, Vektor-Suche)

use Domain\Repository\ChunkRepositoryInterface;
use Domain\Repository\DocumentRepositoryInterface;
use Domain\Repository\EntityRepositoryInterface;
use Domain\Repository\RelationRepositoryInterface;
use Domain\Repository\SemanticSearchRepositoryInterface;
use Framework\Controller;
use Infrastructure\AI\VectorSearchService;

class SemanticExplorerController extends Controller
{
    private DocumentRepositoryInterface $documentRepository;
    private ChunkRepositoryInterface $chunkRepository;
    private EntityRepositoryInterface $entityRepository;
    private RelationRepositoryInterface $relationRepository;
    private SemanticSearchRepositoryInterface $semanticSearchRepository;
    private VectorSearchService $vectorSearchService;

    public function __construct(
        DocumentRepositoryInterface $documentRepository,
        ChunkRepositoryInterface $chunkRepository,
        EntityRepositoryInterface $entityRepository,
        RelationRepositoryInterface $relationRepository,
        SemanticSearchRepositoryInterface $semanticSearchRepository,
        VectorSearchService $vectorSearchService
    ) {
        $this->documentRepository = $documentRepository;
        $this->chunkRepository = $chunkRepository;
        $this->entityRepository = $entityRepository;
        $this->relationRepository = $relationRepository;
        $this->semanticSearchRepository = $semanticSearchRepository;
        $this->vectorSearchService = $vectorSearchService;
    }

    /**
     * GET /semantic-explorer
     * Dashboard mit Statistiken
     */
    public function index(): void
    {
        $docStats = $this->documentRepository->getStats();
        $chunkStats = $this->chunkRepository->getStats();
        $semanticStats = $this->semanticSearchRepository->getSemanticStats();
        $textSemanticStats = $this->semanticSearchRepository->getTextSemanticStats();
        $knowledgeSemanticStats = $this->semanticSearchRepository->getKnowledgeSemanticStats();
        $documents = $this->documentRepository->findAll();
        $recentChunks = $this->chunkRepository->findRecent(5);

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

    /**
     * GET /semantic-explorer/dokumente
     * Liste aller Dokumente
     */
    public function dokumente(): void
    {
        $status = $this->getString('status');
        $search = $this->getString('search');

        $documents = $this->documentRepository->findFiltered($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->documentRepository->find($id);

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

        $chunks = $this->chunkRepository->findByDocument($id);

        // Heading-Paths dekodieren
        foreach ($chunks as &$chunk) {
            $chunk['heading_path_decoded'] = $this->decodeJson($chunk['heading_path'] ?? null);
            $chunk['metadata_decoded'] = $this->decodeJson($chunk['metadata'] ?? null);
        }

        $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 = $this->getString('search');
        $embedded = $this->getString('embedded');
        $pagination = $this->getPagination(50);

        $totalCount = $this->chunkRepository->count($search, $embedded);
        $chunks = $this->chunkRepository->findFiltered($search, $embedded, $pagination->limit, $pagination->offset);
        $pagination = $pagination->withTotal($totalCount);

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

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

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

        // JSON-Felder dekodieren
        $chunk['heading_path_decoded'] = $this->decodeJson($chunk['heading_path'] ?? null);
        $chunk['metadata_decoded'] = $this->decodeJson($chunk['metadata'] ?? null);

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

        // Text-Semantik laden
        $textSemantics = $this->chunkRepository->getTextSemantics($id);

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

    /**
     * GET /semantic-explorer/suche
     * Semantische Suche in Nutzdaten
     */
    public function suche(): void
    {
        $query = $this->getString('q');
        $limit = $this->getLimit(20, 10);

        $results = [];

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

        // HTMX: Return only search results partial
        if ($this->isHtmxRequest()) {
            $this->partial('semantic-explorer.partials.search-results', [
                'query' => $query,
                'results' => $results,
                'limit' => $limit,
            ]);

            return;
        }

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

    /**
     * Vektor-Suche in documents Collection using VectorSearchService
     */
    private function vectorSearch(string $query, int $limit): array
    {
        // Search via service
        $response = $this->vectorSearchService->search($query, 'documents', $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->chunkRepository->find($chunkId);

            if ($chunk !== null) {
                $chunk['score'] = $point['score'];
                $chunk['heading_path_decoded'] = $this->decodeJson($chunk['heading_path'] ?? null);
                $results[] = $chunk;
            }
        }

        return $results;
    }

    /**
     * GET /semantic-explorer/semantik
     * Begriffe mit Bedeutungen
     */
    public function semantik(): void
    {
        $search = $this->getString('search');
        $type = $this->getString('type');
        $pagination = $this->getPagination(50);

        $totalCount = $this->semanticSearchRepository->countEntitySemantics($search, $type);
        $semantics = $this->semanticSearchRepository->findEntitySemantics($search, $type, $pagination->limit, $pagination->offset);
        $pagination = $pagination->withTotal($totalCount);

        $stats = $this->semanticSearchRepository->getSemanticStats();
        $entityTypes = $this->entityRepository->getStats();

        $this->view('semantic-explorer.semantik', [
            'title' => 'Semantik',
            'semantics' => $semantics,
            'stats' => $stats,
            'entityTypes' => $entityTypes,
            'currentSearch' => $search,
            'currentType' => $type,
            'currentPage' => $pagination->page,
            'totalCount' => $pagination->totalCount,
            'totalPages' => $pagination->totalPages(),
        ]);
    }

    /**
     * GET /semantic-explorer/graph
     * Entity-Relations Graph Visualisierung
     */
    public function graph(): void
    {
        $entityTypes = $this->entityRepository->getStats();
        $relationTypes = $this->relationRepository->getTypes();

        $this->view('semantic-explorer.graph', [
            'title' => 'Entity Graph',
            'entityTypes' => $entityTypes,
            'relationTypes' => $relationTypes,
        ]);
    }

    /**
     * GET /semantic-explorer/graph-data
     * JSON-Daten für Graph-Visualisierung
     */
    public function graphData(): void
    {
        $data = $this->semanticSearchRepository->getGraphData();
        $this->json($data);
    }
}
← Übersicht Graph