18817535bSAndreas Gohr<?php 28817535bSAndreas Gohr 38817535bSAndreas Gohruse dokuwiki\plugin\aichat\Embeddings; 48817535bSAndreas Gohruse dokuwiki\plugin\aichat\OpenAI; 58817535bSAndreas Gohruse Hexogen\KDTree\FSKDTree; 68817535bSAndreas Gohruse Hexogen\KDTree\FSTreePersister; 78817535bSAndreas Gohruse Hexogen\KDTree\Item; 88817535bSAndreas Gohruse Hexogen\KDTree\ItemFactory; 98817535bSAndreas Gohruse Hexogen\KDTree\ItemList; 108817535bSAndreas Gohruse Hexogen\KDTree\KDTree; 118817535bSAndreas Gohruse Hexogen\KDTree\NearestSearch; 128817535bSAndreas Gohruse Hexogen\KDTree\Point; 13*c4584168SAndreas Gohruse splitbrain\phpcli\Colors; 148817535bSAndreas Gohruse splitbrain\phpcli\Options; 158817535bSAndreas Gohr 168817535bSAndreas Gohrrequire_once __DIR__ . '/vendor/autoload.php'; 178817535bSAndreas Gohr 188817535bSAndreas Gohr/** 198817535bSAndreas Gohr * DokuWiki Plugin aichat (CLI Component) 208817535bSAndreas Gohr * 218817535bSAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 228817535bSAndreas Gohr * @author Andreas Gohr <gohr@cosmocode.de> 238817535bSAndreas Gohr */ 248817535bSAndreas Gohrclass cli_plugin_aichat extends \dokuwiki\Extension\CLIPlugin 258817535bSAndreas Gohr{ 268817535bSAndreas Gohr 278817535bSAndreas Gohr /** @inheritDoc */ 288817535bSAndreas Gohr protected function setup(Options $options) 298817535bSAndreas Gohr { 308817535bSAndreas Gohr $options->setHelp('Manage the AI chatbot data'); 318817535bSAndreas Gohr 328817535bSAndreas Gohr $options->registerCommand('embed', 'Create embeddings for all pages'); 338817535bSAndreas Gohr 348817535bSAndreas Gohr $options->registerCommand('similar', 'Search for similar pages'); 358817535bSAndreas Gohr $options->registerArgument('query', 'Look up chunks similar to this query', true, 'similar'); 368817535bSAndreas Gohr 378817535bSAndreas Gohr $options->registerCommand('ask', 'Ask a question'); 388817535bSAndreas Gohr $options->registerArgument('question', 'The question to ask', true, 'ask'); 39*c4584168SAndreas Gohr 40*c4584168SAndreas Gohr $options->registerCommand('chat', 'Start an interactive chat session'); 418817535bSAndreas Gohr } 428817535bSAndreas Gohr 438817535bSAndreas Gohr /** @inheritDoc */ 448817535bSAndreas Gohr protected function main(Options $options) 458817535bSAndreas Gohr { 468817535bSAndreas Gohr switch ($options->getCmd()) { 478817535bSAndreas Gohr 488817535bSAndreas Gohr case 'embed': 498817535bSAndreas Gohr $this->createEmbeddings(); 508817535bSAndreas Gohr break; 518817535bSAndreas Gohr case 'similar': 528817535bSAndreas Gohr $this->similar($options->getArgs()[0]); 538817535bSAndreas Gohr break; 547552f1aaSAndreas Gohr case 'ask': 557552f1aaSAndreas Gohr $this->ask($options->getArgs()[0]); 567552f1aaSAndreas Gohr break; 57*c4584168SAndreas Gohr case 'chat': 58*c4584168SAndreas Gohr $this->chat(); 59*c4584168SAndreas Gohr break; 608817535bSAndreas Gohr default: 618817535bSAndreas Gohr echo $options->help(); 628817535bSAndreas Gohr } 638817535bSAndreas Gohr } 648817535bSAndreas Gohr 65*c4584168SAndreas Gohr /** 66*c4584168SAndreas Gohr * Interactive Chat Session 67*c4584168SAndreas Gohr * 68*c4584168SAndreas Gohr * @return void 69*c4584168SAndreas Gohr * @throws Exception 70*c4584168SAndreas Gohr */ 71*c4584168SAndreas Gohr protected function chat() 72*c4584168SAndreas Gohr { 73*c4584168SAndreas Gohr /** @var helper_plugin_aichat_prompt $prompt */ 74*c4584168SAndreas Gohr $prompt = plugin_load('helper', 'aichat_prompt'); 75*c4584168SAndreas Gohr 76*c4584168SAndreas Gohr $history = []; 77*c4584168SAndreas Gohr while ($q = $this->readLine('Your Question')) { 78*c4584168SAndreas Gohr if ($history) { 79*c4584168SAndreas Gohr $question = $prompt->rephraseChatQuestion($q, $history); 80*c4584168SAndreas Gohr $this->colors->ptln("Interpretation: $question", Colors::C_LIGHTPURPLE); 81*c4584168SAndreas Gohr } else { 82*c4584168SAndreas Gohr $question = $q; 83*c4584168SAndreas Gohr } 84*c4584168SAndreas Gohr $result = $prompt->askQuestion($question); 85*c4584168SAndreas Gohr $history[] = [$q, $result['answer']]; 86*c4584168SAndreas Gohr $this->printAnswer($result); 87*c4584168SAndreas Gohr } 88*c4584168SAndreas Gohr 89*c4584168SAndreas Gohr } 90*c4584168SAndreas Gohr 91*c4584168SAndreas Gohr /** 92*c4584168SAndreas Gohr * Print the given detailed answer in a nice way 93*c4584168SAndreas Gohr * 94*c4584168SAndreas Gohr * @param array $answer 95*c4584168SAndreas Gohr * @return void 96*c4584168SAndreas Gohr */ 97*c4584168SAndreas Gohr protected function printAnswer($answer) 98*c4584168SAndreas Gohr { 99*c4584168SAndreas Gohr $this->colors->ptln($answer['answer'], Colors::C_LIGHTCYAN); 100*c4584168SAndreas Gohr echo "\n"; 101*c4584168SAndreas Gohr foreach ($answer['sources'] as $source) { 102*c4584168SAndreas Gohr $this->colors->ptln("\t".$source['meta']['pageid'], Colors::C_LIGHTBLUE); 103*c4584168SAndreas Gohr } 104*c4584168SAndreas Gohr echo "\n"; 105*c4584168SAndreas Gohr } 106*c4584168SAndreas Gohr 107*c4584168SAndreas Gohr /** 108*c4584168SAndreas Gohr * Handle a single, standalone question 109*c4584168SAndreas Gohr * 110*c4584168SAndreas Gohr * @param string $query 111*c4584168SAndreas Gohr * @return void 112*c4584168SAndreas Gohr * @throws Exception 113*c4584168SAndreas Gohr */ 114*c4584168SAndreas Gohr protected function ask($query) 115*c4584168SAndreas Gohr { 1167552f1aaSAndreas Gohr /** @var helper_plugin_aichat_prompt $prompt */ 1177552f1aaSAndreas Gohr $prompt = plugin_load('helper', 'aichat_prompt'); 1187552f1aaSAndreas Gohr 1197552f1aaSAndreas Gohr $result = $prompt->askQuestion($query); 120*c4584168SAndreas Gohr $this->printAnswer($result); 1217552f1aaSAndreas Gohr } 1227552f1aaSAndreas Gohr 123*c4584168SAndreas Gohr /** 124*c4584168SAndreas Gohr * Get the pages that are similar to the query 125*c4584168SAndreas Gohr * 126*c4584168SAndreas Gohr * @param string $query 127*c4584168SAndreas Gohr * @return void 128*c4584168SAndreas Gohr */ 1298817535bSAndreas Gohr protected function similar($query) 1308817535bSAndreas Gohr { 1318817535bSAndreas Gohr $openAI = new OpenAI($this->getConf('openaikey'), $this->getConf('openaiorg')); 1328817535bSAndreas Gohr $embedding = new Embeddings($openAI, $this); 1338817535bSAndreas Gohr 134*c4584168SAndreas Gohr $sources = $embedding->getSimilarChunks($query); 135*c4584168SAndreas Gohr foreach ($sources as $source) { 136*c4584168SAndreas Gohr $this->colors->ptln($source['meta']['pageid'], Colors::C_LIGHTBLUE); 137*c4584168SAndreas Gohr } 1388817535bSAndreas Gohr } 1398817535bSAndreas Gohr 140*c4584168SAndreas Gohr /** 141*c4584168SAndreas Gohr * Recreate chunks and embeddings for all pages 142*c4584168SAndreas Gohr * 143*c4584168SAndreas Gohr * @return void 144*c4584168SAndreas Gohr */ 1458817535bSAndreas Gohr protected function createEmbeddings() 1468817535bSAndreas Gohr { 1478817535bSAndreas Gohr $openAI = new OpenAI($this->getConf('openaikey'), $this->getConf('openaiorg')); 1488817535bSAndreas Gohr 1498817535bSAndreas Gohr $embeddings = new Embeddings($openAI, $this); 1508817535bSAndreas Gohr $embeddings->createNewIndex(); 1518817535bSAndreas Gohr } 1528817535bSAndreas Gohr 153*c4584168SAndreas Gohr /** 154*c4584168SAndreas Gohr * Interactively ask for a value from the user 155*c4584168SAndreas Gohr * 156*c4584168SAndreas Gohr * @param string $prompt 157*c4584168SAndreas Gohr * @return string 158*c4584168SAndreas Gohr */ 159*c4584168SAndreas Gohr protected function readLine($prompt) 160*c4584168SAndreas Gohr { 161*c4584168SAndreas Gohr $value = ''; 1628817535bSAndreas Gohr 163*c4584168SAndreas Gohr while ($value === '') { 164*c4584168SAndreas Gohr echo $prompt; 165*c4584168SAndreas Gohr echo ': '; 166*c4584168SAndreas Gohr 167*c4584168SAndreas Gohr $fh = fopen('php://stdin', 'r'); 168*c4584168SAndreas Gohr $value = trim(fgets($fh)); 169*c4584168SAndreas Gohr fclose($fh); 170*c4584168SAndreas Gohr } 171*c4584168SAndreas Gohr 172*c4584168SAndreas Gohr return $value; 173*c4584168SAndreas Gohr } 1748817535bSAndreas Gohr} 1758817535bSAndreas Gohr 176