xref: /plugin/dokullm/cli.php (revision 17e138999dac575a86cf345a102e785801382874)
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');
40*17e13899SCostin Stroie        //$options->registerOption('collection', 'Collection name to query', 'c', 'collection', 'documents', 'query');
41*17e13899SCostin 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');
51*17e13899SCostin 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
64605489dcSCostin Stroie (aider)        // Get values from DokuWiki settings
65605489dcSCostin Stroie (aider)        $host = $this->getConf('chroma_host');
66605489dcSCostin Stroie (aider)        $port = (int)$this->getConf('chroma_port');
67605489dcSCostin Stroie (aider)        $tenant = $this->getConf('chroma_tenant');
68605489dcSCostin Stroie (aider)        $database = $this->getConf('chroma_database');
69605489dcSCostin Stroie (aider)        $ollamaHost = $this->getConf('ollama_host');
70605489dcSCostin Stroie (aider)        $ollamaPort = (int)$this->getConf('ollama_port');
71605489dcSCostin 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);
165*17e13899SCostin Stroie        //if (auth_quickaclcheck($cleanId) < AUTH_READ) {
166*17e13899SCostin Stroie        //    $this->error("You are not allowed to read this file: $id");
167*17e13899SCostin Stroie        //    return;
168*17e13899SCostin 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