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 $stats = $this->helper->getEmbeddings()->getStorage()->statistics(); 81 foreach($stats as $key => $value) { 82 echo $key . ': ' . $value. "\n"; 83 } 84 } 85 86 /** 87 * Split the given page into chunks and print them 88 * 89 * @param string $page 90 * @return void 91 * @throws Exception 92 */ 93 protected function split($page) 94 { 95 $text = rawWiki($page); 96 $chunks = $this->helper->getEmbeddings()->splitIntoChunks($text); 97 foreach ($chunks as $chunk) { 98 echo $chunk; 99 echo "\n"; 100 $this->colors->ptln('--------------------------------', Colors::C_LIGHTPURPLE); 101 } 102 $this->success('Split into ' . count($chunks) . ' chunks'); 103 } 104 105 /** 106 * Interactive Chat Session 107 * 108 * @return void 109 * @throws Exception 110 */ 111 protected function chat() 112 { 113 $history = []; 114 while ($q = $this->readLine('Your Question')) { 115 if ($history) { 116 $question = $this->helper->rephraseChatQuestion($q, $history); 117 $this->colors->ptln("Interpretation: $question", Colors::C_LIGHTPURPLE); 118 } else { 119 $question = $q; 120 } 121 $result = $this->helper->askQuestion($question); 122 $history[] = [$q, $result['answer']]; 123 $this->printAnswer($result); 124 } 125 } 126 127 /** 128 * Print the given detailed answer in a nice way 129 * 130 * @param array $answer 131 * @return void 132 */ 133 protected function printAnswer($answer) 134 { 135 $this->colors->ptln($answer['answer'], Colors::C_LIGHTCYAN); 136 echo "\n"; 137 foreach ($answer['sources'] as $source) { 138 $this->colors->ptln("\t" . $source['meta']['pageid'], Colors::C_LIGHTBLUE); 139 } 140 echo "\n"; 141 } 142 143 /** 144 * Handle a single, standalone question 145 * 146 * @param string $query 147 * @return void 148 * @throws Exception 149 */ 150 protected function ask($query) 151 { 152 $result = $this->helper->askQuestion($query); 153 $this->printAnswer($result); 154 } 155 156 /** 157 * Get the pages that are similar to the query 158 * 159 * @param string $query 160 * @return void 161 */ 162 protected function similar($query) 163 { 164 $sources = $this->helper->getEmbeddings()->getSimilarChunks($query); 165 foreach ($sources as $source) { 166 $this->colors->ptln($source->getPage(), Colors::C_LIGHTBLUE); 167 } 168 } 169 170 /** 171 * Recreate chunks and embeddings for all pages 172 * 173 * @return void 174 * @todo make skip regex configurable 175 */ 176 protected function createEmbeddings() 177 { 178 ini_set('memory_limit', -1); // we may need a lot of memory here 179 $this->helper->getEmbeddings()->createNewIndex('/(^|:)(playground|sandbox)(:|$)/'); 180 $this->notice('Peak memory used: {memory}', ['memory' => filesize_h(memory_get_peak_usage(true))]); 181 } 182 183 /** 184 * Interactively ask for a value from the user 185 * 186 * @param string $prompt 187 * @return string 188 */ 189 protected function readLine($prompt) 190 { 191 $value = ''; 192 193 while ($value === '') { 194 echo $prompt; 195 echo ': '; 196 197 $fh = fopen('php://stdin', 'r'); 198 $value = trim(fgets($fh)); 199 fclose($fh); 200 } 201 202 return $value; 203 } 204} 205 206