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