xref: /plugin/aichat/cli.php (revision c4584168c6c9af22a69973c17af0a7ff5f7fb802)
18817535bSAndreas Gohr<?php
28817535bSAndreas Gohr
38817535bSAndreas Gohruse dokuwiki\plugin\aichat\Embeddings;
48817535bSAndreas Gohruse dokuwiki\plugin\aichat\OpenAI;
58817535bSAndreas Gohruse Hexogen\KDTree\FSKDTree;
68817535bSAndreas Gohruse Hexogen\KDTree\FSTreePersister;
78817535bSAndreas Gohruse Hexogen\KDTree\Item;
88817535bSAndreas Gohruse Hexogen\KDTree\ItemFactory;
98817535bSAndreas Gohruse Hexogen\KDTree\ItemList;
108817535bSAndreas Gohruse Hexogen\KDTree\KDTree;
118817535bSAndreas Gohruse Hexogen\KDTree\NearestSearch;
128817535bSAndreas Gohruse Hexogen\KDTree\Point;
13*c4584168SAndreas Gohruse splitbrain\phpcli\Colors;
148817535bSAndreas Gohruse splitbrain\phpcli\Options;
158817535bSAndreas Gohr
168817535bSAndreas Gohrrequire_once __DIR__ . '/vendor/autoload.php';
178817535bSAndreas Gohr
188817535bSAndreas Gohr/**
198817535bSAndreas Gohr * DokuWiki Plugin aichat (CLI Component)
208817535bSAndreas Gohr *
218817535bSAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
228817535bSAndreas Gohr * @author  Andreas Gohr <gohr@cosmocode.de>
238817535bSAndreas Gohr */
248817535bSAndreas Gohrclass cli_plugin_aichat extends \dokuwiki\Extension\CLIPlugin
258817535bSAndreas Gohr{
268817535bSAndreas Gohr
278817535bSAndreas Gohr    /** @inheritDoc */
288817535bSAndreas Gohr    protected function setup(Options $options)
298817535bSAndreas Gohr    {
308817535bSAndreas Gohr        $options->setHelp('Manage the AI chatbot data');
318817535bSAndreas Gohr
328817535bSAndreas Gohr        $options->registerCommand('embed', 'Create embeddings for all pages');
338817535bSAndreas Gohr
348817535bSAndreas Gohr        $options->registerCommand('similar', 'Search for similar pages');
358817535bSAndreas Gohr        $options->registerArgument('query', 'Look up chunks similar to this query', true, 'similar');
368817535bSAndreas Gohr
378817535bSAndreas Gohr        $options->registerCommand('ask', 'Ask a question');
388817535bSAndreas Gohr        $options->registerArgument('question', 'The question to ask', true, 'ask');
39*c4584168SAndreas Gohr
40*c4584168SAndreas Gohr        $options->registerCommand('chat', 'Start an interactive chat session');
418817535bSAndreas Gohr    }
428817535bSAndreas Gohr
438817535bSAndreas Gohr    /** @inheritDoc */
448817535bSAndreas Gohr    protected function main(Options $options)
458817535bSAndreas Gohr    {
468817535bSAndreas Gohr        switch ($options->getCmd()) {
478817535bSAndreas Gohr
488817535bSAndreas Gohr            case 'embed':
498817535bSAndreas Gohr                $this->createEmbeddings();
508817535bSAndreas Gohr                break;
518817535bSAndreas Gohr            case 'similar':
528817535bSAndreas Gohr                $this->similar($options->getArgs()[0]);
538817535bSAndreas Gohr                break;
547552f1aaSAndreas Gohr            case 'ask':
557552f1aaSAndreas Gohr                $this->ask($options->getArgs()[0]);
567552f1aaSAndreas Gohr                break;
57*c4584168SAndreas Gohr            case 'chat':
58*c4584168SAndreas Gohr                $this->chat();
59*c4584168SAndreas Gohr                break;
608817535bSAndreas Gohr            default:
618817535bSAndreas Gohr                echo $options->help();
628817535bSAndreas Gohr        }
638817535bSAndreas Gohr    }
648817535bSAndreas Gohr
65*c4584168SAndreas Gohr    /**
66*c4584168SAndreas Gohr     * Interactive Chat Session
67*c4584168SAndreas Gohr     *
68*c4584168SAndreas Gohr     * @return void
69*c4584168SAndreas Gohr     * @throws Exception
70*c4584168SAndreas Gohr     */
71*c4584168SAndreas Gohr    protected function chat()
72*c4584168SAndreas Gohr    {
73*c4584168SAndreas Gohr        /** @var helper_plugin_aichat_prompt $prompt */
74*c4584168SAndreas Gohr        $prompt = plugin_load('helper', 'aichat_prompt');
75*c4584168SAndreas Gohr
76*c4584168SAndreas Gohr        $history = [];
77*c4584168SAndreas Gohr        while ($q = $this->readLine('Your Question')) {
78*c4584168SAndreas Gohr            if ($history) {
79*c4584168SAndreas Gohr                $question = $prompt->rephraseChatQuestion($q, $history);
80*c4584168SAndreas Gohr                $this->colors->ptln("Interpretation: $question", Colors::C_LIGHTPURPLE);
81*c4584168SAndreas Gohr            } else {
82*c4584168SAndreas Gohr                $question = $q;
83*c4584168SAndreas Gohr            }
84*c4584168SAndreas Gohr            $result = $prompt->askQuestion($question);
85*c4584168SAndreas Gohr            $history[] = [$q, $result['answer']];
86*c4584168SAndreas Gohr            $this->printAnswer($result);
87*c4584168SAndreas Gohr        }
88*c4584168SAndreas Gohr
89*c4584168SAndreas Gohr    }
90*c4584168SAndreas Gohr
91*c4584168SAndreas Gohr    /**
92*c4584168SAndreas Gohr     * Print the given detailed answer in a nice way
93*c4584168SAndreas Gohr     *
94*c4584168SAndreas Gohr     * @param array $answer
95*c4584168SAndreas Gohr     * @return void
96*c4584168SAndreas Gohr     */
97*c4584168SAndreas Gohr    protected function printAnswer($answer)
98*c4584168SAndreas Gohr    {
99*c4584168SAndreas Gohr        $this->colors->ptln($answer['answer'], Colors::C_LIGHTCYAN);
100*c4584168SAndreas Gohr        echo "\n";
101*c4584168SAndreas Gohr        foreach ($answer['sources'] as $source) {
102*c4584168SAndreas Gohr            $this->colors->ptln("\t".$source['meta']['pageid'], Colors::C_LIGHTBLUE);
103*c4584168SAndreas Gohr        }
104*c4584168SAndreas Gohr        echo "\n";
105*c4584168SAndreas Gohr    }
106*c4584168SAndreas Gohr
107*c4584168SAndreas Gohr    /**
108*c4584168SAndreas Gohr     * Handle a single, standalone question
109*c4584168SAndreas Gohr     *
110*c4584168SAndreas Gohr     * @param string $query
111*c4584168SAndreas Gohr     * @return void
112*c4584168SAndreas Gohr     * @throws Exception
113*c4584168SAndreas Gohr     */
114*c4584168SAndreas Gohr    protected function ask($query)
115*c4584168SAndreas Gohr    {
1167552f1aaSAndreas Gohr        /** @var helper_plugin_aichat_prompt $prompt */
1177552f1aaSAndreas Gohr        $prompt = plugin_load('helper', 'aichat_prompt');
1187552f1aaSAndreas Gohr
1197552f1aaSAndreas Gohr        $result = $prompt->askQuestion($query);
120*c4584168SAndreas Gohr        $this->printAnswer($result);
1217552f1aaSAndreas Gohr    }
1227552f1aaSAndreas Gohr
123*c4584168SAndreas Gohr    /**
124*c4584168SAndreas Gohr     * Get the pages that are similar to the query
125*c4584168SAndreas Gohr     *
126*c4584168SAndreas Gohr     * @param string $query
127*c4584168SAndreas Gohr     * @return void
128*c4584168SAndreas Gohr     */
1298817535bSAndreas Gohr    protected function similar($query)
1308817535bSAndreas Gohr    {
1318817535bSAndreas Gohr        $openAI = new OpenAI($this->getConf('openaikey'), $this->getConf('openaiorg'));
1328817535bSAndreas Gohr        $embedding = new Embeddings($openAI, $this);
1338817535bSAndreas Gohr
134*c4584168SAndreas Gohr        $sources = $embedding->getSimilarChunks($query);
135*c4584168SAndreas Gohr        foreach ($sources as $source) {
136*c4584168SAndreas Gohr            $this->colors->ptln($source['meta']['pageid'], Colors::C_LIGHTBLUE);
137*c4584168SAndreas Gohr        }
1388817535bSAndreas Gohr    }
1398817535bSAndreas Gohr
140*c4584168SAndreas Gohr    /**
141*c4584168SAndreas Gohr     * Recreate chunks and embeddings for all pages
142*c4584168SAndreas Gohr     *
143*c4584168SAndreas Gohr     * @return void
144*c4584168SAndreas Gohr     */
1458817535bSAndreas Gohr    protected function createEmbeddings()
1468817535bSAndreas Gohr    {
1478817535bSAndreas Gohr        $openAI = new OpenAI($this->getConf('openaikey'), $this->getConf('openaiorg'));
1488817535bSAndreas Gohr
1498817535bSAndreas Gohr        $embeddings = new Embeddings($openAI, $this);
1508817535bSAndreas Gohr        $embeddings->createNewIndex();
1518817535bSAndreas Gohr    }
1528817535bSAndreas Gohr
153*c4584168SAndreas Gohr    /**
154*c4584168SAndreas Gohr     * Interactively ask for a value from the user
155*c4584168SAndreas Gohr     *
156*c4584168SAndreas Gohr     * @param string $prompt
157*c4584168SAndreas Gohr     * @return string
158*c4584168SAndreas Gohr     */
159*c4584168SAndreas Gohr    protected function readLine($prompt)
160*c4584168SAndreas Gohr    {
161*c4584168SAndreas Gohr        $value = '';
1628817535bSAndreas Gohr
163*c4584168SAndreas Gohr        while ($value === '') {
164*c4584168SAndreas Gohr            echo $prompt;
165*c4584168SAndreas Gohr            echo ': ';
166*c4584168SAndreas Gohr
167*c4584168SAndreas Gohr            $fh = fopen('php://stdin', 'r');
168*c4584168SAndreas Gohr            $value = trim(fgets($fh));
169*c4584168SAndreas Gohr            fclose($fh);
170*c4584168SAndreas Gohr        }
171*c4584168SAndreas Gohr
172*c4584168SAndreas Gohr        return $value;
173*c4584168SAndreas Gohr    }
1748817535bSAndreas Gohr}
1758817535bSAndreas Gohr
176