1<?php 2 3use splitbrain\phpcli\Colors; 4use splitbrain\phpcli\Options; 5 6 7/** 8 * DokuWiki Plugin aichat (CLI Component) 9 * 10 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 11 * @author Andreas Gohr <gohr@cosmocode.de> 12 */ 13class cli_plugin_aichat extends \dokuwiki\Extension\CLIPlugin 14{ 15 /** @var helper_plugin_aichat */ 16 protected $helper; 17 18 public function __construct($autocatch = true) 19 { 20 parent::__construct($autocatch); 21 $this->helper = plugin_load('helper', 'aichat'); 22 $this->helper->getEmbeddings()->setLogger($this); 23 } 24 25 26 /** @inheritDoc */ 27 protected function setup(Options $options) 28 { 29 $options->setHelp('Manage and query the AI chatbot data'); 30 31 $options->registerCommand('embed', 'Create embeddings for all pages'); 32 33 $options->registerCommand('similar', 'Search for similar pages'); 34 $options->registerArgument('query', 'Look up chunks similar to this query', true, 'similar'); 35 36 $options->registerCommand('ask', 'Ask a question'); 37 $options->registerArgument('question', 'The question to ask', true, 'ask'); 38 39 $options->registerCommand('chat', 'Start an interactive chat session'); 40 41 $options->registerCommand('split', 'Split a page into chunks (for debugging)'); 42 $options->registerArgument('page', 'The page to split', true, 'split'); 43 44 $options->registerCommand('info', 'Get Info about the K-D Tree'); 45 } 46 47 /** @inheritDoc */ 48 protected function main(Options $options) 49 { 50 switch ($options->getCmd()) { 51 52 case 'embed': 53 $this->createEmbeddings(); 54 break; 55 case 'similar': 56 $this->similar($options->getArgs()[0]); 57 break; 58 case 'ask': 59 $this->ask($options->getArgs()[0]); 60 break; 61 case 'chat': 62 $this->chat(); 63 break; 64 case 'split': 65 $this->split($options->getArgs()[0]); 66 break; 67 case 'info': 68 $this->treeinfo(); 69 break; 70 default: 71 echo $options->help(); 72 } 73 } 74 75 /** 76 * @return void 77 */ 78 protected function treeinfo() 79 { 80 $tree = $this->helper->getEmbeddings()->getTree(); 81 echo 'Items: ' . $tree->getItemCount() . "\n"; 82 echo 'Dimensions: ' . $tree->getDimensionCount() . "\n"; 83 } 84 85 /** 86 * Split the given page into chunks and print them 87 * 88 * @param string $page 89 * @return void 90 * @throws Exception 91 */ 92 protected function split($page) 93 { 94 $text = rawWiki($page); 95 $chunks = $this->helper->getEmbeddings()->splitIntoChunks($text); 96 foreach ($chunks as $chunk) { 97 echo $chunk; 98 echo "\n"; 99 $this->colors->ptln('--------------------------------', Colors::C_LIGHTPURPLE); 100 } 101 $this->success('Split into ' . count($chunks) . ' chunks'); 102 } 103 104 /** 105 * Interactive Chat Session 106 * 107 * @return void 108 * @throws Exception 109 */ 110 protected function chat() 111 { 112 $history = []; 113 while ($q = $this->readLine('Your Question')) { 114 if ($history) { 115 $question = $this->helper->rephraseChatQuestion($q, $history); 116 $this->colors->ptln("Interpretation: $question", Colors::C_LIGHTPURPLE); 117 } else { 118 $question = $q; 119 } 120 $result = $this->helper->askQuestion($question); 121 $history[] = [$q, $result['answer']]; 122 $this->printAnswer($result); 123 } 124 } 125 126 /** 127 * Print the given detailed answer in a nice way 128 * 129 * @param array $answer 130 * @return void 131 */ 132 protected function printAnswer($answer) 133 { 134 $this->colors->ptln($answer['answer'], Colors::C_LIGHTCYAN); 135 echo "\n"; 136 foreach ($answer['sources'] as $source) { 137 $this->colors->ptln("\t" . $source['meta']['pageid'], Colors::C_LIGHTBLUE); 138 } 139 echo "\n"; 140 } 141 142 /** 143 * Handle a single, standalone question 144 * 145 * @param string $query 146 * @return void 147 * @throws Exception 148 */ 149 protected function ask($query) 150 { 151 $result = $this->helper->askQuestion($query); 152 $this->printAnswer($result); 153 } 154 155 /** 156 * Get the pages that are similar to the query 157 * 158 * @param string $query 159 * @return void 160 */ 161 protected function similar($query) 162 { 163 $sources = $this->helper->getEmbeddings()->getSimilarChunks($query); 164 foreach ($sources as $source) { 165 $this->colors->ptln($source['meta']['pageid'], Colors::C_LIGHTBLUE); 166 } 167 } 168 169 /** 170 * Recreate chunks and embeddings for all pages 171 * 172 * @return void 173 * @todo make skip regex configurable 174 */ 175 protected function createEmbeddings() 176 { 177 ini_set('memory_limit', -1); // we may need a lot of memory here 178 $this->helper->getEmbeddings()->createNewIndex('/(^|:)(playground|sandbox)(:|$)/'); 179 $this->notice('Peak memory used: {memory}', ['memory' => filesize_h(memory_get_peak_usage(true))]); 180 } 181 182 /** 183 * Interactively ask for a value from the user 184 * 185 * @param string $prompt 186 * @return string 187 */ 188 protected function readLine($prompt) 189 { 190 $value = ''; 191 192 while ($value === '') { 193 echo $prompt; 194 echo ': '; 195 196 $fh = fopen('php://stdin', 'r'); 197 $value = trim(fgets($fh)); 198 fclose($fh); 199 } 200 201 return $value; 202 } 203} 204 205