diff --git a/config/services.yaml b/config/services.yaml index dc45f61..3275c52 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -172,3 +172,9 @@ services: arguments: $ndJsonPath: '%mto.knowledge.ndjson%' $indexMetaPath: '%mto.knowledge.index_meta%' + + App\Vector\VectorIndexHealthService: + arguments: + $indexNdjsonPath: '%kernel.project_dir%/var/knowledge/index.ndjson' + $vectorIndexPath: '%kernel.project_dir%/var/knowledge/vector.index' + $vectorMetaPath: '%kernel.project_dir%/var/knowledge/vector.index.meta.json' \ No newline at end of file diff --git a/src/Command/VectorHealthCheckCommand.php b/src/Command/VectorHealthCheckCommand.php new file mode 100644 index 0000000..2774651 --- /dev/null +++ b/src/Command/VectorHealthCheckCommand.php @@ -0,0 +1,35 @@ +health->check(); + + $output->writeln(json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + + return str_starts_with($result['status'], 'OK') + ? Command::SUCCESS + : Command::FAILURE; + } +} diff --git a/src/Controller/Admin/DashboardController.php b/src/Controller/Admin/DashboardController.php index b5a6a16..5ce6a71 100644 --- a/src/Controller/Admin/DashboardController.php +++ b/src/Controller/Admin/DashboardController.php @@ -5,6 +5,7 @@ namespace App\Controller\Admin; use App\Index\IndexMetaManager; use App\Ingest\IngestFlow; +use App\Vector\VectorIndexHealthService; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; @@ -12,13 +13,15 @@ use Symfony\Component\Routing\Attribute\Route; final class DashboardController extends AbstractController { #[Route('/admin', name: 'admin_dashboard')] - public function index(): Response + public function index(VectorIndexHealthService $health): Response { - return $this->render('admin/dashboard/index.html.twig'); + return $this->render('admin/dashboard/index.html.twig', [ + 'vectorHealth' => $health->check() + ]); } #[Route('/admin/dashboard', name: 'admin_dashboard')] - public function dashboard(IndexMetaManager $metaManager): Response + public function dashboard(IndexMetaManager $metaManager,VectorIndexHealthService $health): Response { $chunkCount = $metaManager->getRuntimeChunkCount(); $limit = IngestFlow::CHUNK_LIMIT_HARD; @@ -26,6 +29,7 @@ final class DashboardController extends AbstractController return $this->render('admin/dashboard/index.html.twig', [ 'chunkCount' => $chunkCount, 'chunkLimit' => $limit, + 'vectorHealth' => $health->check(), ]); } } diff --git a/src/Service/Admin/IndexNdjsonInspector.php b/src/Service/Admin/IndexNdjsonInspector.php index d134783..a2154ab 100644 --- a/src/Service/Admin/IndexNdjsonInspector.php +++ b/src/Service/Admin/IndexNdjsonInspector.php @@ -30,7 +30,7 @@ final class IndexNdjsonInspector { if (!is_file($this->metaPath)) { return [ - 'error' => 'index_meta.json nicht gefunden', + 'error' => 'index_meta.json nicht gefunden. Bitte laden Sie min. ein Dokument in das System.', 'path' => $this->metaPath, ]; } @@ -67,7 +67,7 @@ final class IndexNdjsonInspector if (!is_file($this->ndjsonPath)) { return [ - 'error' => 'index.ndjson nicht gefunden', + 'error' => 'index.ndjson nicht gefunden. Bitte laden Sie min. ein Dokument in das System.', 'path' => $this->ndjsonPath, 'items' => [], ]; diff --git a/src/Vector/VectorIndexHealthService.php b/src/Vector/VectorIndexHealthService.php new file mode 100644 index 0000000..b4b23f5 --- /dev/null +++ b/src/Vector/VectorIndexHealthService.php @@ -0,0 +1,93 @@ +indexNdjsonPath); + $vectorExists = is_file($this->vectorIndexPath); + $metaExists = is_file($this->vectorMetaPath); + + $ndjsonChunkCount = 0; + + if ($ndjsonExists) { + $h = @fopen($this->indexNdjsonPath, 'r'); + if ($h !== false) { + while (($line = fgets($h)) !== false) { + $line = trim($line); + if ($line === '') { + continue; + } + $data = json_decode($line, true); + if (is_array($data) && !empty($data['chunk_id']) && !empty($data['text'])) { + $ndjsonChunkCount++; + } + } + fclose($h); + } + } + + $vectorChunkCount = 0; + if ($metaExists) { + $meta = json_decode((string) file_get_contents($this->vectorMetaPath), true); + if (is_array($meta)) { + $vectorChunkCount = count($meta); + } + } + + $status = $this->determineStatus( + $ndjsonChunkCount, + $vectorExists, + $metaExists, + $vectorChunkCount + ); + + return [ + 'ndjson_exists' => $ndjsonExists, + 'ndjson_chunk_count' => $ndjsonChunkCount, + 'vector_exists' => $vectorExists, + 'meta_exists' => $metaExists, + 'vector_chunk_count' => $vectorChunkCount, + 'status' => $status, + ]; + } + + private function determineStatus( + int $ndjsonChunkCount, + bool $vectorExists, + bool $metaExists, + int $vectorChunkCount + ): string { + if ($ndjsonChunkCount === 0 && !$vectorExists && !$metaExists) { + return 'OK_EMPTY'; + } + + if ($ndjsonChunkCount > 0 && $vectorExists && $metaExists && $vectorChunkCount === $ndjsonChunkCount) { + return 'OK'; + } + + if ($ndjsonChunkCount === 0 && ($vectorExists || $metaExists)) { + return 'INCONSISTENT_STALE_VECTOR'; + } + + if ($ndjsonChunkCount > 0 && (!$vectorExists || !$metaExists)) { + return 'INCONSISTENT_MISSING_VECTOR'; + } + + if ($ndjsonChunkCount !== $vectorChunkCount) { + return 'INCONSISTENT_COUNT_MISMATCH'; + } + + return 'UNKNOWN'; + } +} diff --git a/templates/admin/dashboard/index.html.twig b/templates/admin/dashboard/index.html.twig index 9ae67a7..9b492f1 100644 --- a/templates/admin/dashboard/index.html.twig +++ b/templates/admin/dashboard/index.html.twig @@ -1,130 +1,207 @@ {% extends 'admin/base.html.twig' %} -{% block title %}Admin Dashboard{% endblock %} +{% block title %}System Dashboard{% endblock %} {% block body %} -