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 45 /** @inheritDoc */ 46 protected function main(Options $options) 47 { 48 switch ($options->getCmd()) { 49 50 case 'embed': 51 $this->createEmbeddings(); 52 break; 53 case 'similar': 54 $this->similar($options->getArgs()[0]); 55 break; 56 case 'ask': 57 $this->ask($options->getArgs()[0]); 58 break; 59 case 'chat': 60 $this->chat(); 61 break; 62 case 'split': 63 $this->split($options->getArgs()[0]); 64 break; 65 default: 66 echo $options->help(); 67 } 68 } 69 70 /** 71 * Split the given page into chunks and print them 72 * 73 * @param string $page 74 * @return void 75 * @throws Exception 76 */ 77 protected function split($page) 78 { 79 $text = rawWiki($page); 80 $chunks = $this->helper->getEmbeddings()->splitIntoChunks($text); 81 foreach ($chunks as $chunk) { 82 echo $chunk; 83 echo "\n"; 84 $this->colors->ptln('--------------------------------', Colors::C_LIGHTPURPLE); 85 } 86 $this->success('Split into ' . count($chunks) . ' chunks'); 87 } 88 89 /** 90 * Interactive Chat Session 91 * 92 * @return void 93 * @throws Exception 94 */ 95 protected function chat() 96 { 97 $history = []; 98 while ($q = $this->readLine('Your Question')) { 99 if ($history) { 100 $question = $this->helper->rephraseChatQuestion($q, $history); 101 $this->colors->ptln("Interpretation: $question", Colors::C_LIGHTPURPLE); 102 } else { 103 $question = $q; 104 } 105 $result = $this->helper->askQuestion($question); 106 $history[] = [$q, $result['answer']]; 107 $this->printAnswer($result); 108 } 109 } 110 111 /** 112 * Print the given detailed answer in a nice way 113 * 114 * @param array $answer 115 * @return void 116 */ 117 protected function printAnswer($answer) 118 { 119 $this->colors->ptln($answer['answer'], Colors::C_LIGHTCYAN); 120 echo "\n"; 121 foreach ($answer['sources'] as $source) { 122 $this->colors->ptln("\t" . $source['meta']['pageid'], Colors::C_LIGHTBLUE); 123 } 124 echo "\n"; 125 } 126 127 /** 128 * Handle a single, standalone question 129 * 130 * @param string $query 131 * @return void 132 * @throws Exception 133 */ 134 protected function ask($query) 135 { 136 $result = $this->helper->askQuestion($query); 137 $this->printAnswer($result); 138 } 139 140 /** 141 * Get the pages that are similar to the query 142 * 143 * @param string $query 144 * @return void 145 */ 146 protected function similar($query) 147 { 148 $sources = $this->helper->getEmbeddings()->getSimilarChunks($query); 149 foreach ($sources as $source) { 150 $this->colors->ptln($source['meta']['pageid'], Colors::C_LIGHTBLUE); 151 } 152 } 153 154 /** 155 * Recreate chunks and embeddings for all pages 156 * 157 * @return void 158 * @todo make skip regex configurable 159 */ 160 protected function createEmbeddings() 161 { 162 ini_set('memory_limit', -1); // we may need a lot of memory here 163 $this->helper->getEmbeddings()->createNewIndex('/(^|:)(playground|sandbox)(:|$)/'); 164 $this->notice('Peak memory used: {memory}', ['memory' => filesize_h(memory_get_peak_usage(true))]); 165 } 166 167 /** 168 * Interactively ask for a value from the user 169 * 170 * @param string $prompt 171 * @return string 172 */ 173 protected function readLine($prompt) 174 { 175 $value = ''; 176 177 while ($value === '') { 178 echo $prompt; 179 echo ': '; 180 181 $fh = fopen('php://stdin', 'r'); 182 $value = trim(fgets($fh)); 183 fclose($fh); 184 } 185 186 return $value; 187 } 188} 189 190