{
"tool_response": {
"filePath": "\/var\/www\/dev.campus.systemische-tools.de\/src\/Controller\/SemanticExplorerController.php",
"oldString": " \/**\n * Vektor-Suche in documents Collection\n *\/\n private function vectorSearch(string $query, int $limit): array\n {\n \/\/ Embedding generieren\n $embedding = $this->getEmbedding($query);\n if (empty($embedding)) {\n return [];\n }\n\n \/\/ Qdrant suchen\n $response = $this->qdrantSearch($embedding, $limit);\n if (empty($response)) {\n return [];\n }\n\n \/\/ Chunk-Details aus DB laden\n $results = [];\n foreach ($response as $point) {\n $chunkId = $point['payload']['chunk_id'] ?? null;\n if ($chunkId === null) {\n continue;\n }\n\n $chunk = $this->repository->getChunkById($chunkId);\n\n if ($chunk !== null) {\n $chunk['score'] = $point['score'];\n $chunk['heading_path_decoded'] = json_decode($chunk['heading_path'] ?? '[]', true) ?: [];\n $results[] = $chunk;\n }\n }\n\n return $results;\n }\n\n \/**\n * Embedding via Ollama\n *\/\n private function getEmbedding(string $text): array\n {\n $ch = curl_init('http:\/\/localhost:11434\/api\/embeddings');\n curl_setopt_array($ch, [\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_POST => true,\n CURLOPT_HTTPHEADER => ['Content-Type: application\/json'],\n CURLOPT_POSTFIELDS => json_encode([\n 'model' => 'mxbai-embed-large',\n 'prompt' => $text,\n ]),\n ]);\n\n $response = curl_exec($ch);\n curl_close($ch);\n\n $data = json_decode($response, true);\n\n return $data['embedding'] ?? [];\n }\n\n \/**\n * Qdrant-Suche\n *\/\n private function qdrantSearch(array $embedding, int $limit): array\n {\n $ch = curl_init('http:\/\/localhost:6333\/collections\/documents\/points\/search');\n curl_setopt_array($ch, [\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_POST => true,\n CURLOPT_HTTPHEADER => ['Content-Type: application\/json'],\n CURLOPT_POSTFIELDS => json_encode([\n 'vector' => $embedding,\n 'limit' => $limit,\n 'with_payload' => true,\n ]),\n ]);\n\n $response = curl_exec($ch);\n curl_close($ch);\n\n $data = json_decode($response, true);\n\n return $data['result'] ?? [];\n }",
"newString": " \/**\n * Vektor-Suche in documents Collection using VectorSearchService\n *\/\n private function vectorSearch(string $query, int $limit): array\n {\n \/\/ Search via service\n $response = $this->vectorSearchService->search($query, 'documents', $limit);\n\n if (empty($response)) {\n return [];\n }\n\n \/\/ Chunk-Details aus DB laden\n $results = [];\n foreach ($response as $point) {\n $chunkId = $point['payload']['chunk_id'] ?? null;\n if ($chunkId === null) {\n continue;\n }\n\n $chunk = $this->repository->getChunkById($chunkId);\n\n if ($chunk !== null) {\n $chunk['score'] = $point['score'];\n $chunk['heading_path_decoded'] = json_decode($chunk['heading_path'] ?? '[]', true) ?: [];\n $results[] = $chunk;\n }\n }\n\n return $results;\n }",
"originalFile": "<?php\n\nnamespace Controller;\n\nuse Framework\\Controller;\nuse Infrastructure\\AI\\VectorSearchService;\nuse Infrastructure\\SemanticExplorerRepository;\n\n\/**\n * SemanticExplorerController - Nutzdaten Explorer\n *\n * Zeigt Dokumente und Chunks aus Nextcloud (documents, chunks Tabellen).\n * Für Endnutzer - Coaching-Materialien, PDFs, später Mails.\n *\/\nclass SemanticExplorerController extends Controller\n{\n private SemanticExplorerRepository $repository;\n private VectorSearchService $vectorSearchService;\n\n public function __construct()\n {\n $this->repository = new SemanticExplorerRepository();\n $this->vectorSearchService = new VectorSearchService();\n }\n\n \/**\n * GET \/semantic-explorer\n * Dashboard mit Statistiken\n *\/\n public function index(): void\n {\n $docStats = $this->repository->getDocumentStats();\n $chunkStats = $this->repository->getChunkStats();\n $documents = $this->repository->getDocuments();\n $recentChunks = $this->repository->getRecentChunks(5);\n\n $this->view('semantic-explorer.index', [\n 'title' => 'Semantic Explorer',\n 'docStats' => $docStats,\n 'chunkStats' => $chunkStats,\n 'documents' => $documents,\n 'recentChunks' => $recentChunks,\n ]);\n }\n\n \/**\n * GET \/semantic-explorer\/dokumente\n * Liste aller Dokumente\n *\/\n public function dokumente(): void\n {\n $status = $_GET['status'] ?? '';\n $search = $_GET['search'] ?? '';\n\n $documents = $this->repository->getDocumentsFiltered($status, $search);\n\n $this->view('semantic-explorer.dokumente.index', [\n 'title' => 'Dokumente',\n 'documents' => $documents,\n 'currentStatus' => $status,\n 'currentSearch' => $search,\n ]);\n }\n\n \/**\n * GET \/semantic-explorer\/dokumente\/{id}\n * Dokument-Details mit Chunks\n *\/\n public function dokumentShow(int $id): void\n {\n $document = $this->repository->getDocument($id);\n\n if ($document === null) {\n $this->notFound('Dokument nicht gefunden');\n }\n\n $chunks = $this->repository->getChunksForDocument($id);\n\n \/\/ Heading-Paths dekodieren\n foreach ($chunks as &$chunk) {\n $chunk['heading_path_decoded'] = json_decode($chunk['heading_path'] ?? '[]', true) ?: [];\n $chunk['metadata_decoded'] = json_decode($chunk['metadata'] ?? '{}', true) ?: [];\n }\n\n $this->view('semantic-explorer.dokumente.show', [\n 'title' => $document['filename'],\n 'document' => $document,\n 'chunks' => $chunks,\n ]);\n }\n\n \/**\n * GET \/semantic-explorer\/chunks\n * Liste aller Chunks\n *\/\n public function chunks(): void\n {\n $search = $_GET['search'] ?? '';\n $embedded = $_GET['embedded'] ?? '';\n $page = max(1, (int) ($_GET['page'] ?? 1));\n $limit = 50;\n $offset = ($page - 1) * $limit;\n\n $totalCount = $this->repository->getChunksCount($search, $embedded);\n $chunks = $this->repository->getChunksFiltered($search, $embedded, $limit, $offset);\n\n $this->view('semantic-explorer.chunks.index', [\n 'title' => 'Chunks',\n 'chunks' => $chunks,\n 'currentSearch' => $search,\n 'currentEmbedded' => $embedded,\n 'currentPage' => $page,\n 'totalCount' => $totalCount,\n 'totalPages' => ceil($totalCount \/ $limit),\n ]);\n }\n\n \/**\n * GET \/semantic-explorer\/chunks\/{id}\n * Chunk-Details\n *\/\n public function chunkShow(int $id): void\n {\n $chunk = $this->repository->getChunk($id);\n\n if ($chunk === null) {\n $this->notFound('Chunk nicht gefunden');\n }\n\n \/\/ JSON-Felder dekodieren\n $chunk['heading_path_decoded'] = json_decode($chunk['heading_path'] ?? '[]', true) ?: [];\n $chunk['metadata_decoded'] = json_decode($chunk['metadata'] ?? '{}', true) ?: [];\n\n \/\/ Nachbar-Chunks\n $prevChunk = $this->repository->getChunkByDocumentAndIndex(\n $chunk['document_id'],\n $chunk['chunk_index'] - 1\n );\n $nextChunk = $this->repository->getChunkByDocumentAndIndex(\n $chunk['document_id'],\n $chunk['chunk_index'] + 1\n );\n\n $this->view('semantic-explorer.chunks.show', [\n 'title' => 'Chunk #' . $chunk['id'],\n 'chunk' => $chunk,\n 'prevChunk' => $prevChunk,\n 'nextChunk' => $nextChunk,\n ]);\n }\n\n \/**\n * GET \/semantic-explorer\/suche\n * Semantische Suche in Nutzdaten\n *\/\n public function suche(): void\n {\n $query = $_GET['q'] ?? '';\n $limit = min(20, max(1, (int) ($_GET['limit'] ?? 10)));\n\n $results = [];\n\n if ($query !== '') {\n \/\/ Vektor-Suche via Qdrant\n $results = $this->vectorSearch($query, $limit);\n }\n\n $this->view('semantic-explorer.suche', [\n 'title' => 'Semantische Suche',\n 'query' => $query,\n 'results' => $results,\n 'limit' => $limit,\n ]);\n }\n\n \/**\n * Vektor-Suche in documents Collection\n *\/\n private function vectorSearch(string $query, int $limit): array\n {\n \/\/ Embedding generieren\n $embedding = $this->getEmbedding($query);\n if (empty($embedding)) {\n return [];\n }\n\n \/\/ Qdrant suchen\n $response = $this->qdrantSearch($embedding, $limit);\n if (empty($response)) {\n return [];\n }\n\n \/\/ Chunk-Details aus DB laden\n $results = [];\n foreach ($response as $point) {\n $chunkId = $point['payload']['chunk_id'] ?? null;\n if ($chunkId === null) {\n continue;\n }\n\n $chunk = $this->repository->getChunkById($chunkId);\n\n if ($chunk !== null) {\n $chunk['score'] = $point['score'];\n $chunk['heading_path_decoded'] = json_decode($chunk['heading_path'] ?? '[]', true) ?: [];\n $results[] = $chunk;\n }\n }\n\n return $results;\n }\n\n \/**\n * Embedding via Ollama\n *\/\n private function getEmbedding(string $text): array\n {\n $ch = curl_init('http:\/\/localhost:11434\/api\/embeddings');\n curl_setopt_array($ch, [\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_POST => true,\n CURLOPT_HTTPHEADER => ['Content-Type: application\/json'],\n CURLOPT_POSTFIELDS => json_encode([\n 'model' => 'mxbai-embed-large',\n 'prompt' => $text,\n ]),\n ]);\n\n $response = curl_exec($ch);\n curl_close($ch);\n\n $data = json_decode($response, true);\n\n return $data['embedding'] ?? [];\n }\n\n \/**\n * Qdrant-Suche\n *\/\n private function qdrantSearch(array $embedding, int $limit): array\n {\n $ch = curl_init('http:\/\/localhost:6333\/collections\/documents\/points\/search');\n curl_setopt_array($ch, [\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_POST => true,\n CURLOPT_HTTPHEADER => ['Content-Type: application\/json'],\n CURLOPT_POSTFIELDS => json_encode([\n 'vector' => $embedding,\n 'limit' => $limit,\n 'with_payload' => true,\n ]),\n ]);\n\n $response = curl_exec($ch);\n curl_close($ch);\n\n $data = json_decode($response, true);\n\n return $data['result'] ?? [];\n }\n\n \/**\n * GET \/semantic-explorer\/semantik\n * Semantische Analyse pro Chunk\n *\/\n public function semantik(): void\n {\n $sentiment = $_GET['sentiment'] ?? '';\n $page = max(1, (int) ($_GET['page'] ?? 1));\n $limit = 50;\n $offset = ($page - 1) * $limit;\n\n $totalCount = $this->repository->getSemanticsCount($sentiment);\n $semantics = $this->repository->getSemanticsFiltered($sentiment, $limit, $offset);\n\n \/\/ JSON dekodieren\n foreach ($semantics as &$s) {\n $s['keywords_decoded'] = json_decode($s['keywords'] ?? '[]', true) ?: [];\n $s['topics_decoded'] = json_decode($s['topics'] ?? '[]', true) ?: [];\n }\n\n $stats = $this->repository->getSemanticStats();\n\n $this->view('semantic-explorer.semantik', [\n 'title' => 'Semantik',\n 'semantics' => $semantics,\n 'stats' => $stats,\n 'currentSentiment' => $sentiment,\n 'currentPage' => $page,\n 'totalCount' => $totalCount,\n 'totalPages' => ceil($totalCount \/ $limit),\n ]);\n }\n}\n",
"structuredPatch": [
{
"oldStart": 174,
"oldLines": 18,
"newStart": 174,
"newLines": 13,
"lines": [
" }",
" ",
" \/**",
"- * Vektor-Suche in documents Collection",
"+ * Vektor-Suche in documents Collection using VectorSearchService",
" *\/",
" private function vectorSearch(string $query, int $limit): array",
" {",
"- \/\/ Embedding generieren",
"- $embedding = $this->getEmbedding($query);",
"- if (empty($embedding)) {",
"- return [];",
"- }",
"+ \/\/ Search via service",
"+ $response = $this->vectorSearchService->search($query, 'documents', $limit);",
" ",
"- \/\/ Qdrant suchen",
"- $response = $this->qdrantSearch($embedding, $limit);",
" if (empty($response)) {",
" return [];",
" }"
]
},
{
"oldStart": 211,
"oldLines": 55,
"newStart": 206,
"newLines": 6,
"lines": [
" }",
" ",
" \/**",
"- * 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\/semantik",
" * Semantische Analyse pro Chunk",
" *\/"
]
}
],
"userModified": false,
"replaceAll": false
}
}