1fdbf4cdbSCostin Stroie<?php 2fdbf4cdbSCostin Stroie 3fdbf4cdbSCostin Stroieuse dokuwiki\Extension\CLIPlugin; 4fdbf4cdbSCostin Stroieuse splitbrain\phpcli\Options; 5fdbf4cdbSCostin Stroie 6fdbf4cdbSCostin Stroieif(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__) . '/../../../') . '/'); 7fdbf4cdbSCostin Stroie 8fdbf4cdbSCostin Stroie/** 9fdbf4cdbSCostin Stroie * DokuWiki CLI plugin for ChromaDB operations 10fdbf4cdbSCostin Stroie */ 114de98450SCostin Stroieclass cli_plugin_dokullm extends CLIPlugin { 12fdbf4cdbSCostin Stroie 13fdbf4cdbSCostin Stroie /** 14fdbf4cdbSCostin Stroie * Register options and arguments 15fdbf4cdbSCostin Stroie * 16fdbf4cdbSCostin Stroie * @param Options $options 17fdbf4cdbSCostin Stroie */ 18fdbf4cdbSCostin Stroie protected function setup(Options $options) { 19fdbf4cdbSCostin Stroie // Set help text 20fdbf4cdbSCostin Stroie $options->setHelp( 214de98450SCostin Stroie "ChromaDB CLI plugin for DokuLLM\n\n" . 224de98450SCostin Stroie "Usage: ./bin/plugin.php dokullm [action] [options]\n\n" . 23fdbf4cdbSCostin Stroie "Actions:\n" . 24fdbf4cdbSCostin Stroie " send Send a file or directory to ChromaDB\n" . 25fdbf4cdbSCostin Stroie " query Query ChromaDB\n" . 26fdbf4cdbSCostin Stroie " heartbeat Check if ChromaDB server is alive\n" . 27fdbf4cdbSCostin Stroie " identity Get authentication and identity information\n" . 28fdbf4cdbSCostin Stroie " list List all collections\n" . 29fdbf4cdbSCostin Stroie " get Get a document by its ID\n" 30fdbf4cdbSCostin Stroie ); 31fdbf4cdbSCostin Stroie 32fdbf4cdbSCostin Stroie // Global options 33fdbf4cdbSCostin Stroie $options->registerOption('verbose', 'Enable verbose output', 'v'); 34fdbf4cdbSCostin Stroie 35fdbf4cdbSCostin Stroie // Action-specific options 36fdbf4cdbSCostin Stroie $options->registerCommand('send', 'Send a file or directory to ChromaDB'); 37fdbf4cdbSCostin Stroie $options->registerArgument('path', 'File or directory path', true, 'send'); 38fdbf4cdbSCostin Stroie 39fdbf4cdbSCostin Stroie $options->registerCommand('query', 'Query ChromaDB'); 40fdbf4cdbSCostin Stroie $options->registerOption('collection', 'Collection name to query', 'c', 'collection', 'documents', 'query'); 41fdbf4cdbSCostin Stroie $options->registerOption('limit', 'Number of results to return', 'l', 'limit', '5', 'query'); 42fdbf4cdbSCostin Stroie $options->registerArgument('search', 'Search terms', true, 'query'); 43fdbf4cdbSCostin Stroie 44fdbf4cdbSCostin Stroie $options->registerCommand('heartbeat', 'Check if ChromaDB server is alive'); 45fdbf4cdbSCostin Stroie 46fdbf4cdbSCostin Stroie $options->registerCommand('identity', 'Get authentication and identity information'); 47fdbf4cdbSCostin Stroie 48fdbf4cdbSCostin Stroie $options->registerCommand('list', 'List all collections'); 49fdbf4cdbSCostin Stroie 50fdbf4cdbSCostin Stroie $options->registerCommand('get', 'Get a document by its ID'); 51fdbf4cdbSCostin Stroie $options->registerOption('collection', 'Collection name', 'c', 'collection', 'documents', 'get'); 52fdbf4cdbSCostin Stroie $options->registerArgument('id', 'Document ID', true, 'get'); 53fdbf4cdbSCostin Stroie } 54fdbf4cdbSCostin Stroie 55fdbf4cdbSCostin Stroie /** 56fdbf4cdbSCostin Stroie * Main plugin logic 57fdbf4cdbSCostin Stroie * 58fdbf4cdbSCostin Stroie * @param Options $options 59fdbf4cdbSCostin Stroie */ 60fdbf4cdbSCostin Stroie protected function main(Options $options) { 61fdbf4cdbSCostin Stroie // Include the ChromaDBClient class 62fdbf4cdbSCostin Stroie require_once dirname(__FILE__) . '/ChromaDBClient.php'; 63fdbf4cdbSCostin Stroie 64*605489dcSCostin Stroie (aider) // Get values from DokuWiki settings 65*605489dcSCostin Stroie (aider) $host = $this->getConf('chroma_host'); 66*605489dcSCostin Stroie (aider) $port = (int)$this->getConf('chroma_port'); 67*605489dcSCostin Stroie (aider) $tenant = $this->getConf('chroma_tenant'); 68*605489dcSCostin Stroie (aider) $database = $this->getConf('chroma_database'); 69*605489dcSCostin Stroie (aider) $ollamaHost = $this->getConf('ollama_host'); 70*605489dcSCostin Stroie (aider) $ollamaPort = (int)$this->getConf('ollama_port'); 71*605489dcSCostin Stroie (aider) $ollamaModel = $this->getConf('ollama_model'); 72a15292f0SCostin Stroie (aider) $verbose = $options->getOpt('verbose'); 73a15292f0SCostin Stroie (aider) 74a15292f0SCostin Stroie (aider) $action = $options->getCmd(); 75fdbf4cdbSCostin Stroie 76fdbf4cdbSCostin Stroie switch ($action) { 77fdbf4cdbSCostin Stroie case 'send': 78fdbf4cdbSCostin Stroie $path = $options->getArgs()[0] ?? null; 79fdbf4cdbSCostin Stroie if (!$path) { 80fdbf4cdbSCostin Stroie $this->fatal('Missing file path for send action'); 81fdbf4cdbSCostin Stroie } 82fdbf4cdbSCostin Stroie $this->sendFile($path, $host, $port, $tenant, $database, $ollamaHost, $ollamaPort, $ollamaModel, $verbose); 83fdbf4cdbSCostin Stroie break; 84fdbf4cdbSCostin Stroie 85fdbf4cdbSCostin Stroie case 'query': 86fdbf4cdbSCostin Stroie $searchTerms = $options->getArgs()[0] ?? null; 87fdbf4cdbSCostin Stroie if (!$searchTerms) { 88fdbf4cdbSCostin Stroie $this->fatal('Missing search terms for query action'); 89fdbf4cdbSCostin Stroie } 90fdbf4cdbSCostin Stroie $collection = $options->getOpt('collection', 'documents'); 91fdbf4cdbSCostin Stroie $limit = (int)$options->getOpt('limit', 5); 92fdbf4cdbSCostin Stroie $this->queryChroma($searchTerms, $limit, $host, $port, $tenant, $database, $collection, $ollamaHost, $ollamaPort, $ollamaModel, $verbose); 93fdbf4cdbSCostin Stroie break; 94fdbf4cdbSCostin Stroie 95fdbf4cdbSCostin Stroie case 'heartbeat': 96fdbf4cdbSCostin Stroie $this->checkHeartbeat($host, $port, $tenant, $database, $ollamaHost, $ollamaPort, $ollamaModel, $verbose); 97fdbf4cdbSCostin Stroie break; 98fdbf4cdbSCostin Stroie 99fdbf4cdbSCostin Stroie case 'identity': 100fdbf4cdbSCostin Stroie $this->checkIdentity($host, $port, $tenant, $database, $ollamaHost, $ollamaPort, $ollamaModel, $verbose); 101fdbf4cdbSCostin Stroie break; 102fdbf4cdbSCostin Stroie 103fdbf4cdbSCostin Stroie case 'list': 104fdbf4cdbSCostin Stroie $this->listCollections($host, $port, $tenant, $database, $ollamaHost, $ollamaPort, $ollamaModel, $verbose); 105fdbf4cdbSCostin Stroie break; 106fdbf4cdbSCostin Stroie 107fdbf4cdbSCostin Stroie case 'get': 108fdbf4cdbSCostin Stroie $documentId = $options->getArgs()[0] ?? null; 109fdbf4cdbSCostin Stroie if (!$documentId) { 110fdbf4cdbSCostin Stroie $this->fatal('Missing document ID for get action'); 111fdbf4cdbSCostin Stroie } 112fdbf4cdbSCostin Stroie $collection = $options->getOpt('collection', null); 113fdbf4cdbSCostin Stroie $this->getDocument($documentId, $host, $port, $tenant, $database, $collection, $ollamaHost, $ollamaPort, $ollamaModel, $verbose); 114fdbf4cdbSCostin Stroie break; 115fdbf4cdbSCostin Stroie 116fdbf4cdbSCostin Stroie default: 117fdbf4cdbSCostin Stroie echo $options->help(); 118fdbf4cdbSCostin Stroie exit(1); 119fdbf4cdbSCostin Stroie } 120fdbf4cdbSCostin Stroie } 121fdbf4cdbSCostin Stroie 122fdbf4cdbSCostin Stroie /** 123fdbf4cdbSCostin Stroie * Send a file or directory of files to ChromaDB 124fdbf4cdbSCostin Stroie */ 125fdbf4cdbSCostin Stroie private function sendFile($path, $host, $port, $tenant, $database, $ollamaHost, $ollamaPort, $ollamaModel, $verbose = false) { 126fdbf4cdbSCostin Stroie // Create ChromaDB client 127fdbf4cdbSCostin Stroie $chroma = new \dokuwiki\plugin\dokullm\ChromaDBClient($host, $port, $tenant, $database, 'documents', $ollamaHost, $ollamaPort, $ollamaModel); 128fdbf4cdbSCostin Stroie 129fdbf4cdbSCostin Stroie if (is_dir($path)) { 130fdbf4cdbSCostin Stroie // Process directory 131fdbf4cdbSCostin Stroie $this->processDirectory($path, $chroma, $host, $port, $tenant, $database, $verbose); 132fdbf4cdbSCostin Stroie } else { 133fdbf4cdbSCostin Stroie // Process single file 134fdbf4cdbSCostin Stroie if (!file_exists($path)) { 135fdbf4cdbSCostin Stroie $this->error("File does not exist: $path"); 136fdbf4cdbSCostin Stroie return; 137fdbf4cdbSCostin Stroie } 138fdbf4cdbSCostin Stroie 139fdbf4cdbSCostin Stroie // Skip files that start with underscore 140fdbf4cdbSCostin Stroie $filename = basename($path); 141fdbf4cdbSCostin Stroie if ($filename[0] === '_') { 142fdbf4cdbSCostin Stroie if ($verbose) { 143fdbf4cdbSCostin Stroie $this->info("Skipping file (starts with underscore): $path"); 144fdbf4cdbSCostin Stroie } 145fdbf4cdbSCostin Stroie return; 146fdbf4cdbSCostin Stroie } 147fdbf4cdbSCostin Stroie 148fdbf4cdbSCostin Stroie $this->processSingleFile($path, $chroma, $host, $port, $tenant, $database, false, $verbose); 149fdbf4cdbSCostin Stroie } 150fdbf4cdbSCostin Stroie } 151fdbf4cdbSCostin Stroie 152fdbf4cdbSCostin Stroie /** 153fdbf4cdbSCostin Stroie * Process a single DokuWiki file and send it to ChromaDB 154fdbf4cdbSCostin Stroie */ 155fdbf4cdbSCostin Stroie private function processSingleFile($filePath, $chroma, $host, $port, $tenant, $database, $collectionChecked = false, $verbose = false) { 156fdbf4cdbSCostin Stroie // Parse file path to extract metadata 157fdbf4cdbSCostin Stroie $id = \dokuwiki\plugin\dokullm\parseFilePath($filePath); 158fdbf4cdbSCostin Stroie 159fdbf4cdbSCostin Stroie // Use the first part of the document ID as collection name, fallback to 'documents' 160fdbf4cdbSCostin Stroie $idParts = explode(':', $id); 161fdbf4cdbSCostin Stroie $collectionName = isset($idParts[0]) && !empty($idParts[0]) ? $idParts[0] : 'documents'; 162fdbf4cdbSCostin Stroie 163fdbf4cdbSCostin Stroie // Clean the ID and check ACL 164fdbf4cdbSCostin Stroie $cleanId = cleanID($id); 165fdbf4cdbSCostin Stroie if (auth_quickaclcheck($cleanId) < AUTH_READ) { 166fdbf4cdbSCostin Stroie $this->error("You are not allowed to read this file: $id"); 167fdbf4cdbSCostin Stroie return; 168fdbf4cdbSCostin Stroie } 169fdbf4cdbSCostin Stroie 170fdbf4cdbSCostin Stroie try { 171fdbf4cdbSCostin Stroie // Process the file using the class method 172fdbf4cdbSCostin Stroie $result = $chroma->processSingleFile($filePath, $collectionName, $collectionChecked); 173fdbf4cdbSCostin Stroie 174fdbf4cdbSCostin Stroie // Handle the result with verbose output 175fdbf4cdbSCostin Stroie if ($verbose && !empty($result['collection_status'])) { 176fdbf4cdbSCostin Stroie $this->info($result['collection_status']); 177fdbf4cdbSCostin Stroie } 178fdbf4cdbSCostin Stroie 179fdbf4cdbSCostin Stroie switch ($result['status']) { 180fdbf4cdbSCostin Stroie case 'success': 181fdbf4cdbSCostin Stroie if ($verbose) { 182fdbf4cdbSCostin Stroie $this->info("Adding " . $result['details']['chunks'] . " chunks to ChromaDB..."); 183fdbf4cdbSCostin Stroie } 184fdbf4cdbSCostin Stroie $this->success("Successfully sent file to ChromaDB:"); 185fdbf4cdbSCostin Stroie $this->info(" Document ID: " . $result['details']['document_id']); 186fdbf4cdbSCostin Stroie if ($verbose) { 187fdbf4cdbSCostin Stroie $this->info(" Chunks: " . $result['details']['chunks']); 188fdbf4cdbSCostin Stroie $this->info(" Host: $host:$port"); 189fdbf4cdbSCostin Stroie $this->info(" Tenant: $tenant"); 190fdbf4cdbSCostin Stroie $this->info(" Database: $database"); 191fdbf4cdbSCostin Stroie $this->info(" Collection: " . $result['details']['collection']); 192fdbf4cdbSCostin Stroie } 193fdbf4cdbSCostin Stroie break; 194fdbf4cdbSCostin Stroie 195fdbf4cdbSCostin Stroie case 'skipped': 196fdbf4cdbSCostin Stroie if ($verbose) { 197fdbf4cdbSCostin Stroie $this->info($result['message']); 198fdbf4cdbSCostin Stroie } 199fdbf4cdbSCostin Stroie break; 200fdbf4cdbSCostin Stroie 201fdbf4cdbSCostin Stroie case 'error': 202fdbf4cdbSCostin Stroie $this->error($result['message']); 203fdbf4cdbSCostin Stroie break; 204fdbf4cdbSCostin Stroie } 205fdbf4cdbSCostin Stroie } catch (Exception $e) { 206fdbf4cdbSCostin Stroie $this->error("Error sending file to ChromaDB: " . $e->getMessage()); 207fdbf4cdbSCostin Stroie return; 208fdbf4cdbSCostin Stroie } 209fdbf4cdbSCostin Stroie } 210fdbf4cdbSCostin Stroie 211fdbf4cdbSCostin Stroie /** 212fdbf4cdbSCostin Stroie * Process all DokuWiki files in a directory and send them to ChromaDB 213fdbf4cdbSCostin Stroie */ 214fdbf4cdbSCostin Stroie private function processDirectory($dirPath, $chroma, $host, $port, $tenant, $database, $verbose = false) { 215fdbf4cdbSCostin Stroie if ($verbose) { 216fdbf4cdbSCostin Stroie $this->info("Processing directory: $dirPath"); 217fdbf4cdbSCostin Stroie } 218fdbf4cdbSCostin Stroie 219fdbf4cdbSCostin Stroie // Check if directory exists 220fdbf4cdbSCostin Stroie if (!is_dir($dirPath)) { 221fdbf4cdbSCostin Stroie $this->error("Directory does not exist: $dirPath"); 222fdbf4cdbSCostin Stroie return; 223fdbf4cdbSCostin Stroie } 224fdbf4cdbSCostin Stroie 225fdbf4cdbSCostin Stroie // Create RecursiveIteratorIterator to process directories recursively 226fdbf4cdbSCostin Stroie $iterator = new RecursiveIteratorIterator( 227fdbf4cdbSCostin Stroie new RecursiveDirectoryIterator($dirPath, RecursiveDirectoryIterator::SKIP_DOTS), 228fdbf4cdbSCostin Stroie RecursiveIteratorIterator::LEAVES_ONLY 229fdbf4cdbSCostin Stroie ); 230fdbf4cdbSCostin Stroie 231fdbf4cdbSCostin Stroie $files = []; 232fdbf4cdbSCostin Stroie foreach ($iterator as $file) { 233fdbf4cdbSCostin Stroie // Process only .txt files that don't start with underscore 234fdbf4cdbSCostin Stroie if ($file->isFile() && $file->getExtension() === 'txt' && $file->getFilename()[0] !== '_') { 235fdbf4cdbSCostin Stroie $files[] = $file->getPathname(); 236fdbf4cdbSCostin Stroie } 237fdbf4cdbSCostin Stroie } 238fdbf4cdbSCostin Stroie 239fdbf4cdbSCostin Stroie // Skip if no files 240fdbf4cdbSCostin Stroie if (empty($files)) { 241fdbf4cdbSCostin Stroie if ($verbose) { 242fdbf4cdbSCostin Stroie $this->info("No .txt files found in directory: $dirPath"); 243fdbf4cdbSCostin Stroie } 244fdbf4cdbSCostin Stroie return; 245fdbf4cdbSCostin Stroie } 246fdbf4cdbSCostin Stroie 247fdbf4cdbSCostin Stroie if ($verbose) { 248fdbf4cdbSCostin Stroie $this->info("Found " . count($files) . " files to process."); 249fdbf4cdbSCostin Stroie } 250fdbf4cdbSCostin Stroie 251fdbf4cdbSCostin Stroie // Use the first part of the document ID as collection name, fallback to 'documents' 252fdbf4cdbSCostin Stroie $sampleFile = $files[0]; 253fdbf4cdbSCostin Stroie $id = \dokuwiki\plugin\dokullm\parseFilePath($sampleFile); 254fdbf4cdbSCostin Stroie $idParts = explode(':', $id); 255fdbf4cdbSCostin Stroie $collectionName = isset($idParts[0]) && !empty($idParts[0]) ? $idParts[0] : 'documents'; 256fdbf4cdbSCostin Stroie 257fdbf4cdbSCostin Stroie try { 258fdbf4cdbSCostin Stroie $collectionStatus = $chroma->ensureCollectionExists($collectionName); 259fdbf4cdbSCostin Stroie if ($verbose) { 260fdbf4cdbSCostin Stroie $this->info($collectionStatus); 261fdbf4cdbSCostin Stroie } 262fdbf4cdbSCostin Stroie $collectionChecked = true; 263fdbf4cdbSCostin Stroie } catch (Exception $e) { 264fdbf4cdbSCostin Stroie $collectionChecked = true; 265fdbf4cdbSCostin Stroie } 266fdbf4cdbSCostin Stroie 267fdbf4cdbSCostin Stroie // Process each file 268fdbf4cdbSCostin Stroie $processedCount = 0; 269fdbf4cdbSCostin Stroie $skippedCount = 0; 270fdbf4cdbSCostin Stroie $errorCount = 0; 271fdbf4cdbSCostin Stroie 272fdbf4cdbSCostin Stroie foreach ($files as $file) { 273fdbf4cdbSCostin Stroie if ($verbose) { 274fdbf4cdbSCostin Stroie $this->info("\nProcessing file: $file"); 275fdbf4cdbSCostin Stroie } 276fdbf4cdbSCostin Stroie 277fdbf4cdbSCostin Stroie try { 278fdbf4cdbSCostin Stroie $result = $chroma->processSingleFile($file, $collectionName, $collectionChecked); 279fdbf4cdbSCostin Stroie 280fdbf4cdbSCostin Stroie // Handle the result with verbose output 281fdbf4cdbSCostin Stroie if ($verbose && !empty($result['collection_status'])) { 282fdbf4cdbSCostin Stroie $this->info($result['collection_status']); 283fdbf4cdbSCostin Stroie } 284fdbf4cdbSCostin Stroie 285fdbf4cdbSCostin Stroie switch ($result['status']) { 286fdbf4cdbSCostin Stroie case 'success': 287fdbf4cdbSCostin Stroie $processedCount++; 288fdbf4cdbSCostin Stroie if ($verbose) { 289fdbf4cdbSCostin Stroie $this->info("Adding " . $result['details']['chunks'] . " chunks to ChromaDB..."); 290fdbf4cdbSCostin Stroie } 291fdbf4cdbSCostin Stroie $this->success("Successfully sent file to ChromaDB:"); 292fdbf4cdbSCostin Stroie $this->info(" Document ID: " . $result['details']['document_id']); 293fdbf4cdbSCostin Stroie if ($verbose) { 294fdbf4cdbSCostin Stroie $this->info(" Chunks: " . $result['details']['chunks']); 295fdbf4cdbSCostin Stroie $this->info(" Host: $host:$port"); 296fdbf4cdbSCostin Stroie $this->info(" Tenant: $tenant"); 297fdbf4cdbSCostin Stroie $this->info(" Database: $database"); 298fdbf4cdbSCostin Stroie $this->info(" Collection: " . $result['details']['collection']); 299fdbf4cdbSCostin Stroie } 300fdbf4cdbSCostin Stroie break; 301fdbf4cdbSCostin Stroie 302fdbf4cdbSCostin Stroie case 'skipped': 303fdbf4cdbSCostin Stroie $skippedCount++; 304fdbf4cdbSCostin Stroie if ($verbose) { 305fdbf4cdbSCostin Stroie $this->info($result['message']); 306fdbf4cdbSCostin Stroie } 307fdbf4cdbSCostin Stroie break; 308fdbf4cdbSCostin Stroie 309fdbf4cdbSCostin Stroie case 'error': 310fdbf4cdbSCostin Stroie $errorCount++; 311fdbf4cdbSCostin Stroie $this->error($result['message']); 312fdbf4cdbSCostin Stroie break; 313fdbf4cdbSCostin Stroie } 314fdbf4cdbSCostin Stroie } catch (Exception $e) { 315fdbf4cdbSCostin Stroie $errorCount++; 316fdbf4cdbSCostin Stroie $this->error("Error processing file $file: " . $e->getMessage()); 317fdbf4cdbSCostin Stroie } 318fdbf4cdbSCostin Stroie } 319fdbf4cdbSCostin Stroie 320fdbf4cdbSCostin Stroie if ($verbose) { 321fdbf4cdbSCostin Stroie $this->info("\nFinished processing directory."); 322fdbf4cdbSCostin Stroie $this->info("Processing summary:"); 323fdbf4cdbSCostin Stroie $this->info(" Processed: $processedCount files"); 324fdbf4cdbSCostin Stroie $this->info(" Skipped: $skippedCount files"); 325fdbf4cdbSCostin Stroie $this->info(" Errors: $errorCount files"); 326fdbf4cdbSCostin Stroie } else { 327fdbf4cdbSCostin Stroie // Even in non-verbose mode, show summary stats if there were processed files 328fdbf4cdbSCostin Stroie if ($processedCount > 0 || $skippedCount > 0 || $errorCount > 0) { 329fdbf4cdbSCostin Stroie $this->info("Processing summary:"); 330fdbf4cdbSCostin Stroie if ($processedCount > 0) { 331fdbf4cdbSCostin Stroie $this->info(" Processed: $processedCount files"); 332fdbf4cdbSCostin Stroie } 333fdbf4cdbSCostin Stroie if ($skippedCount > 0) { 334fdbf4cdbSCostin Stroie $this->info(" Skipped: $skippedCount files"); 335fdbf4cdbSCostin Stroie } 336fdbf4cdbSCostin Stroie if ($errorCount > 0) { 337fdbf4cdbSCostin Stroie $this->info(" Errors: $errorCount files"); 338fdbf4cdbSCostin Stroie } 339fdbf4cdbSCostin Stroie } 340fdbf4cdbSCostin Stroie } 341fdbf4cdbSCostin Stroie } 342fdbf4cdbSCostin Stroie 343fdbf4cdbSCostin Stroie /** 344fdbf4cdbSCostin Stroie * Query ChromaDB for similar documents 345fdbf4cdbSCostin Stroie */ 346fdbf4cdbSCostin Stroie private function queryChroma($searchTerms, $limit, $host, $port, $tenant, $database, $collection, $ollamaHost, $ollamaPort, $ollamaModel, $verbose = false) { 347fdbf4cdbSCostin Stroie // Create ChromaDB client 348fdbf4cdbSCostin Stroie $chroma = new \dokuwiki\plugin\dokullm\ChromaDBClient($host, $port, $tenant, $database, $collection, $ollamaHost, $ollamaPort, $ollamaModel); 349fdbf4cdbSCostin Stroie 350fdbf4cdbSCostin Stroie try { 351fdbf4cdbSCostin Stroie // Query the specified collection by collection 352fdbf4cdbSCostin Stroie $results = $chroma->queryCollection($collection, [$searchTerms], $limit); 353fdbf4cdbSCostin Stroie 354fdbf4cdbSCostin Stroie $this->info("Query results for: \"$searchTerms\""); 355fdbf4cdbSCostin Stroie $this->info("Host: $host:$port"); 356fdbf4cdbSCostin Stroie $this->info("Tenant: $tenant"); 357fdbf4cdbSCostin Stroie $this->info("Database: $database"); 358fdbf4cdbSCostin Stroie $this->info("Collection: $collection"); 359fdbf4cdbSCostin Stroie $this->info("=========================================="); 360fdbf4cdbSCostin Stroie 361fdbf4cdbSCostin Stroie if (empty($results['ids'][0])) { 362fdbf4cdbSCostin Stroie $this->info("No results found."); 363fdbf4cdbSCostin Stroie return; 364fdbf4cdbSCostin Stroie } 365fdbf4cdbSCostin Stroie 366fdbf4cdbSCostin Stroie for ($i = 0; $i < count($results['ids'][0]); $i++) { 367fdbf4cdbSCostin Stroie $this->info("Result " . ($i + 1) . ":"); 368fdbf4cdbSCostin Stroie $this->info(" ID: " . $results['ids'][0][$i]); 369fdbf4cdbSCostin Stroie $this->info(" Distance: " . $results['distances'][0][$i]); 370fdbf4cdbSCostin Stroie $this->info(" Document: " . substr($results['documents'][0][$i], 0, 255) . "..."); 371fdbf4cdbSCostin Stroie 372fdbf4cdbSCostin Stroie if (isset($results['metadatas'][0][$i])) { 373fdbf4cdbSCostin Stroie $this->info(" Metadata: " . json_encode($results['metadatas'][0][$i])); 374fdbf4cdbSCostin Stroie } 375fdbf4cdbSCostin Stroie $this->info(""); 376fdbf4cdbSCostin Stroie } 377fdbf4cdbSCostin Stroie } catch (Exception $e) { 378fdbf4cdbSCostin Stroie $this->error("Error querying ChromaDB: " . $e->getMessage()); 379fdbf4cdbSCostin Stroie return; 380fdbf4cdbSCostin Stroie } 381fdbf4cdbSCostin Stroie } 382fdbf4cdbSCostin Stroie 383fdbf4cdbSCostin Stroie /** 384fdbf4cdbSCostin Stroie * Check if the ChromaDB server is alive 385fdbf4cdbSCostin Stroie */ 386fdbf4cdbSCostin Stroie private function checkHeartbeat($host, $port, $tenant, $database, $ollamaHost, $ollamaPort, $ollamaModel, $verbose = false) { 387fdbf4cdbSCostin Stroie // Create ChromaDB client 388fdbf4cdbSCostin Stroie $chroma = new \dokuwiki\plugin\dokullm\ChromaDBClient($host, $port, $tenant, $database, 'documents', $ollamaHost, $ollamaPort, $ollamaModel); 389fdbf4cdbSCostin Stroie 390fdbf4cdbSCostin Stroie try { 391fdbf4cdbSCostin Stroie if ($verbose) { 392fdbf4cdbSCostin Stroie $this->info("Checking ChromaDB server status..."); 393fdbf4cdbSCostin Stroie $this->info("Host: $host:$port"); 394fdbf4cdbSCostin Stroie $this->info("Tenant: $tenant"); 395fdbf4cdbSCostin Stroie $this->info("Database: $database"); 396fdbf4cdbSCostin Stroie $this->info("=========================================="); 397fdbf4cdbSCostin Stroie } 398fdbf4cdbSCostin Stroie 399fdbf4cdbSCostin Stroie $result = $chroma->heartbeat(); 400fdbf4cdbSCostin Stroie 401fdbf4cdbSCostin Stroie $this->success("Server is alive!"); 402fdbf4cdbSCostin Stroie $this->info("Response: " . json_encode($result)); 403fdbf4cdbSCostin Stroie } catch (Exception $e) { 404fdbf4cdbSCostin Stroie $this->error("Error checking ChromaDB server status: " . $e->getMessage()); 405fdbf4cdbSCostin Stroie return; 406fdbf4cdbSCostin Stroie } 407fdbf4cdbSCostin Stroie } 408fdbf4cdbSCostin Stroie 409fdbf4cdbSCostin Stroie /** 410fdbf4cdbSCostin Stroie * Get authentication and identity information from ChromaDB 411fdbf4cdbSCostin Stroie */ 412fdbf4cdbSCostin Stroie private function checkIdentity($host, $port, $tenant, $database, $ollamaHost, $ollamaPort, $ollamaModel, $verbose = false) { 413fdbf4cdbSCostin Stroie // Create ChromaDB client 414fdbf4cdbSCostin Stroie $chroma = new \dokuwiki\plugin\dokullm\ChromaDBClient($host, $port, $tenant, $database, 'documents', $ollamaHost, $ollamaPort, $ollamaModel); 415fdbf4cdbSCostin Stroie 416fdbf4cdbSCostin Stroie try { 417fdbf4cdbSCostin Stroie if ($verbose) { 418fdbf4cdbSCostin Stroie $this->info("Checking ChromaDB identity..."); 419fdbf4cdbSCostin Stroie $this->info("Host: $host:$port"); 420fdbf4cdbSCostin Stroie $this->info("Tenant: $tenant"); 421fdbf4cdbSCostin Stroie $this->info("Database: $database"); 422fdbf4cdbSCostin Stroie $this->info("=========================================="); 423fdbf4cdbSCostin Stroie } 424fdbf4cdbSCostin Stroie 425fdbf4cdbSCostin Stroie $result = $chroma->getIdentity(); 426fdbf4cdbSCostin Stroie 427fdbf4cdbSCostin Stroie $this->info("Identity information:"); 428fdbf4cdbSCostin Stroie $this->info("Response: " . json_encode($result, JSON_PRETTY_PRINT)); 429fdbf4cdbSCostin Stroie } catch (Exception $e) { 430fdbf4cdbSCostin Stroie $this->error("Error checking ChromaDB identity: " . $e->getMessage()); 431fdbf4cdbSCostin Stroie return; 432fdbf4cdbSCostin Stroie } 433fdbf4cdbSCostin Stroie } 434fdbf4cdbSCostin Stroie 435fdbf4cdbSCostin Stroie /** 436fdbf4cdbSCostin Stroie * List all collections in the ChromaDB database 437fdbf4cdbSCostin Stroie */ 438fdbf4cdbSCostin Stroie private function listCollections($host, $port, $tenant, $database, $ollamaHost, $ollamaPort, $ollamaModel, $verbose = false) { 439fdbf4cdbSCostin Stroie // Create ChromaDB client 440fdbf4cdbSCostin Stroie $chroma = new \dokuwiki\plugin\dokullm\ChromaDBClient($host, $port, $tenant, $database, 'documents', $ollamaHost, $ollamaPort, $ollamaModel); 441fdbf4cdbSCostin Stroie 442fdbf4cdbSCostin Stroie try { 443fdbf4cdbSCostin Stroie if ($verbose) { 444fdbf4cdbSCostin Stroie $this->info("Listing ChromaDB collections..."); 445fdbf4cdbSCostin Stroie $this->info("Host: $host:$port"); 446fdbf4cdbSCostin Stroie $this->info("Tenant: $tenant"); 447fdbf4cdbSCostin Stroie $this->info("Database: $database"); 448fdbf4cdbSCostin Stroie $this->info("=========================================="); 449fdbf4cdbSCostin Stroie } 450fdbf4cdbSCostin Stroie 451fdbf4cdbSCostin Stroie $result = $chroma->listCollections(); 452fdbf4cdbSCostin Stroie 453fdbf4cdbSCostin Stroie if (empty($result)) { 454fdbf4cdbSCostin Stroie $this->info("No collections found."); 455fdbf4cdbSCostin Stroie return; 456fdbf4cdbSCostin Stroie } 457fdbf4cdbSCostin Stroie 458fdbf4cdbSCostin Stroie $this->info("Collections:"); 459fdbf4cdbSCostin Stroie foreach ($result as $collection) { 460fdbf4cdbSCostin Stroie $this->info(" - " . (isset($collection['name']) ? $collection['name'] : json_encode($collection))); 461fdbf4cdbSCostin Stroie } 462fdbf4cdbSCostin Stroie } catch (Exception $e) { 463fdbf4cdbSCostin Stroie $this->error("Error listing ChromaDB collections: " . $e->getMessage()); 464fdbf4cdbSCostin Stroie return; 465fdbf4cdbSCostin Stroie } 466fdbf4cdbSCostin Stroie } 467fdbf4cdbSCostin Stroie 468fdbf4cdbSCostin Stroie /** 469fdbf4cdbSCostin Stroie * Get a document by its ID from ChromaDB 470fdbf4cdbSCostin Stroie */ 471fdbf4cdbSCostin Stroie private function getDocument($documentId, $host, $port, $tenant, $database, $collection, $ollamaHost, $ollamaPort, $ollamaModel, $verbose = false) { 472fdbf4cdbSCostin Stroie // If no collection specified, derive it from the first part of the document ID 473fdbf4cdbSCostin Stroie if (empty($collection)) { 474fdbf4cdbSCostin Stroie $idParts = explode(':', $documentId); 475fdbf4cdbSCostin Stroie $collection = isset($idParts[0]) && !empty($idParts[0]) ? $idParts[0] : 'documents'; 476fdbf4cdbSCostin Stroie } 477fdbf4cdbSCostin Stroie 478fdbf4cdbSCostin Stroie // Create ChromaDB client 479fdbf4cdbSCostin Stroie $chroma = new \dokuwiki\plugin\dokullm\ChromaDBClient($host, $port, $tenant, $database, $collection, $ollamaHost, $ollamaPort, $ollamaModel); 480fdbf4cdbSCostin Stroie 481fdbf4cdbSCostin Stroie try { 482fdbf4cdbSCostin Stroie // Get the specified document by ID 483fdbf4cdbSCostin Stroie $results = $chroma->getDocument($collection, $documentId); 484fdbf4cdbSCostin Stroie 485fdbf4cdbSCostin Stroie if ($verbose) { 486fdbf4cdbSCostin Stroie $this->info("Document retrieval results for: \"$documentId\""); 487fdbf4cdbSCostin Stroie $this->info("Host: $host:$port"); 488fdbf4cdbSCostin Stroie $this->info("Tenant: $tenant"); 489fdbf4cdbSCostin Stroie $this->info("Database: $database"); 490fdbf4cdbSCostin Stroie $this->info("Collection: $collection"); 491fdbf4cdbSCostin Stroie $this->info("=========================================="); 492fdbf4cdbSCostin Stroie } 493fdbf4cdbSCostin Stroie 494fdbf4cdbSCostin Stroie if (empty($results['ids'])) { 495fdbf4cdbSCostin Stroie $this->info("No document found with ID: $documentId"); 496fdbf4cdbSCostin Stroie return; 497fdbf4cdbSCostin Stroie } 498fdbf4cdbSCostin Stroie 499fdbf4cdbSCostin Stroie for ($i = 0; $i < count($results['ids']); $i++) { 500fdbf4cdbSCostin Stroie $this->info("Document " . ($i + 1) . ":"); 501fdbf4cdbSCostin Stroie $this->info(" ID: " . $results['ids'][$i]); 502fdbf4cdbSCostin Stroie 503fdbf4cdbSCostin Stroie if (isset($results['documents'][$i])) { 504fdbf4cdbSCostin Stroie $this->info(" Content: " . $results['documents'][$i]); 505fdbf4cdbSCostin Stroie } 506fdbf4cdbSCostin Stroie 507fdbf4cdbSCostin Stroie if (isset($results['metadatas'][$i])) { 508fdbf4cdbSCostin Stroie $this->info(" Metadata: " . json_encode($results['metadatas'][$i], JSON_PRETTY_PRINT)); 509fdbf4cdbSCostin Stroie } 510fdbf4cdbSCostin Stroie $this->info(""); 511fdbf4cdbSCostin Stroie } 512fdbf4cdbSCostin Stroie } catch (Exception $e) { 513fdbf4cdbSCostin Stroie $this->error("Error retrieving document from ChromaDB: " . $e->getMessage()); 514fdbf4cdbSCostin Stroie return; 515fdbf4cdbSCostin Stroie } 516fdbf4cdbSCostin Stroie } 517fdbf4cdbSCostin Stroie} 518