xref: /plugin/aichat/cli.php (revision ad38c5fd62a65d04772bdd994d54a93483f88639)
18817535bSAndreas Gohr<?php
28817535bSAndreas Gohr
3c4584168SAndreas Gohruse splitbrain\phpcli\Colors;
48817535bSAndreas Gohruse splitbrain\phpcli\Options;
58817535bSAndreas Gohr
68817535bSAndreas Gohr
78817535bSAndreas Gohr/**
88817535bSAndreas Gohr * DokuWiki Plugin aichat (CLI Component)
98817535bSAndreas Gohr *
108817535bSAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
118817535bSAndreas Gohr * @author  Andreas Gohr <gohr@cosmocode.de>
128817535bSAndreas Gohr */
138817535bSAndreas Gohrclass cli_plugin_aichat extends \dokuwiki\Extension\CLIPlugin
148817535bSAndreas Gohr{
150337f47fSAndreas Gohr    /** @var helper_plugin_aichat */
160337f47fSAndreas Gohr    protected $helper;
170337f47fSAndreas Gohr
180337f47fSAndreas Gohr    public function __construct($autocatch = true)
190337f47fSAndreas Gohr    {
200337f47fSAndreas Gohr        parent::__construct($autocatch);
210337f47fSAndreas Gohr        $this->helper = plugin_load('helper', 'aichat');
222ecc089aSAndreas Gohr        $this->helper->getEmbeddings()->setLogger($this);
230337f47fSAndreas Gohr    }
240337f47fSAndreas Gohr
258817535bSAndreas Gohr
268817535bSAndreas Gohr    /** @inheritDoc */
278817535bSAndreas Gohr    protected function setup(Options $options)
288817535bSAndreas Gohr    {
299da5f0dfSAndreas Gohr        $options->setHelp('Manage and query the AI chatbot data');
308817535bSAndreas Gohr
318817535bSAndreas Gohr        $options->registerCommand('embed', 'Create embeddings for all pages');
328817535bSAndreas Gohr
338817535bSAndreas Gohr        $options->registerCommand('similar', 'Search for similar pages');
348817535bSAndreas Gohr        $options->registerArgument('query', 'Look up chunks similar to this query', true, 'similar');
358817535bSAndreas Gohr
368817535bSAndreas Gohr        $options->registerCommand('ask', 'Ask a question');
378817535bSAndreas Gohr        $options->registerArgument('question', 'The question to ask', true, 'ask');
38c4584168SAndreas Gohr
39c4584168SAndreas Gohr        $options->registerCommand('chat', 'Start an interactive chat session');
40*ad38c5fdSAndreas Gohr
41*ad38c5fdSAndreas Gohr        $options->registerCommand('split', 'Split a page into chunks (for debugging)');
42*ad38c5fdSAndreas Gohr        $options->registerArgument('page', 'The page to split', true, 'split');
438817535bSAndreas Gohr    }
448817535bSAndreas Gohr
458817535bSAndreas Gohr    /** @inheritDoc */
468817535bSAndreas Gohr    protected function main(Options $options)
478817535bSAndreas Gohr    {
488817535bSAndreas Gohr        switch ($options->getCmd()) {
498817535bSAndreas Gohr
508817535bSAndreas Gohr            case 'embed':
518817535bSAndreas Gohr                $this->createEmbeddings();
528817535bSAndreas Gohr                break;
538817535bSAndreas Gohr            case 'similar':
548817535bSAndreas Gohr                $this->similar($options->getArgs()[0]);
558817535bSAndreas Gohr                break;
567552f1aaSAndreas Gohr            case 'ask':
577552f1aaSAndreas Gohr                $this->ask($options->getArgs()[0]);
587552f1aaSAndreas Gohr                break;
59c4584168SAndreas Gohr            case 'chat':
60c4584168SAndreas Gohr                $this->chat();
61c4584168SAndreas Gohr                break;
62*ad38c5fdSAndreas Gohr            case 'split':
63*ad38c5fdSAndreas Gohr                $this->split($options->getArgs()[0]);
64*ad38c5fdSAndreas Gohr                break;
658817535bSAndreas Gohr            default:
668817535bSAndreas Gohr                echo $options->help();
678817535bSAndreas Gohr        }
688817535bSAndreas Gohr    }
698817535bSAndreas Gohr
70c4584168SAndreas Gohr    /**
71*ad38c5fdSAndreas Gohr     * Split the given page into chunks and print them
72*ad38c5fdSAndreas Gohr     *
73*ad38c5fdSAndreas Gohr     * @param string $page
74*ad38c5fdSAndreas Gohr     * @return void
75*ad38c5fdSAndreas Gohr     * @throws Exception
76*ad38c5fdSAndreas Gohr     */
77*ad38c5fdSAndreas Gohr    protected function split($page)
78*ad38c5fdSAndreas Gohr    {
79*ad38c5fdSAndreas Gohr        $text = rawWiki($page);
80*ad38c5fdSAndreas Gohr        $chunks = $this->helper->getEmbeddings()->splitIntoChunks($text);
81*ad38c5fdSAndreas Gohr        foreach ($chunks as $chunk) {
82*ad38c5fdSAndreas Gohr            echo $chunk;
83*ad38c5fdSAndreas Gohr            echo "\n";
84*ad38c5fdSAndreas Gohr            $this->colors->ptln('--------------------------------', Colors::C_LIGHTPURPLE);
85*ad38c5fdSAndreas Gohr        }
86*ad38c5fdSAndreas Gohr        $this->success('Split into ' . count($chunks) . ' chunks');
87*ad38c5fdSAndreas Gohr    }
88*ad38c5fdSAndreas Gohr
89*ad38c5fdSAndreas Gohr    /**
90c4584168SAndreas Gohr     * Interactive Chat Session
91c4584168SAndreas Gohr     *
92c4584168SAndreas Gohr     * @return void
93c4584168SAndreas Gohr     * @throws Exception
94c4584168SAndreas Gohr     */
95c4584168SAndreas Gohr    protected function chat()
96c4584168SAndreas Gohr    {
97c4584168SAndreas Gohr        $history = [];
98c4584168SAndreas Gohr        while ($q = $this->readLine('Your Question')) {
99c4584168SAndreas Gohr            if ($history) {
1000337f47fSAndreas Gohr                $question = $this->helper->rephraseChatQuestion($q, $history);
101c4584168SAndreas Gohr                $this->colors->ptln("Interpretation: $question", Colors::C_LIGHTPURPLE);
102c4584168SAndreas Gohr            } else {
103c4584168SAndreas Gohr                $question = $q;
104c4584168SAndreas Gohr            }
1050337f47fSAndreas Gohr            $result = $this->helper->askQuestion($question);
106c4584168SAndreas Gohr            $history[] = [$q, $result['answer']];
107c4584168SAndreas Gohr            $this->printAnswer($result);
108c4584168SAndreas Gohr        }
109c4584168SAndreas Gohr    }
110c4584168SAndreas Gohr
111c4584168SAndreas Gohr    /**
112c4584168SAndreas Gohr     * Print the given detailed answer in a nice way
113c4584168SAndreas Gohr     *
114c4584168SAndreas Gohr     * @param array $answer
115c4584168SAndreas Gohr     * @return void
116c4584168SAndreas Gohr     */
117c4584168SAndreas Gohr    protected function printAnswer($answer)
118c4584168SAndreas Gohr    {
119c4584168SAndreas Gohr        $this->colors->ptln($answer['answer'], Colors::C_LIGHTCYAN);
120c4584168SAndreas Gohr        echo "\n";
121c4584168SAndreas Gohr        foreach ($answer['sources'] as $source) {
122c4584168SAndreas Gohr            $this->colors->ptln("\t" . $source['meta']['pageid'], Colors::C_LIGHTBLUE);
123c4584168SAndreas Gohr        }
124c4584168SAndreas Gohr        echo "\n";
125c4584168SAndreas Gohr    }
126c4584168SAndreas Gohr
127c4584168SAndreas Gohr    /**
128c4584168SAndreas Gohr     * Handle a single, standalone question
129c4584168SAndreas Gohr     *
130c4584168SAndreas Gohr     * @param string $query
131c4584168SAndreas Gohr     * @return void
132c4584168SAndreas Gohr     * @throws Exception
133c4584168SAndreas Gohr     */
134c4584168SAndreas Gohr    protected function ask($query)
135c4584168SAndreas Gohr    {
1360337f47fSAndreas Gohr        $result = $this->helper->askQuestion($query);
137c4584168SAndreas Gohr        $this->printAnswer($result);
1387552f1aaSAndreas Gohr    }
1397552f1aaSAndreas Gohr
140c4584168SAndreas Gohr    /**
141c4584168SAndreas Gohr     * Get the pages that are similar to the query
142c4584168SAndreas Gohr     *
143c4584168SAndreas Gohr     * @param string $query
144c4584168SAndreas Gohr     * @return void
145c4584168SAndreas Gohr     */
1468817535bSAndreas Gohr    protected function similar($query)
1478817535bSAndreas Gohr    {
1480337f47fSAndreas Gohr        $sources = $this->helper->getEmbeddings()->getSimilarChunks($query);
149c4584168SAndreas Gohr        foreach ($sources as $source) {
150c4584168SAndreas Gohr            $this->colors->ptln($source['meta']['pageid'], Colors::C_LIGHTBLUE);
151c4584168SAndreas Gohr        }
1528817535bSAndreas Gohr    }
1538817535bSAndreas Gohr
154c4584168SAndreas Gohr    /**
155c4584168SAndreas Gohr     * Recreate chunks and embeddings for all pages
156c4584168SAndreas Gohr     *
157c4584168SAndreas Gohr     * @return void
158*ad38c5fdSAndreas Gohr     * @todo make skip regex configurable
159c4584168SAndreas Gohr     */
1608817535bSAndreas Gohr    protected function createEmbeddings()
1618817535bSAndreas Gohr    {
162*ad38c5fdSAndreas Gohr        ini_set('memory_limit', -1); // we may need a lot of memory here
163*ad38c5fdSAndreas Gohr        $this->helper->getEmbeddings()->createNewIndex('/(^|:)(playground|sandbox)(:|$)/');
164*ad38c5fdSAndreas Gohr        $this->notice('Peak memory used: {memory}', ['memory' => filesize_h(memory_get_peak_usage(true))]);
1658817535bSAndreas Gohr    }
1668817535bSAndreas Gohr
167c4584168SAndreas Gohr    /**
168c4584168SAndreas Gohr     * Interactively ask for a value from the user
169c4584168SAndreas Gohr     *
170c4584168SAndreas Gohr     * @param string $prompt
171c4584168SAndreas Gohr     * @return string
172c4584168SAndreas Gohr     */
173c4584168SAndreas Gohr    protected function readLine($prompt)
174c4584168SAndreas Gohr    {
175c4584168SAndreas Gohr        $value = '';
1768817535bSAndreas Gohr
177c4584168SAndreas Gohr        while ($value === '') {
178c4584168SAndreas Gohr            echo $prompt;
179c4584168SAndreas Gohr            echo ': ';
180c4584168SAndreas Gohr
181c4584168SAndreas Gohr            $fh = fopen('php://stdin', 'r');
182c4584168SAndreas Gohr            $value = trim(fgets($fh));
183c4584168SAndreas Gohr            fclose($fh);
184c4584168SAndreas Gohr        }
185c4584168SAndreas Gohr
186c4584168SAndreas Gohr        return $value;
187c4584168SAndreas Gohr    }
1888817535bSAndreas Gohr}
1898817535bSAndreas Gohr
190