repository = new SemanticExplorerRepository(); $this->vectorSearchService = new VectorSearchService(); } /** * 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 = $this->getString('status'); $search = $this->getString('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'] = $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'); $page = $this->getPage(); $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) { $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->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 = $this->getString('q'); $limit = $this->getLimit(20, 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 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->repository->getChunkById($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 * Semantische Analyse pro Chunk */ public function semantik(): void { $sentiment = $this->getString('sentiment'); $page = $this->getPage(); $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'] = $this->decodeJson($s['keywords'] ?? null); $s['topics_decoded'] = $this->decodeJson($s['topics'] ?? null); } $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), ]); } }