xref: /plugin/aichat/helper.php (revision 51aa8517a15244890eb0132c8019c9857c046a12)
10337f47fSAndreas Gohr<?php
20337f47fSAndreas Gohr
33379af09SAndreas Gohruse dokuwiki\Extension\CLIPlugin;
45e6dd16eSAndreas Gohruse dokuwiki\Extension\Plugin;
5e33a1d7aSAndreas Gohruse dokuwiki\plugin\aichat\AIChat;
6f6ef2e50SAndreas Gohruse dokuwiki\plugin\aichat\Chunk;
70337f47fSAndreas Gohruse dokuwiki\plugin\aichat\Embeddings;
8294a9eafSAndreas Gohruse dokuwiki\plugin\aichat\Model\ChatInterface;
9294a9eafSAndreas Gohruse dokuwiki\plugin\aichat\Model\EmbeddingInterface;
1001f06932SAndreas Gohruse dokuwiki\plugin\aichat\Storage\AbstractStorage;
110337f47fSAndreas Gohr
120337f47fSAndreas Gohr/**
130337f47fSAndreas Gohr * DokuWiki Plugin aichat (Helper Component)
140337f47fSAndreas Gohr *
150337f47fSAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
160337f47fSAndreas Gohr * @author  Andreas Gohr <gohr@cosmocode.de>
170337f47fSAndreas Gohr */
187ebc7895Ssplitbrainclass helper_plugin_aichat extends Plugin
190337f47fSAndreas Gohr{
203379af09SAndreas Gohr    /** @var CLIPlugin $logger */
213379af09SAndreas Gohr    protected $logger;
22294a9eafSAndreas Gohr    /** @var ChatInterface */
236a18e0f4SAndreas Gohr    protected $chatModel;
24*51aa8517SAndreas Gohr    /** @var ChatInterface */
25*51aa8517SAndreas Gohr    protected $rephraseModel;
26294a9eafSAndreas Gohr    /** @var EmbeddingInterface */
276a18e0f4SAndreas Gohr    protected $embedModel;
280337f47fSAndreas Gohr    /** @var Embeddings */
290337f47fSAndreas Gohr    protected $embeddings;
3001f06932SAndreas Gohr    /** @var AbstractStorage */
3101f06932SAndreas Gohr    protected $storage;
320337f47fSAndreas Gohr
33e75dc39fSAndreas Gohr    /** @var array where to store meta data on the last run */
34e75dc39fSAndreas Gohr    protected $runDataFile;
35e75dc39fSAndreas Gohr
36*51aa8517SAndreas Gohr
370337f47fSAndreas Gohr    /**
38f8d5ae01SAndreas Gohr     * Constructor. Initializes vendor autoloader
39f8d5ae01SAndreas Gohr     */
40f8d5ae01SAndreas Gohr    public function __construct()
41f8d5ae01SAndreas Gohr    {
42e75dc39fSAndreas Gohr        require_once __DIR__ . '/vendor/autoload.php'; // FIXME obsolete from Kaos onwards
43e75dc39fSAndreas Gohr        global $conf;
44e75dc39fSAndreas Gohr        $this->runDataFile = $conf['metadir'] . '/aichat__run.json';
45d02b7935SAndreas Gohr        $this->loadConfig();
46f8d5ae01SAndreas Gohr    }
47f8d5ae01SAndreas Gohr
48f8d5ae01SAndreas Gohr    /**
493379af09SAndreas Gohr     * Use the given CLI plugin for logging
503379af09SAndreas Gohr     *
513379af09SAndreas Gohr     * @param CLIPlugin $logger
523379af09SAndreas Gohr     * @return void
533379af09SAndreas Gohr     */
548285fff9SAndreas Gohr    public function setLogger($logger)
558285fff9SAndreas Gohr    {
563379af09SAndreas Gohr        $this->logger = $logger;
573379af09SAndreas Gohr    }
583379af09SAndreas Gohr
593379af09SAndreas Gohr    /**
60c4127b8eSAndreas Gohr     * Check if the current user is allowed to use the plugin (if it has been restricted)
61c4127b8eSAndreas Gohr     *
62c4127b8eSAndreas Gohr     * @return bool
63c4127b8eSAndreas Gohr     */
64c4127b8eSAndreas Gohr    public function userMayAccess()
65c4127b8eSAndreas Gohr    {
66c4127b8eSAndreas Gohr        global $auth;
67c4127b8eSAndreas Gohr        global $USERINFO;
68c4127b8eSAndreas Gohr        global $INPUT;
69c4127b8eSAndreas Gohr
70c4127b8eSAndreas Gohr        if (!$auth) return true;
71c4127b8eSAndreas Gohr        if (!$this->getConf('restrict')) return true;
72c4127b8eSAndreas Gohr        if (!isset($USERINFO)) return false;
73c4127b8eSAndreas Gohr
74c4127b8eSAndreas Gohr        return auth_isMember($this->getConf('restrict'), $INPUT->server->str('REMOTE_USER'), $USERINFO['grps']);
75c4127b8eSAndreas Gohr    }
76c4127b8eSAndreas Gohr
77c4127b8eSAndreas Gohr    /**
786a18e0f4SAndreas Gohr     * Access the Chat Model
790337f47fSAndreas Gohr     *
80294a9eafSAndreas Gohr     * @return ChatInterface
810337f47fSAndreas Gohr     */
826a18e0f4SAndreas Gohr    public function getChatModel()
830337f47fSAndreas Gohr    {
84294a9eafSAndreas Gohr        if ($this->chatModel instanceof ChatInterface) {
856a18e0f4SAndreas Gohr            return $this->chatModel;
866a18e0f4SAndreas Gohr        }
876a18e0f4SAndreas Gohr
88dce0dee5SAndreas Gohr        [$namespace, $name] = sexplode(' ', $this->getConf('chatmodel'), 2);
89dce0dee5SAndreas Gohr        $class = '\\dokuwiki\\plugin\\aichat\\Model\\' . $namespace . '\\ChatModel';
90d02b7935SAndreas Gohr
919f6b34c4SAndreas Gohr        if (!class_exists($class)) {
92dce0dee5SAndreas Gohr            throw new \RuntimeException('No ChatModel found for ' . $namespace);
939f6b34c4SAndreas Gohr        }
94d02b7935SAndreas Gohr
95dce0dee5SAndreas Gohr        $this->chatModel = new $class($name, $this->conf);
966a18e0f4SAndreas Gohr        return $this->chatModel;
979f6b34c4SAndreas Gohr    }
989f6b34c4SAndreas Gohr
996a18e0f4SAndreas Gohr    /**
100*51aa8517SAndreas Gohr     * @return ChatInterface
101*51aa8517SAndreas Gohr     */
102*51aa8517SAndreas Gohr    public function getRephraseModel()
103*51aa8517SAndreas Gohr    {
104*51aa8517SAndreas Gohr        if ($this->rephraseModel instanceof ChatInterface) {
105*51aa8517SAndreas Gohr            return $this->rephraseModel;
106*51aa8517SAndreas Gohr        }
107*51aa8517SAndreas Gohr
108*51aa8517SAndreas Gohr        [$namespace, $name] = sexplode(' ', $this->getConf('rephrasemodel'), 2);
109*51aa8517SAndreas Gohr        $class = '\\dokuwiki\\plugin\\aichat\\Model\\' . $namespace . '\\ChatModel';
110*51aa8517SAndreas Gohr
111*51aa8517SAndreas Gohr        if (!class_exists($class)) {
112*51aa8517SAndreas Gohr            throw new \RuntimeException('No ChatModel found for ' . $namespace);
113*51aa8517SAndreas Gohr        }
114*51aa8517SAndreas Gohr
115*51aa8517SAndreas Gohr        $this->rephraseModel = new $class($name, $this->conf);
116*51aa8517SAndreas Gohr        return $this->rephraseModel;
117*51aa8517SAndreas Gohr    }
118*51aa8517SAndreas Gohr
119*51aa8517SAndreas Gohr    /**
1206a18e0f4SAndreas Gohr     * Access the Embedding Model
1216a18e0f4SAndreas Gohr     *
122294a9eafSAndreas Gohr     * @return EmbeddingInterface
1236a18e0f4SAndreas Gohr     */
1246a18e0f4SAndreas Gohr    public function getEmbedModel()
1256a18e0f4SAndreas Gohr    {
126294a9eafSAndreas Gohr        if ($this->embedModel instanceof EmbeddingInterface) {
1276a18e0f4SAndreas Gohr            return $this->embedModel;
1280337f47fSAndreas Gohr        }
1290337f47fSAndreas Gohr
130dce0dee5SAndreas Gohr        [$namespace, $name] = sexplode(' ', $this->getConf('embedmodel'), 2);
131dce0dee5SAndreas Gohr        $class = '\\dokuwiki\\plugin\\aichat\\Model\\' . $namespace . '\\EmbeddingModel';
1326a18e0f4SAndreas Gohr
133dce0dee5SAndreas Gohr        if (!class_exists($class)) {
134dce0dee5SAndreas Gohr            throw new \RuntimeException('No EmbeddingModel found for ' . $namespace);
135dce0dee5SAndreas Gohr        }
136dce0dee5SAndreas Gohr
137dce0dee5SAndreas Gohr        $this->embedModel = new $class($name, $this->conf);
1386a18e0f4SAndreas Gohr        return $this->embedModel;
1396a18e0f4SAndreas Gohr    }
1406a18e0f4SAndreas Gohr
1416a18e0f4SAndreas Gohr
1420337f47fSAndreas Gohr    /**
1430337f47fSAndreas Gohr     * Access the Embeddings interface
1440337f47fSAndreas Gohr     *
1450337f47fSAndreas Gohr     * @return Embeddings
1460337f47fSAndreas Gohr     */
1470337f47fSAndreas Gohr    public function getEmbeddings()
1480337f47fSAndreas Gohr    {
1496a18e0f4SAndreas Gohr        if ($this->embeddings instanceof Embeddings) {
1506a18e0f4SAndreas Gohr            return $this->embeddings;
1516a18e0f4SAndreas Gohr        }
1526a18e0f4SAndreas Gohr
15334a1c478SAndreas Gohr        $this->embeddings = new Embeddings(
15434a1c478SAndreas Gohr            $this->getChatModel(),
15534a1c478SAndreas Gohr            $this->getEmbedModel(),
15634a1c478SAndreas Gohr            $this->getStorage(),
15734a1c478SAndreas Gohr            $this->conf
15834a1c478SAndreas Gohr        );
1593379af09SAndreas Gohr        if ($this->logger) {
1603379af09SAndreas Gohr            $this->embeddings->setLogger($this->logger);
1613379af09SAndreas Gohr        }
1629f6b34c4SAndreas Gohr
1630337f47fSAndreas Gohr        return $this->embeddings;
1640337f47fSAndreas Gohr    }
1650337f47fSAndreas Gohr
1660337f47fSAndreas Gohr    /**
16701f06932SAndreas Gohr     * Access the Storage interface
16801f06932SAndreas Gohr     *
16901f06932SAndreas Gohr     * @return AbstractStorage
17001f06932SAndreas Gohr     */
17101f06932SAndreas Gohr    public function getStorage()
17201f06932SAndreas Gohr    {
1736a18e0f4SAndreas Gohr        if ($this->storage instanceof AbstractStorage) {
1746a18e0f4SAndreas Gohr            return $this->storage;
1756a18e0f4SAndreas Gohr        }
1766a18e0f4SAndreas Gohr
17704afb84fSAndreas Gohr        $class = '\\dokuwiki\\plugin\\aichat\\Storage\\' . $this->getConf('storage') . 'Storage';
17804afb84fSAndreas Gohr        $this->storage = new $class($this->conf);
1798285fff9SAndreas Gohr
1803379af09SAndreas Gohr        if ($this->logger) {
1813379af09SAndreas Gohr            $this->storage->setLogger($this->logger);
1823379af09SAndreas Gohr        }
18301f06932SAndreas Gohr
18401f06932SAndreas Gohr        return $this->storage;
18501f06932SAndreas Gohr    }
18601f06932SAndreas Gohr
18701f06932SAndreas Gohr    /**
1880337f47fSAndreas Gohr     * Ask a question with a chat history
1890337f47fSAndreas Gohr     *
1900337f47fSAndreas Gohr     * @param string $question
1910337f47fSAndreas Gohr     * @param array[] $history The chat history [[user, ai], [user, ai], ...]
1920337f47fSAndreas Gohr     * @return array ['question' => $question, 'answer' => $answer, 'sources' => $sources]
1930337f47fSAndreas Gohr     * @throws Exception
1940337f47fSAndreas Gohr     */
1950337f47fSAndreas Gohr    public function askChatQuestion($question, $history = [])
1960337f47fSAndreas Gohr    {
197*51aa8517SAndreas Gohr        if ($history && $this->getConf('rephraseHistory') > 0) {
1980337f47fSAndreas Gohr            $standaloneQuestion = $this->rephraseChatQuestion($question, $history);
1990337f47fSAndreas Gohr        } else {
2000337f47fSAndreas Gohr            $standaloneQuestion = $question;
2010337f47fSAndreas Gohr        }
20234a1c478SAndreas Gohr        return $this->askQuestion($standaloneQuestion, $history);
2030337f47fSAndreas Gohr    }
2040337f47fSAndreas Gohr
2050337f47fSAndreas Gohr    /**
2060337f47fSAndreas Gohr     * Ask a single standalone question
2070337f47fSAndreas Gohr     *
2080337f47fSAndreas Gohr     * @param string $question
20934a1c478SAndreas Gohr     * @param array $history [user, ai] of the previous question
2100337f47fSAndreas Gohr     * @return array ['question' => $question, 'answer' => $answer, 'sources' => $sources]
2110337f47fSAndreas Gohr     * @throws Exception
2120337f47fSAndreas Gohr     */
21334a1c478SAndreas Gohr    public function askQuestion($question, $history = [])
2140337f47fSAndreas Gohr    {
215e33a1d7aSAndreas Gohr        $similar = $this->getEmbeddings()->getSimilarChunks($question, $this->getLanguageLimit());
2169e81bea7SAndreas Gohr        if ($similar) {
217441edf84SAndreas Gohr            $context = implode(
218441edf84SAndreas Gohr                "\n",
219441edf84SAndreas Gohr                array_map(static fn(Chunk $chunk) => "\n```\n" . $chunk->getText() . "\n```\n", $similar)
220441edf84SAndreas Gohr            );
221219268b1SAndreas Gohr            $prompt = $this->getPrompt('question', [
222219268b1SAndreas Gohr                'context' => $context,
223219268b1SAndreas Gohr            ]);
2249e81bea7SAndreas Gohr        } else {
22534a1c478SAndreas Gohr            $prompt = $this->getPrompt('noanswer');
22634a1c478SAndreas Gohr            $history = [];
2279e81bea7SAndreas Gohr        }
22868908844SAndreas Gohr
229*51aa8517SAndreas Gohr        $messages = $this->prepareMessages(
230*51aa8517SAndreas Gohr            $this->getChatModel(), $prompt, $question, $history, $this->getConf('chatHistory')
231*51aa8517SAndreas Gohr        );
2326a18e0f4SAndreas Gohr        $answer = $this->getChatModel()->getAnswer($messages);
2330337f47fSAndreas Gohr
2340337f47fSAndreas Gohr        return [
2350337f47fSAndreas Gohr            'question' => $question,
2360337f47fSAndreas Gohr            'answer' => $answer,
2370337f47fSAndreas Gohr            'sources' => $similar,
2380337f47fSAndreas Gohr        ];
2390337f47fSAndreas Gohr    }
2400337f47fSAndreas Gohr
2410337f47fSAndreas Gohr    /**
2420337f47fSAndreas Gohr     * Rephrase a question into a standalone question based on the chat history
2430337f47fSAndreas Gohr     *
2440337f47fSAndreas Gohr     * @param string $question The original user question
2450337f47fSAndreas Gohr     * @param array[] $history The chat history [[user, ai], [user, ai], ...]
2460337f47fSAndreas Gohr     * @return string The rephrased question
2470337f47fSAndreas Gohr     * @throws Exception
2480337f47fSAndreas Gohr     */
2490337f47fSAndreas Gohr    public function rephraseChatQuestion($question, $history)
2500337f47fSAndreas Gohr    {
25134a1c478SAndreas Gohr        $prompt = $this->getPrompt('rephrase');
252*51aa8517SAndreas Gohr        $messages = $this->prepareMessages(
253*51aa8517SAndreas Gohr            $this->getRephraseModel(), $prompt, $question, $history, $this->getConf('rephraseHistory')
254*51aa8517SAndreas Gohr        );
255*51aa8517SAndreas Gohr        return $this->getRephraseModel()->getAnswer($messages);
25634a1c478SAndreas Gohr    }
25734a1c478SAndreas Gohr
25834a1c478SAndreas Gohr    /**
25934a1c478SAndreas Gohr     * Prepare the messages for the AI
26034a1c478SAndreas Gohr     *
261*51aa8517SAndreas Gohr     * @param ChatInterface $model The used model
26234a1c478SAndreas Gohr     * @param string $prompt The fully prepared system prompt
26334a1c478SAndreas Gohr     * @param string $question The user question
26434a1c478SAndreas Gohr     * @param array[] $history The chat history [[user, ai], [user, ai], ...]
265*51aa8517SAndreas Gohr     * @param int $historySize The maximum number of messages to use from the history
26634a1c478SAndreas Gohr     * @return array An OpenAI compatible array of messages
26734a1c478SAndreas Gohr     */
268*51aa8517SAndreas Gohr    protected function prepareMessages(
269*51aa8517SAndreas Gohr        ChatInterface $model, string $prompt, string $question, array $history, int $historySize
270*51aa8517SAndreas Gohr    ): array
27134a1c478SAndreas Gohr    {
27234a1c478SAndreas Gohr        // calculate the space for context
273*51aa8517SAndreas Gohr        $remainingContext = $model->getMaxInputTokenLength();
27434a1c478SAndreas Gohr        $remainingContext -= $this->countTokens($prompt);
27534a1c478SAndreas Gohr        $remainingContext -= $this->countTokens($question);
27634a1c478SAndreas Gohr        $safetyMargin = $remainingContext * 0.05; // 5% safety margin
27734a1c478SAndreas Gohr        $remainingContext -= $safetyMargin;
27834a1c478SAndreas Gohr        // FIXME we may want to also have an upper limit for the history and not always use the full context
27934a1c478SAndreas Gohr
280*51aa8517SAndreas Gohr        $messages = $this->historyMessages($history, $remainingContext, $historySize);
28134a1c478SAndreas Gohr        $messages[] = [
28234a1c478SAndreas Gohr            'role' => 'system',
28334a1c478SAndreas Gohr            'content' => $prompt
28434a1c478SAndreas Gohr        ];
28534a1c478SAndreas Gohr        $messages[] = [
28634a1c478SAndreas Gohr            'role' => 'user',
28734a1c478SAndreas Gohr            'content' => $question
28834a1c478SAndreas Gohr        ];
28934a1c478SAndreas Gohr        return $messages;
29034a1c478SAndreas Gohr    }
29134a1c478SAndreas Gohr
29234a1c478SAndreas Gohr    /**
29334a1c478SAndreas Gohr     * Create an array of OpenAI compatible messages from the given history
29434a1c478SAndreas Gohr     *
29534a1c478SAndreas Gohr     * Only as many messages are used as fit into the token limit
29634a1c478SAndreas Gohr     *
29734a1c478SAndreas Gohr     * @param array[] $history The chat history [[user, ai], [user, ai], ...]
298*51aa8517SAndreas Gohr     * @param int $tokenLimit The maximum number of tokens to use
299*51aa8517SAndreas Gohr     * @param int $sizeLimit The maximum number of messages to use
30034a1c478SAndreas Gohr     * @return array
30134a1c478SAndreas Gohr     */
302*51aa8517SAndreas Gohr    protected function historyMessages(array $history, int $tokenLimit, int $sizeLimit): array
30334a1c478SAndreas Gohr    {
30434a1c478SAndreas Gohr        $remainingContext = $tokenLimit;
30534a1c478SAndreas Gohr
30634a1c478SAndreas Gohr        $messages = [];
3070337f47fSAndreas Gohr        $history = array_reverse($history);
308*51aa8517SAndreas Gohr        $history = array_slice($history, 0, $sizeLimit);
3090337f47fSAndreas Gohr        foreach ($history as $row) {
31034a1c478SAndreas Gohr            $length = $this->countTokens($row[0] . $row[1]);
31134a1c478SAndreas Gohr            if ($length > $remainingContext) {
3120337f47fSAndreas Gohr                break;
3130337f47fSAndreas Gohr            }
31434a1c478SAndreas Gohr            $remainingContext -= $length;
3150337f47fSAndreas Gohr
31634a1c478SAndreas Gohr            $messages[] = [
31734a1c478SAndreas Gohr                'role' => 'assistant',
31834a1c478SAndreas Gohr                'content' => $row[1]
31934a1c478SAndreas Gohr            ];
32034a1c478SAndreas Gohr            $messages[] = [
32134a1c478SAndreas Gohr                'role' => 'user',
32234a1c478SAndreas Gohr                'content' => $row[0]
32334a1c478SAndreas Gohr            ];
32434a1c478SAndreas Gohr        }
32534a1c478SAndreas Gohr        return array_reverse($messages);
3260337f47fSAndreas Gohr    }
3270337f47fSAndreas Gohr
32834a1c478SAndreas Gohr    /**
32934a1c478SAndreas Gohr     * Get an aproximation of the token count for the given text
33034a1c478SAndreas Gohr     *
33134a1c478SAndreas Gohr     * @param $text
33234a1c478SAndreas Gohr     * @return int
33334a1c478SAndreas Gohr     */
33434a1c478SAndreas Gohr    protected function countTokens($text)
33534a1c478SAndreas Gohr    {
33634a1c478SAndreas Gohr        return count($this->getEmbeddings()->getTokenEncoder()->encode($text));
3370337f47fSAndreas Gohr    }
3380337f47fSAndreas Gohr
3390337f47fSAndreas Gohr    /**
3400337f47fSAndreas Gohr     * Load the given prompt template and fill in the variables
3410337f47fSAndreas Gohr     *
3420337f47fSAndreas Gohr     * @param string $type
3430337f47fSAndreas Gohr     * @param string[] $vars
3440337f47fSAndreas Gohr     * @return string
3450337f47fSAndreas Gohr     */
3460337f47fSAndreas Gohr    protected function getPrompt($type, $vars = [])
3470337f47fSAndreas Gohr    {
3480337f47fSAndreas Gohr        $template = file_get_contents($this->localFN('prompt_' . $type));
34934a1c478SAndreas Gohr        $vars['language'] = $this->getLanguagePrompt();
3500337f47fSAndreas Gohr
3517ebc7895Ssplitbrain        $replace = [];
3520337f47fSAndreas Gohr        foreach ($vars as $key => $val) {
3530337f47fSAndreas Gohr            $replace['{{' . strtoupper($key) . '}}'] = $val;
3540337f47fSAndreas Gohr        }
3550337f47fSAndreas Gohr
3560337f47fSAndreas Gohr        return strtr($template, $replace);
3570337f47fSAndreas Gohr    }
358219268b1SAndreas Gohr
359219268b1SAndreas Gohr    /**
360219268b1SAndreas Gohr     * Construct the prompt to define the answer language
361219268b1SAndreas Gohr     *
362219268b1SAndreas Gohr     * @return string
363219268b1SAndreas Gohr     */
364219268b1SAndreas Gohr    protected function getLanguagePrompt()
365219268b1SAndreas Gohr    {
366219268b1SAndreas Gohr        global $conf;
367cfaf6b32SAndreas Gohr        $isoLangnames = include(__DIR__ . '/lang/languages.php');
368cfaf6b32SAndreas Gohr
369cfaf6b32SAndreas Gohr        $currentLang = $isoLangnames[$conf['lang']] ?? 'English';
370219268b1SAndreas Gohr
371e33a1d7aSAndreas Gohr        if ($this->getConf('preferUIlanguage') > AIChat::LANG_AUTO_ALL) {
372219268b1SAndreas Gohr            if (isset($isoLangnames[$conf['lang']])) {
373219268b1SAndreas Gohr                $languagePrompt = 'Always answer in ' . $isoLangnames[$conf['lang']] . '.';
374219268b1SAndreas Gohr                return $languagePrompt;
375219268b1SAndreas Gohr            }
376219268b1SAndreas Gohr        }
377219268b1SAndreas Gohr
378cfaf6b32SAndreas Gohr        $languagePrompt = 'Always answer in the user\'s language. ' .
379cfaf6b32SAndreas Gohr            "If you are unsure about the language, speak $currentLang.";
380219268b1SAndreas Gohr        return $languagePrompt;
381219268b1SAndreas Gohr    }
382e33a1d7aSAndreas Gohr
383e33a1d7aSAndreas Gohr    /**
384e33a1d7aSAndreas Gohr     * Should sources be limited to current language?
385e33a1d7aSAndreas Gohr     *
386e33a1d7aSAndreas Gohr     * @return string The current language code or empty string
387e33a1d7aSAndreas Gohr     */
388e33a1d7aSAndreas Gohr    public function getLanguageLimit()
389e33a1d7aSAndreas Gohr    {
390e33a1d7aSAndreas Gohr        if ($this->getConf('preferUIlanguage') >= AIChat::LANG_UI_LIMITED) {
391e33a1d7aSAndreas Gohr            global $conf;
392e33a1d7aSAndreas Gohr            return $conf['lang'];
393e33a1d7aSAndreas Gohr        } else {
394e33a1d7aSAndreas Gohr            return '';
395e33a1d7aSAndreas Gohr        }
396e33a1d7aSAndreas Gohr    }
397e75dc39fSAndreas Gohr
398e75dc39fSAndreas Gohr    /**
399e75dc39fSAndreas Gohr     * Store info about the last run
400e75dc39fSAndreas Gohr     *
401e75dc39fSAndreas Gohr     * @param array $data
402e75dc39fSAndreas Gohr     * @return void
403e75dc39fSAndreas Gohr     */
404e75dc39fSAndreas Gohr    public function setRunData(array $data)
405e75dc39fSAndreas Gohr    {
406e75dc39fSAndreas Gohr        file_put_contents($this->runDataFile, json_encode($data, JSON_PRETTY_PRINT));
407e75dc39fSAndreas Gohr    }
408e75dc39fSAndreas Gohr
409e75dc39fSAndreas Gohr    /**
410e75dc39fSAndreas Gohr     * Get info about the last run
411e75dc39fSAndreas Gohr     *
412e75dc39fSAndreas Gohr     * @return array
413e75dc39fSAndreas Gohr     */
414e75dc39fSAndreas Gohr    public function getRunData()
415e75dc39fSAndreas Gohr    {
416e75dc39fSAndreas Gohr        if (!file_exists($this->runDataFile)) {
417e75dc39fSAndreas Gohr            return [];
418e75dc39fSAndreas Gohr        }
419e75dc39fSAndreas Gohr        return json_decode(file_get_contents($this->runDataFile), true);
420e75dc39fSAndreas Gohr    }
4210337f47fSAndreas Gohr}
422