18817535bSAndreas Gohr<?php 28817535bSAndreas Gohr 3f6ef2e50SAndreas Gohruse dokuwiki\Extension\CLIPlugin; 4f6ef2e50SAndreas Gohruse dokuwiki\plugin\aichat\Chunk; 501f06932SAndreas Gohruse dokuwiki\Search\Indexer; 6c4584168SAndreas Gohruse splitbrain\phpcli\Colors; 78817535bSAndreas Gohruse splitbrain\phpcli\Options; 83379af09SAndreas Gohruse splitbrain\phpcli\TableFormatter; 98817535bSAndreas Gohr 108817535bSAndreas Gohr/** 118817535bSAndreas Gohr * DokuWiki Plugin aichat (CLI Component) 128817535bSAndreas Gohr * 138817535bSAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 148817535bSAndreas Gohr * @author Andreas Gohr <gohr@cosmocode.de> 158817535bSAndreas Gohr */ 16f6ef2e50SAndreas Gohrclass cli_plugin_aichat extends CLIPlugin 178817535bSAndreas Gohr{ 180337f47fSAndreas Gohr /** @var helper_plugin_aichat */ 190337f47fSAndreas Gohr protected $helper; 200337f47fSAndreas Gohr 210337f47fSAndreas Gohr public function __construct($autocatch = true) 220337f47fSAndreas Gohr { 230337f47fSAndreas Gohr parent::__construct($autocatch); 240337f47fSAndreas Gohr $this->helper = plugin_load('helper', 'aichat'); 253379af09SAndreas Gohr $this->helper->setLogger($this); 260337f47fSAndreas Gohr } 270337f47fSAndreas Gohr 288817535bSAndreas Gohr /** @inheritDoc */ 298817535bSAndreas Gohr protected function setup(Options $options) 308817535bSAndreas Gohr { 31bddd899cSAndreas Gohr $options->useCompactHelp(); 32bddd899cSAndreas Gohr 335284515dSAndreas Gohr $options->setHelp( 345284515dSAndreas Gohr 'Manage and query the AI chatbot data. Please note that calls to your LLM provider will be made. ' . 355284515dSAndreas Gohr 'This may incur costs.' 365284515dSAndreas Gohr ); 378817535bSAndreas Gohr 385284515dSAndreas Gohr $options->registerCommand( 395284515dSAndreas Gohr 'embed', 405284515dSAndreas Gohr 'Create embeddings for all pages. This skips pages that already have embeddings' 415284515dSAndreas Gohr ); 425284515dSAndreas Gohr $options->registerOption( 435284515dSAndreas Gohr 'clear', 445284515dSAndreas Gohr 'Clear all existing embeddings before creating new ones', 457ebc7895Ssplitbrain 'c', 467ebc7895Ssplitbrain false, 477ebc7895Ssplitbrain 'embed' 485284515dSAndreas Gohr ); 498817535bSAndreas Gohr 503379af09SAndreas Gohr $options->registerCommand('maintenance', 'Run storage maintenance. Refert to the documentation for details.'); 513379af09SAndreas Gohr 528817535bSAndreas Gohr $options->registerCommand('similar', 'Search for similar pages'); 538817535bSAndreas Gohr $options->registerArgument('query', 'Look up chunks similar to this query', true, 'similar'); 548817535bSAndreas Gohr 558817535bSAndreas Gohr $options->registerCommand('ask', 'Ask a question'); 568817535bSAndreas Gohr $options->registerArgument('question', 'The question to ask', true, 'ask'); 57c4584168SAndreas Gohr 58c4584168SAndreas Gohr $options->registerCommand('chat', 'Start an interactive chat session'); 59ad38c5fdSAndreas Gohr 60e75dc39fSAndreas Gohr $options->registerCommand('info', 'Get Info about the vector storage and other stats'); 618c8b7ba6SAndreas Gohr 62ad38c5fdSAndreas Gohr $options->registerCommand('split', 'Split a page into chunks (for debugging)'); 63ad38c5fdSAndreas Gohr $options->registerArgument('page', 'The page to split', true, 'split'); 645786be46SAndreas Gohr 6501f06932SAndreas Gohr $options->registerCommand('page', 'Check if chunks for a given page are available (for debugging)'); 6601f06932SAndreas Gohr $options->registerArgument('page', 'The page to check', true, 'page'); 67dc355d57SAndreas Gohr $options->registerOption('dump', 'Dump the chunks', 'd', false, 'page'); 6801f06932SAndreas Gohr 698c8b7ba6SAndreas Gohr $options->registerCommand('tsv', 'Create TSV files for visualizing at http://projector.tensorflow.org/' . 708c8b7ba6SAndreas Gohr ' Not supported on all storages.'); 718c8b7ba6SAndreas Gohr $options->registerArgument('vector.tsv', 'The vector file', false, 'tsv'); 728c8b7ba6SAndreas Gohr $options->registerArgument('meta.tsv', 'The meta file', false, 'tsv'); 738817535bSAndreas Gohr } 748817535bSAndreas Gohr 758817535bSAndreas Gohr /** @inheritDoc */ 768817535bSAndreas Gohr protected function main(Options $options) 778817535bSAndreas Gohr { 783379af09SAndreas Gohr ini_set('memory_limit', -1); 798817535bSAndreas Gohr switch ($options->getCmd()) { 808817535bSAndreas Gohr case 'embed': 815284515dSAndreas Gohr $this->createEmbeddings($options->getOpt('clear')); 828817535bSAndreas Gohr break; 833379af09SAndreas Gohr case 'maintenance': 843379af09SAndreas Gohr $this->runMaintenance(); 853379af09SAndreas Gohr break; 868817535bSAndreas Gohr case 'similar': 878817535bSAndreas Gohr $this->similar($options->getArgs()[0]); 888817535bSAndreas Gohr break; 897552f1aaSAndreas Gohr case 'ask': 907552f1aaSAndreas Gohr $this->ask($options->getArgs()[0]); 917552f1aaSAndreas Gohr break; 92c4584168SAndreas Gohr case 'chat': 93c4584168SAndreas Gohr $this->chat(); 94c4584168SAndreas Gohr break; 95ad38c5fdSAndreas Gohr case 'split': 96ad38c5fdSAndreas Gohr $this->split($options->getArgs()[0]); 97ad38c5fdSAndreas Gohr break; 9801f06932SAndreas Gohr case 'page': 99dc355d57SAndreas Gohr $this->page($options->getArgs()[0], $options->getOpt('dump')); 10001f06932SAndreas Gohr break; 1015786be46SAndreas Gohr case 'info': 102f6ef2e50SAndreas Gohr $this->showinfo(); 1035786be46SAndreas Gohr break; 1048c8b7ba6SAndreas Gohr case 'tsv': 1058c8b7ba6SAndreas Gohr $args = $options->getArgs(); 1068c8b7ba6SAndreas Gohr $vector = $args[0] ?? 'vector.tsv'; 1078c8b7ba6SAndreas Gohr $meta = $args[1] ?? 'meta.tsv'; 1088c8b7ba6SAndreas Gohr $this->tsv($vector, $meta); 1098c8b7ba6SAndreas Gohr break; 1108817535bSAndreas Gohr default: 1118817535bSAndreas Gohr echo $options->help(); 1128817535bSAndreas Gohr } 1138817535bSAndreas Gohr } 1148817535bSAndreas Gohr 115c4584168SAndreas Gohr /** 1165786be46SAndreas Gohr * @return void 1175786be46SAndreas Gohr */ 118f6ef2e50SAndreas Gohr protected function showinfo() 1195786be46SAndreas Gohr { 1203379af09SAndreas Gohr $stats = [ 1213379af09SAndreas Gohr 'model' => $this->getConf('model'), 1223379af09SAndreas Gohr ]; 123e75dc39fSAndreas Gohr $stats = array_merge( 124e75dc39fSAndreas Gohr $stats, 125e75dc39fSAndreas Gohr array_map('dformat', $this->helper->getRunData()), 126e75dc39fSAndreas Gohr $this->helper->getStorage()->statistics() 127e75dc39fSAndreas Gohr ); 1283379af09SAndreas Gohr $this->printTable($stats); 1297ee8b02dSAndreas Gohr } 130911314cdSAndreas Gohr 1313379af09SAndreas Gohr /** 1323379af09SAndreas Gohr * Print key value data as tabular data 1333379af09SAndreas Gohr * 1343379af09SAndreas Gohr * @param array $data 1353379af09SAndreas Gohr * @param int $level 1363379af09SAndreas Gohr * @return void 1373379af09SAndreas Gohr */ 1383379af09SAndreas Gohr protected function printTable($data, $level = 0) 1393379af09SAndreas Gohr { 1403379af09SAndreas Gohr $tf = new TableFormatter($this->colors); 1413379af09SAndreas Gohr foreach ($data as $key => $value) { 1423379af09SAndreas Gohr if (is_array($value)) { 1433379af09SAndreas Gohr echo $tf->format( 144e75dc39fSAndreas Gohr [$level * 2, 20, '*'], 1453379af09SAndreas Gohr ['', $key, ''], 1463379af09SAndreas Gohr [Colors::C_LIGHTBLUE, Colors::C_LIGHTBLUE, Colors::C_LIGHTBLUE] 1473379af09SAndreas Gohr ); 1483379af09SAndreas Gohr $this->printTable($value, $level + 1); 1493379af09SAndreas Gohr } else { 1503379af09SAndreas Gohr echo $tf->format( 151e75dc39fSAndreas Gohr [$level * 2, 20, '*'], 1523379af09SAndreas Gohr ['', $key, $value], 1533379af09SAndreas Gohr [Colors::C_LIGHTBLUE, Colors::C_LIGHTBLUE, Colors::C_LIGHTGRAY] 1543379af09SAndreas Gohr ); 1553379af09SAndreas Gohr } 1563379af09SAndreas Gohr } 1575786be46SAndreas Gohr } 1585786be46SAndreas Gohr 1595786be46SAndreas Gohr /** 16001f06932SAndreas Gohr * Check chunk availability for a given page 16101f06932SAndreas Gohr * 16201f06932SAndreas Gohr * @param string $page 16301f06932SAndreas Gohr * @return void 16401f06932SAndreas Gohr */ 165dc355d57SAndreas Gohr protected function page($page, $dump = false) 16601f06932SAndreas Gohr { 16701f06932SAndreas Gohr $indexer = new Indexer(); 16801f06932SAndreas Gohr $pages = $indexer->getPages(); 16901f06932SAndreas Gohr $pos = array_search(cleanID($page), $pages); 17001f06932SAndreas Gohr 17101f06932SAndreas Gohr if ($pos === false) { 17201f06932SAndreas Gohr $this->error('Page not found'); 17301f06932SAndreas Gohr return; 17401f06932SAndreas Gohr } 17501f06932SAndreas Gohr 17601f06932SAndreas Gohr $storage = $this->helper->getStorage(); 17701f06932SAndreas Gohr $chunks = $storage->getPageChunks($page, $pos * 100); 17801f06932SAndreas Gohr if ($chunks) { 17901f06932SAndreas Gohr $this->success('Found ' . count($chunks) . ' chunks'); 180dc355d57SAndreas Gohr if ($dump) { 181dc355d57SAndreas Gohr echo json_encode($chunks, JSON_PRETTY_PRINT); 182dc355d57SAndreas Gohr } 18301f06932SAndreas Gohr } else { 18401f06932SAndreas Gohr $this->error('No chunks found'); 18501f06932SAndreas Gohr } 18601f06932SAndreas Gohr } 18701f06932SAndreas Gohr 18801f06932SAndreas Gohr /** 189ad38c5fdSAndreas Gohr * Split the given page into chunks and print them 190ad38c5fdSAndreas Gohr * 191ad38c5fdSAndreas Gohr * @param string $page 192ad38c5fdSAndreas Gohr * @return void 193ad38c5fdSAndreas Gohr * @throws Exception 194ad38c5fdSAndreas Gohr */ 195ad38c5fdSAndreas Gohr protected function split($page) 196ad38c5fdSAndreas Gohr { 197ad38c5fdSAndreas Gohr $text = rawWiki($page); 198ad38c5fdSAndreas Gohr $chunks = $this->helper->getEmbeddings()->splitIntoChunks($text); 199ad38c5fdSAndreas Gohr foreach ($chunks as $chunk) { 200ad38c5fdSAndreas Gohr echo $chunk; 201ad38c5fdSAndreas Gohr echo "\n"; 202ad38c5fdSAndreas Gohr $this->colors->ptln('--------------------------------', Colors::C_LIGHTPURPLE); 203ad38c5fdSAndreas Gohr } 204ad38c5fdSAndreas Gohr $this->success('Split into ' . count($chunks) . ' chunks'); 205ad38c5fdSAndreas Gohr } 206ad38c5fdSAndreas Gohr 207ad38c5fdSAndreas Gohr /** 208c4584168SAndreas Gohr * Interactive Chat Session 209c4584168SAndreas Gohr * 210c4584168SAndreas Gohr * @return void 211c4584168SAndreas Gohr * @throws Exception 212c4584168SAndreas Gohr */ 213c4584168SAndreas Gohr protected function chat() 214c4584168SAndreas Gohr { 215*34a1c478SAndreas Gohr if($this->loglevel['debug']['enabled']) { 216*34a1c478SAndreas Gohr $this->helper->getChatModel()->setDebug(true); 217*34a1c478SAndreas Gohr } 218*34a1c478SAndreas Gohr 219c4584168SAndreas Gohr $history = []; 220c4584168SAndreas Gohr while ($q = $this->readLine('Your Question')) { 2216a18e0f4SAndreas Gohr $this->helper->getChatModel()->resetUsageStats(); 222f6ef2e50SAndreas Gohr $result = $this->helper->askChatQuestion($q, $history); 223f6ef2e50SAndreas Gohr $this->colors->ptln("Interpretation: {$result['question']}", Colors::C_LIGHTPURPLE); 224f6ef2e50SAndreas Gohr $history[] = [$result['question'], $result['answer']]; 225c4584168SAndreas Gohr $this->printAnswer($result); 226c4584168SAndreas Gohr } 227c4584168SAndreas Gohr } 228c4584168SAndreas Gohr 229c4584168SAndreas Gohr /** 230c4584168SAndreas Gohr * Handle a single, standalone question 231c4584168SAndreas Gohr * 232c4584168SAndreas Gohr * @param string $query 233c4584168SAndreas Gohr * @return void 234c4584168SAndreas Gohr * @throws Exception 235c4584168SAndreas Gohr */ 236c4584168SAndreas Gohr protected function ask($query) 237c4584168SAndreas Gohr { 238*34a1c478SAndreas Gohr if($this->loglevel['debug']['enabled']) { 239*34a1c478SAndreas Gohr $this->helper->getChatModel()->setDebug(true); 240*34a1c478SAndreas Gohr } 241*34a1c478SAndreas Gohr 2420337f47fSAndreas Gohr $result = $this->helper->askQuestion($query); 243c4584168SAndreas Gohr $this->printAnswer($result); 2447552f1aaSAndreas Gohr } 2457552f1aaSAndreas Gohr 246c4584168SAndreas Gohr /** 247c4584168SAndreas Gohr * Get the pages that are similar to the query 248c4584168SAndreas Gohr * 249c4584168SAndreas Gohr * @param string $query 250c4584168SAndreas Gohr * @return void 251c4584168SAndreas Gohr */ 2528817535bSAndreas Gohr protected function similar($query) 2538817535bSAndreas Gohr { 254e33a1d7aSAndreas Gohr $langlimit = $this->helper->getLanguageLimit(); 255e33a1d7aSAndreas Gohr if ($langlimit) { 256e33a1d7aSAndreas Gohr $this->info('Limiting results to {lang}', ['lang' => $langlimit]); 257e33a1d7aSAndreas Gohr } 258e33a1d7aSAndreas Gohr 259e33a1d7aSAndreas Gohr $sources = $this->helper->getEmbeddings()->getSimilarChunks($query, $langlimit); 260f6ef2e50SAndreas Gohr $this->printSources($sources); 2618817535bSAndreas Gohr } 2628817535bSAndreas Gohr 263c4584168SAndreas Gohr /** 2643379af09SAndreas Gohr * Run the maintenance tasks 2653379af09SAndreas Gohr * 2663379af09SAndreas Gohr * @return void 2673379af09SAndreas Gohr */ 2683379af09SAndreas Gohr protected function runMaintenance() 2693379af09SAndreas Gohr { 2703379af09SAndreas Gohr $start = time(); 2713379af09SAndreas Gohr $this->helper->getStorage()->runMaintenance(); 2723379af09SAndreas Gohr $this->notice('Peak memory used: {memory}', ['memory' => filesize_h(memory_get_peak_usage(true))]); 2733379af09SAndreas Gohr $this->notice('Spent time: {time}min', ['time' => round((time() - $start) / 60, 2)]); 274e75dc39fSAndreas Gohr 275e75dc39fSAndreas Gohr $data = $this->helper->getRunData(); 276e75dc39fSAndreas Gohr $data['maintenance ran at'] = time(); 277e75dc39fSAndreas Gohr $this->helper->setRunData($data); 2783379af09SAndreas Gohr } 2793379af09SAndreas Gohr 2803379af09SAndreas Gohr /** 281c4584168SAndreas Gohr * Recreate chunks and embeddings for all pages 282c4584168SAndreas Gohr * 283c4584168SAndreas Gohr * @return void 284c4584168SAndreas Gohr */ 2855284515dSAndreas Gohr protected function createEmbeddings($clear) 2868817535bSAndreas Gohr { 287d5c102b3SAndreas Gohr [$skipRE, $matchRE] = $this->getRegexps(); 288d5c102b3SAndreas Gohr 2893379af09SAndreas Gohr $start = time(); 290d5c102b3SAndreas Gohr $this->helper->getEmbeddings()->createNewIndex($skipRE, $matchRE, $clear); 291ad38c5fdSAndreas Gohr $this->notice('Peak memory used: {memory}', ['memory' => filesize_h(memory_get_peak_usage(true))]); 2923379af09SAndreas Gohr $this->notice('Spent time: {time}min', ['time' => round((time() - $start) / 60, 2)]); 293e75dc39fSAndreas Gohr 294e75dc39fSAndreas Gohr $data = $this->helper->getRunData(); 295e75dc39fSAndreas Gohr $data['embed ran at'] = time(); 296e75dc39fSAndreas Gohr $this->helper->setRunData($data); 2978817535bSAndreas Gohr } 2988817535bSAndreas Gohr 299c4584168SAndreas Gohr /** 3008c8b7ba6SAndreas Gohr * Dump TSV files for debugging 3018c8b7ba6SAndreas Gohr * 3028c8b7ba6SAndreas Gohr * @return void 3038c8b7ba6SAndreas Gohr */ 3048c8b7ba6SAndreas Gohr protected function tsv($vector, $meta) 3058c8b7ba6SAndreas Gohr { 3068c8b7ba6SAndreas Gohr 3078c8b7ba6SAndreas Gohr $storage = $this->helper->getStorage(); 3088c8b7ba6SAndreas Gohr $storage->dumpTSV($vector, $meta); 3098c8b7ba6SAndreas Gohr $this->success('written to ' . $vector . ' and ' . $meta); 3108c8b7ba6SAndreas Gohr } 3118c8b7ba6SAndreas Gohr 3128c8b7ba6SAndreas Gohr /** 31355392016SAndreas Gohr * Print the given detailed answer in a nice way 31455392016SAndreas Gohr * 31555392016SAndreas Gohr * @param array $answer 31655392016SAndreas Gohr * @return void 31755392016SAndreas Gohr */ 31855392016SAndreas Gohr protected function printAnswer($answer) 31955392016SAndreas Gohr { 32055392016SAndreas Gohr $this->colors->ptln($answer['answer'], Colors::C_LIGHTCYAN); 32155392016SAndreas Gohr echo "\n"; 322f6ef2e50SAndreas Gohr $this->printSources($answer['sources']); 32355392016SAndreas Gohr echo "\n"; 32455392016SAndreas Gohr $this->printUsage(); 32555392016SAndreas Gohr } 32655392016SAndreas Gohr 32755392016SAndreas Gohr /** 328f6ef2e50SAndreas Gohr * Print the given sources 329f6ef2e50SAndreas Gohr * 330f6ef2e50SAndreas Gohr * @param Chunk[] $sources 331f6ef2e50SAndreas Gohr * @return void 332f6ef2e50SAndreas Gohr */ 333f6ef2e50SAndreas Gohr protected function printSources($sources) 334f6ef2e50SAndreas Gohr { 335f6ef2e50SAndreas Gohr foreach ($sources as $source) { 336f6ef2e50SAndreas Gohr /** @var Chunk $source */ 3379b3d1b36SAndreas Gohr $this->colors->ptln( 3389b3d1b36SAndreas Gohr "\t" . $source->getPage() . ' ' . $source->getId() . ' (' . $source->getScore() . ')', 3399b3d1b36SAndreas Gohr Colors::C_LIGHTBLUE 3409b3d1b36SAndreas Gohr ); 341f6ef2e50SAndreas Gohr } 342f6ef2e50SAndreas Gohr } 343f6ef2e50SAndreas Gohr 344f6ef2e50SAndreas Gohr /** 34555392016SAndreas Gohr * Print the usage statistics for OpenAI 34655392016SAndreas Gohr * 34755392016SAndreas Gohr * @return void 34855392016SAndreas Gohr */ 349f6ef2e50SAndreas Gohr protected function printUsage() 350f6ef2e50SAndreas Gohr { 35155392016SAndreas Gohr $this->info( 352f6ef2e50SAndreas Gohr 'Made {requests} requests in {time}s to Model. Used {tokens} tokens for about ${cost}.', 3536a18e0f4SAndreas Gohr $this->helper->getChatModel()->getUsageStats() 35455392016SAndreas Gohr ); 35555392016SAndreas Gohr } 35655392016SAndreas Gohr 35755392016SAndreas Gohr /** 358c4584168SAndreas Gohr * Interactively ask for a value from the user 359c4584168SAndreas Gohr * 360c4584168SAndreas Gohr * @param string $prompt 361c4584168SAndreas Gohr * @return string 362c4584168SAndreas Gohr */ 363c4584168SAndreas Gohr protected function readLine($prompt) 364c4584168SAndreas Gohr { 365c4584168SAndreas Gohr $value = ''; 3668817535bSAndreas Gohr 367c4584168SAndreas Gohr while ($value === '') { 368c4584168SAndreas Gohr echo $prompt; 369c4584168SAndreas Gohr echo ': '; 370c4584168SAndreas Gohr 371c4584168SAndreas Gohr $fh = fopen('php://stdin', 'r'); 372c4584168SAndreas Gohr $value = trim(fgets($fh)); 373c4584168SAndreas Gohr fclose($fh); 374c4584168SAndreas Gohr } 375c4584168SAndreas Gohr 376c4584168SAndreas Gohr return $value; 377c4584168SAndreas Gohr } 378d5c102b3SAndreas Gohr 379d5c102b3SAndreas Gohr /** 380d5c102b3SAndreas Gohr * Read the skip and match regex from the config 381d5c102b3SAndreas Gohr * 382d5c102b3SAndreas Gohr * Ensures the regular expressions are valid 383d5c102b3SAndreas Gohr * 384d5c102b3SAndreas Gohr * @return string[] [$skipRE, $matchRE] 385d5c102b3SAndreas Gohr */ 386d5c102b3SAndreas Gohr protected function getRegexps() 387d5c102b3SAndreas Gohr { 388d5c102b3SAndreas Gohr $skip = $this->getConf('skipRegex'); 389d5c102b3SAndreas Gohr $skipRE = ''; 390d5c102b3SAndreas Gohr $match = $this->getConf('matchRegex'); 391d5c102b3SAndreas Gohr $matchRE = ''; 392d5c102b3SAndreas Gohr 393d5c102b3SAndreas Gohr if ($skip) { 394d5c102b3SAndreas Gohr $skipRE = '/' . $skip . '/'; 39549a7d3ccSsplitbrain if (@preg_match($skipRE, '') === false) { 396d5c102b3SAndreas Gohr $this->error(preg_last_error_msg()); 397d5c102b3SAndreas Gohr $this->error('Invalid regular expression in $conf[\'skipRegex\']. Ignored.'); 398d5c102b3SAndreas Gohr $skipRE = ''; 399d5c102b3SAndreas Gohr } else { 400d5c102b3SAndreas Gohr $this->success('Skipping pages matching ' . $skipRE); 401d5c102b3SAndreas Gohr } 402d5c102b3SAndreas Gohr } 403d5c102b3SAndreas Gohr 404d5c102b3SAndreas Gohr if ($match) { 405d5c102b3SAndreas Gohr $matchRE = '/' . $match . '/'; 40649a7d3ccSsplitbrain if (@preg_match($matchRE, '') === false) { 407d5c102b3SAndreas Gohr $this->error(preg_last_error_msg()); 408d5c102b3SAndreas Gohr $this->error('Invalid regular expression in $conf[\'matchRegex\']. Ignored.'); 409d5c102b3SAndreas Gohr $matchRE = ''; 410d5c102b3SAndreas Gohr } else { 411d5c102b3SAndreas Gohr $this->success('Only indexing pages matching ' . $matchRE); 412d5c102b3SAndreas Gohr } 413d5c102b3SAndreas Gohr } 414d5c102b3SAndreas Gohr return [$skipRE, $matchRE]; 415d5c102b3SAndreas Gohr } 4168817535bSAndreas Gohr} 417