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