chatService = $chatService; $this->ollamaService = $ollamaService; $this->qdrantService = $qdrantService; $this->dokumentRepository = $dokumentRepository; $this->seiteRepository = $seiteRepository; $this->chunkRepository = $chunkRepository; } /** * POST /api/v1/chat * Handle chat message */ public function send(): void { $input = $this->getJsonInput(); $question = trim($input['message'] ?? ''); if ($question === '') { $this->json(['error' => 'Keine Frage angegeben'], 400); return; } // Release session lock before long-running LLM call session_write_close(); try { $result = $this->askChat($question); $this->json($result); } catch (\Exception $e) { error_log(sprintf('[API.Chat.send] %s: %s', get_class($e), $e->getMessage())); $this->json(['error' => $e->getMessage()], 500); } } /** * GET /api/v1/chat/search * Search for relevant chunks */ public function search(): void { $query = $this->getString('q'); $limit = $this->getInt('limit', 5); if ($query === '') { $this->json(['error' => 'Keine Suchanfrage'], 400); return; } try { $results = $this->searchChunks($query, $limit); $this->json(['results' => $results]); } catch (\Exception $e) { error_log(sprintf('[API.Chat.search] %s: %s', get_class($e), $e->getMessage())); $this->json(['error' => $e->getMessage()], 500); } } /** * GET /api/v1/chat/stats * Get chat/document statistics (Doc2Vector Pipeline) */ public function stats(): void { try { $chunkStats = $this->chunkRepository->getChunkStats(); $stats = [ 'dokumente' => $this->dokumentRepository->countDokumente(), 'seiten' => $this->seiteRepository->countSeiten(), 'chunks' => $chunkStats['total'], 'tokens' => $chunkStats['tokens'], 'analyzed' => $chunkStats['analyzed'], 'synced' => $chunkStats['synced'], ]; $this->json($stats); } catch (\Exception $e) { error_log(sprintf('[API.Chat.stats] %s: %s', get_class($e), $e->getMessage())); $this->json(['error' => $e->getMessage()], 500); } } /** * Ask chat question using ChatService * * @param string $question User's question * * @return array Chat response with answer and sources * * @throws \RuntimeException If chat service fails */ private function askChat(string $question): array { try { // Use dokumentation_chunks collection for RAG return $this->chatService->chat($question, 'claude-opus-4-5-20251101', ['dokumentation_chunks'], 5); } catch (\RuntimeException $e) { throw new \RuntimeException('Chat-Service konnte nicht ausgeführt werden: ' . $e->getMessage(), 0, $e); } } /** * Search for similar chunks using vector search * * @param string $query Search query * @param int $limit Maximum number of results * * @return array}> Search results * * @throws \RuntimeException If search fails */ private function searchChunks(string $query, int $limit): array { try { $queryEmbedding = $this->ollamaService->getEmbedding($query); // Use dokumentation_chunks collection return $this->qdrantService->search($queryEmbedding, 'dokumentation_chunks', $limit); } catch (\RuntimeException $e) { throw new \RuntimeException('Suche konnte nicht ausgeführt werden: ' . $e->getMessage(), 0, $e); } } }