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