18817535bSAndreas Gohr<?php 28817535bSAndreas Gohr 3c4584168SAndreas Gohruse splitbrain\phpcli\Colors; 48817535bSAndreas Gohruse splitbrain\phpcli\Options; 58817535bSAndreas Gohr 68817535bSAndreas Gohr 78817535bSAndreas Gohr/** 88817535bSAndreas Gohr * DokuWiki Plugin aichat (CLI Component) 98817535bSAndreas Gohr * 108817535bSAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 118817535bSAndreas Gohr * @author Andreas Gohr <gohr@cosmocode.de> 128817535bSAndreas Gohr */ 138817535bSAndreas Gohrclass cli_plugin_aichat extends \dokuwiki\Extension\CLIPlugin 148817535bSAndreas Gohr{ 150337f47fSAndreas Gohr /** @var helper_plugin_aichat */ 160337f47fSAndreas Gohr protected $helper; 170337f47fSAndreas Gohr 180337f47fSAndreas Gohr public function __construct($autocatch = true) 190337f47fSAndreas Gohr { 200337f47fSAndreas Gohr parent::__construct($autocatch); 210337f47fSAndreas Gohr $this->helper = plugin_load('helper', 'aichat'); 222ecc089aSAndreas Gohr $this->helper->getEmbeddings()->setLogger($this); 230337f47fSAndreas Gohr } 240337f47fSAndreas Gohr 258817535bSAndreas Gohr 268817535bSAndreas Gohr /** @inheritDoc */ 278817535bSAndreas Gohr protected function setup(Options $options) 288817535bSAndreas Gohr { 299da5f0dfSAndreas Gohr $options->setHelp('Manage and query the AI chatbot data'); 308817535bSAndreas Gohr 318817535bSAndreas Gohr $options->registerCommand('embed', 'Create embeddings for all pages'); 328817535bSAndreas Gohr 338817535bSAndreas Gohr $options->registerCommand('similar', 'Search for similar pages'); 348817535bSAndreas Gohr $options->registerArgument('query', 'Look up chunks similar to this query', true, 'similar'); 358817535bSAndreas Gohr 368817535bSAndreas Gohr $options->registerCommand('ask', 'Ask a question'); 378817535bSAndreas Gohr $options->registerArgument('question', 'The question to ask', true, 'ask'); 38c4584168SAndreas Gohr 39c4584168SAndreas Gohr $options->registerCommand('chat', 'Start an interactive chat session'); 40*ad38c5fdSAndreas Gohr 41*ad38c5fdSAndreas Gohr $options->registerCommand('split', 'Split a page into chunks (for debugging)'); 42*ad38c5fdSAndreas Gohr $options->registerArgument('page', 'The page to split', true, 'split'); 438817535bSAndreas Gohr } 448817535bSAndreas Gohr 458817535bSAndreas Gohr /** @inheritDoc */ 468817535bSAndreas Gohr protected function main(Options $options) 478817535bSAndreas Gohr { 488817535bSAndreas Gohr switch ($options->getCmd()) { 498817535bSAndreas Gohr 508817535bSAndreas Gohr case 'embed': 518817535bSAndreas Gohr $this->createEmbeddings(); 528817535bSAndreas Gohr break; 538817535bSAndreas Gohr case 'similar': 548817535bSAndreas Gohr $this->similar($options->getArgs()[0]); 558817535bSAndreas Gohr break; 567552f1aaSAndreas Gohr case 'ask': 577552f1aaSAndreas Gohr $this->ask($options->getArgs()[0]); 587552f1aaSAndreas Gohr break; 59c4584168SAndreas Gohr case 'chat': 60c4584168SAndreas Gohr $this->chat(); 61c4584168SAndreas Gohr break; 62*ad38c5fdSAndreas Gohr case 'split': 63*ad38c5fdSAndreas Gohr $this->split($options->getArgs()[0]); 64*ad38c5fdSAndreas Gohr break; 658817535bSAndreas Gohr default: 668817535bSAndreas Gohr echo $options->help(); 678817535bSAndreas Gohr } 688817535bSAndreas Gohr } 698817535bSAndreas Gohr 70c4584168SAndreas Gohr /** 71*ad38c5fdSAndreas Gohr * Split the given page into chunks and print them 72*ad38c5fdSAndreas Gohr * 73*ad38c5fdSAndreas Gohr * @param string $page 74*ad38c5fdSAndreas Gohr * @return void 75*ad38c5fdSAndreas Gohr * @throws Exception 76*ad38c5fdSAndreas Gohr */ 77*ad38c5fdSAndreas Gohr protected function split($page) 78*ad38c5fdSAndreas Gohr { 79*ad38c5fdSAndreas Gohr $text = rawWiki($page); 80*ad38c5fdSAndreas Gohr $chunks = $this->helper->getEmbeddings()->splitIntoChunks($text); 81*ad38c5fdSAndreas Gohr foreach ($chunks as $chunk) { 82*ad38c5fdSAndreas Gohr echo $chunk; 83*ad38c5fdSAndreas Gohr echo "\n"; 84*ad38c5fdSAndreas Gohr $this->colors->ptln('--------------------------------', Colors::C_LIGHTPURPLE); 85*ad38c5fdSAndreas Gohr } 86*ad38c5fdSAndreas Gohr $this->success('Split into ' . count($chunks) . ' chunks'); 87*ad38c5fdSAndreas Gohr } 88*ad38c5fdSAndreas Gohr 89*ad38c5fdSAndreas Gohr /** 90c4584168SAndreas Gohr * Interactive Chat Session 91c4584168SAndreas Gohr * 92c4584168SAndreas Gohr * @return void 93c4584168SAndreas Gohr * @throws Exception 94c4584168SAndreas Gohr */ 95c4584168SAndreas Gohr protected function chat() 96c4584168SAndreas Gohr { 97c4584168SAndreas Gohr $history = []; 98c4584168SAndreas Gohr while ($q = $this->readLine('Your Question')) { 99c4584168SAndreas Gohr if ($history) { 1000337f47fSAndreas Gohr $question = $this->helper->rephraseChatQuestion($q, $history); 101c4584168SAndreas Gohr $this->colors->ptln("Interpretation: $question", Colors::C_LIGHTPURPLE); 102c4584168SAndreas Gohr } else { 103c4584168SAndreas Gohr $question = $q; 104c4584168SAndreas Gohr } 1050337f47fSAndreas Gohr $result = $this->helper->askQuestion($question); 106c4584168SAndreas Gohr $history[] = [$q, $result['answer']]; 107c4584168SAndreas Gohr $this->printAnswer($result); 108c4584168SAndreas Gohr } 109c4584168SAndreas Gohr } 110c4584168SAndreas Gohr 111c4584168SAndreas Gohr /** 112c4584168SAndreas Gohr * Print the given detailed answer in a nice way 113c4584168SAndreas Gohr * 114c4584168SAndreas Gohr * @param array $answer 115c4584168SAndreas Gohr * @return void 116c4584168SAndreas Gohr */ 117c4584168SAndreas Gohr protected function printAnswer($answer) 118c4584168SAndreas Gohr { 119c4584168SAndreas Gohr $this->colors->ptln($answer['answer'], Colors::C_LIGHTCYAN); 120c4584168SAndreas Gohr echo "\n"; 121c4584168SAndreas Gohr foreach ($answer['sources'] as $source) { 122c4584168SAndreas Gohr $this->colors->ptln("\t" . $source['meta']['pageid'], Colors::C_LIGHTBLUE); 123c4584168SAndreas Gohr } 124c4584168SAndreas Gohr echo "\n"; 125c4584168SAndreas Gohr } 126c4584168SAndreas Gohr 127c4584168SAndreas Gohr /** 128c4584168SAndreas Gohr * Handle a single, standalone question 129c4584168SAndreas Gohr * 130c4584168SAndreas Gohr * @param string $query 131c4584168SAndreas Gohr * @return void 132c4584168SAndreas Gohr * @throws Exception 133c4584168SAndreas Gohr */ 134c4584168SAndreas Gohr protected function ask($query) 135c4584168SAndreas Gohr { 1360337f47fSAndreas Gohr $result = $this->helper->askQuestion($query); 137c4584168SAndreas Gohr $this->printAnswer($result); 1387552f1aaSAndreas Gohr } 1397552f1aaSAndreas Gohr 140c4584168SAndreas Gohr /** 141c4584168SAndreas Gohr * Get the pages that are similar to the query 142c4584168SAndreas Gohr * 143c4584168SAndreas Gohr * @param string $query 144c4584168SAndreas Gohr * @return void 145c4584168SAndreas Gohr */ 1468817535bSAndreas Gohr protected function similar($query) 1478817535bSAndreas Gohr { 1480337f47fSAndreas Gohr $sources = $this->helper->getEmbeddings()->getSimilarChunks($query); 149c4584168SAndreas Gohr foreach ($sources as $source) { 150c4584168SAndreas Gohr $this->colors->ptln($source['meta']['pageid'], Colors::C_LIGHTBLUE); 151c4584168SAndreas Gohr } 1528817535bSAndreas Gohr } 1538817535bSAndreas Gohr 154c4584168SAndreas Gohr /** 155c4584168SAndreas Gohr * Recreate chunks and embeddings for all pages 156c4584168SAndreas Gohr * 157c4584168SAndreas Gohr * @return void 158*ad38c5fdSAndreas Gohr * @todo make skip regex configurable 159c4584168SAndreas Gohr */ 1608817535bSAndreas Gohr protected function createEmbeddings() 1618817535bSAndreas Gohr { 162*ad38c5fdSAndreas Gohr ini_set('memory_limit', -1); // we may need a lot of memory here 163*ad38c5fdSAndreas Gohr $this->helper->getEmbeddings()->createNewIndex('/(^|:)(playground|sandbox)(:|$)/'); 164*ad38c5fdSAndreas Gohr $this->notice('Peak memory used: {memory}', ['memory' => filesize_h(memory_get_peak_usage(true))]); 1658817535bSAndreas Gohr } 1668817535bSAndreas Gohr 167c4584168SAndreas Gohr /** 168c4584168SAndreas Gohr * Interactively ask for a value from the user 169c4584168SAndreas Gohr * 170c4584168SAndreas Gohr * @param string $prompt 171c4584168SAndreas Gohr * @return string 172c4584168SAndreas Gohr */ 173c4584168SAndreas Gohr protected function readLine($prompt) 174c4584168SAndreas Gohr { 175c4584168SAndreas Gohr $value = ''; 1768817535bSAndreas Gohr 177c4584168SAndreas Gohr while ($value === '') { 178c4584168SAndreas Gohr echo $prompt; 179c4584168SAndreas Gohr echo ': '; 180c4584168SAndreas Gohr 181c4584168SAndreas Gohr $fh = fopen('php://stdin', 'r'); 182c4584168SAndreas Gohr $value = trim(fgets($fh)); 183c4584168SAndreas Gohr fclose($fh); 184c4584168SAndreas Gohr } 185c4584168SAndreas Gohr 186c4584168SAndreas Gohr return $value; 187c4584168SAndreas Gohr } 1888817535bSAndreas Gohr} 1898817535bSAndreas Gohr 190