xref: /plugin/aichat/cli.php (revision 7ee8b02d54a468f0b7f9bba54177ef52db1e95de)
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