xref: /plugin/aichat/helper.php (revision 9634d7345e88e8177bbba1e0ecb312352866df1d)
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;
10c2b7a1f7SAndreas Gohruse dokuwiki\plugin\aichat\ModelFactory;
1101f06932SAndreas Gohruse dokuwiki\plugin\aichat\Storage\AbstractStorage;
120337f47fSAndreas Gohr
130337f47fSAndreas Gohr/**
140337f47fSAndreas Gohr * DokuWiki Plugin aichat (Helper Component)
150337f47fSAndreas Gohr *
160337f47fSAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
170337f47fSAndreas Gohr * @author  Andreas Gohr <gohr@cosmocode.de>
180337f47fSAndreas Gohr */
197ebc7895Ssplitbrainclass helper_plugin_aichat extends Plugin
200337f47fSAndreas Gohr{
21c2b7a1f7SAndreas Gohr    /** @var ModelFactory */
22c2b7a1f7SAndreas Gohr    public $factory;
23c2b7a1f7SAndreas Gohr
243379af09SAndreas Gohr    /** @var CLIPlugin $logger */
253379af09SAndreas Gohr    protected $logger;
26c2b7a1f7SAndreas Gohr
270337f47fSAndreas Gohr    /** @var Embeddings */
280337f47fSAndreas Gohr    protected $embeddings;
2901f06932SAndreas Gohr    /** @var AbstractStorage */
3001f06932SAndreas Gohr    protected $storage;
310337f47fSAndreas Gohr
32e75dc39fSAndreas Gohr    /** @var array where to store meta data on the last run */
33e75dc39fSAndreas Gohr    protected $runDataFile;
34e75dc39fSAndreas Gohr
3551aa8517SAndreas Gohr
360337f47fSAndreas Gohr    /**
37f8d5ae01SAndreas Gohr     * Constructor. Initializes vendor autoloader
38f8d5ae01SAndreas Gohr     */
39f8d5ae01SAndreas Gohr    public function __construct()
40f8d5ae01SAndreas Gohr    {
41e75dc39fSAndreas Gohr        require_once __DIR__ . '/vendor/autoload.php'; // FIXME obsolete from Kaos onwards
42e75dc39fSAndreas Gohr        global $conf;
43e75dc39fSAndreas Gohr        $this->runDataFile = $conf['metadir'] . '/aichat__run.json';
44d02b7935SAndreas Gohr        $this->loadConfig();
45c2b7a1f7SAndreas Gohr        $this->factory = new ModelFactory($this->conf);
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    /**
600de7e020SAndreas Gohr     * Update the configuration
610de7e020SAndreas Gohr     *
620de7e020SAndreas Gohr     * @param array $config
630de7e020SAndreas Gohr     * @return void
640de7e020SAndreas Gohr     */
650de7e020SAndreas Gohr    public function updateConfig(array $config)
660de7e020SAndreas Gohr    {
670de7e020SAndreas Gohr        $this->conf = array_merge($this->conf, $config);
680de7e020SAndreas Gohr        $this->factory->updateConfig($config);
690de7e020SAndreas Gohr    }
700de7e020SAndreas Gohr
710de7e020SAndreas Gohr    /**
72c4127b8eSAndreas Gohr     * Check if the current user is allowed to use the plugin (if it has been restricted)
73c4127b8eSAndreas Gohr     *
74c4127b8eSAndreas Gohr     * @return bool
75c4127b8eSAndreas Gohr     */
76c4127b8eSAndreas Gohr    public function userMayAccess()
77c4127b8eSAndreas Gohr    {
78c4127b8eSAndreas Gohr        global $auth;
79c4127b8eSAndreas Gohr        global $USERINFO;
80c4127b8eSAndreas Gohr        global $INPUT;
81c4127b8eSAndreas Gohr
82c4127b8eSAndreas Gohr        if (!$auth) return true;
83c4127b8eSAndreas Gohr        if (!$this->getConf('restrict')) return true;
84c4127b8eSAndreas Gohr        if (!isset($USERINFO)) return false;
85c4127b8eSAndreas Gohr
86c4127b8eSAndreas Gohr        return auth_isMember($this->getConf('restrict'), $INPUT->server->str('REMOTE_USER'), $USERINFO['grps']);
87c4127b8eSAndreas Gohr    }
88c4127b8eSAndreas Gohr
89c4127b8eSAndreas Gohr    /**
906a18e0f4SAndreas Gohr     * Access the Chat Model
910337f47fSAndreas Gohr     *
92294a9eafSAndreas Gohr     * @return ChatInterface
930337f47fSAndreas Gohr     */
946a18e0f4SAndreas Gohr    public function getChatModel()
950337f47fSAndreas Gohr    {
96c2b7a1f7SAndreas Gohr        return $this->factory->getChatModel();
979f6b34c4SAndreas Gohr    }
989f6b34c4SAndreas Gohr
996a18e0f4SAndreas Gohr    /**
10051aa8517SAndreas Gohr     * @return ChatInterface
10151aa8517SAndreas Gohr     */
10251aa8517SAndreas Gohr    public function getRephraseModel()
10351aa8517SAndreas Gohr    {
104c2b7a1f7SAndreas Gohr        return $this->factory->getRephraseModel();
10551aa8517SAndreas Gohr    }
10651aa8517SAndreas Gohr
10751aa8517SAndreas Gohr    /**
1086a18e0f4SAndreas Gohr     * Access the Embedding Model
1096a18e0f4SAndreas Gohr     *
110294a9eafSAndreas Gohr     * @return EmbeddingInterface
1116a18e0f4SAndreas Gohr     */
112c2b7a1f7SAndreas Gohr    public function getEmbeddingModel()
1136a18e0f4SAndreas Gohr    {
114c2b7a1f7SAndreas Gohr        return $this->factory->getEmbeddingModel();
1150337f47fSAndreas Gohr    }
1160337f47fSAndreas Gohr
1170337f47fSAndreas Gohr    /**
1180337f47fSAndreas Gohr     * Access the Embeddings interface
1190337f47fSAndreas Gohr     *
1200337f47fSAndreas Gohr     * @return Embeddings
1210337f47fSAndreas Gohr     */
1220337f47fSAndreas Gohr    public function getEmbeddings()
1230337f47fSAndreas Gohr    {
1246a18e0f4SAndreas Gohr        if ($this->embeddings instanceof Embeddings) {
1256a18e0f4SAndreas Gohr            return $this->embeddings;
1266a18e0f4SAndreas Gohr        }
1276a18e0f4SAndreas Gohr
12834a1c478SAndreas Gohr        $this->embeddings = new Embeddings(
12934a1c478SAndreas Gohr            $this->getChatModel(),
130c2b7a1f7SAndreas Gohr            $this->getEmbeddingModel(),
13134a1c478SAndreas Gohr            $this->getStorage(),
13234a1c478SAndreas Gohr            $this->conf
13334a1c478SAndreas Gohr        );
1343379af09SAndreas Gohr        if ($this->logger) {
1353379af09SAndreas Gohr            $this->embeddings->setLogger($this->logger);
1363379af09SAndreas Gohr        }
1379f6b34c4SAndreas Gohr
1380337f47fSAndreas Gohr        return $this->embeddings;
1390337f47fSAndreas Gohr    }
1400337f47fSAndreas Gohr
1410337f47fSAndreas Gohr    /**
14201f06932SAndreas Gohr     * Access the Storage interface
14301f06932SAndreas Gohr     *
14401f06932SAndreas Gohr     * @return AbstractStorage
14501f06932SAndreas Gohr     */
14601f06932SAndreas Gohr    public function getStorage()
14701f06932SAndreas Gohr    {
1486a18e0f4SAndreas Gohr        if ($this->storage instanceof AbstractStorage) {
1496a18e0f4SAndreas Gohr            return $this->storage;
1506a18e0f4SAndreas Gohr        }
1516a18e0f4SAndreas Gohr
15204afb84fSAndreas Gohr        $class = '\\dokuwiki\\plugin\\aichat\\Storage\\' . $this->getConf('storage') . 'Storage';
15304afb84fSAndreas Gohr        $this->storage = new $class($this->conf);
1548285fff9SAndreas Gohr
1553379af09SAndreas Gohr        if ($this->logger) {
1563379af09SAndreas Gohr            $this->storage->setLogger($this->logger);
1573379af09SAndreas Gohr        }
15801f06932SAndreas Gohr
15901f06932SAndreas Gohr        return $this->storage;
16001f06932SAndreas Gohr    }
16101f06932SAndreas Gohr
16201f06932SAndreas Gohr    /**
1630337f47fSAndreas Gohr     * Ask a question with a chat history
1640337f47fSAndreas Gohr     *
1650337f47fSAndreas Gohr     * @param string $question
1660337f47fSAndreas Gohr     * @param array[] $history The chat history [[user, ai], [user, ai], ...]
1670337f47fSAndreas Gohr     * @return array ['question' => $question, 'answer' => $answer, 'sources' => $sources]
1680337f47fSAndreas Gohr     * @throws Exception
1690337f47fSAndreas Gohr     */
170ed47fd87SAndreas Gohr    public function askChatQuestion($question, $history = [], $sourcePage = '')
1710337f47fSAndreas Gohr    {
17251aa8517SAndreas Gohr        if ($history && $this->getConf('rephraseHistory') > 0) {
17387090e4bSAndreas Gohr            $contextQuestion = $this->rephraseChatQuestion($question, $history);
17487090e4bSAndreas Gohr
17587090e4bSAndreas Gohr            // Only use the rephrased question if it has more history than the chat history provides
17687090e4bSAndreas Gohr            if ($this->getConf('rephraseHistory') > $this->getConf('chatHistory')) {
17787090e4bSAndreas Gohr                $question = $contextQuestion;
1780337f47fSAndreas Gohr            }
17987090e4bSAndreas Gohr        } else {
18087090e4bSAndreas Gohr            $contextQuestion = $question;
18187090e4bSAndreas Gohr        }
182ed47fd87SAndreas Gohr        return $this->askQuestion($question, $history, $contextQuestion, $sourcePage);
1830337f47fSAndreas Gohr    }
1840337f47fSAndreas Gohr
1850337f47fSAndreas Gohr    /**
1860337f47fSAndreas Gohr     * Ask a single standalone question
1870337f47fSAndreas Gohr     *
18887090e4bSAndreas Gohr     * @param string $question The question to ask
18934a1c478SAndreas Gohr     * @param array $history [user, ai] of the previous question
19087090e4bSAndreas Gohr     * @param string $contextQuestion The question to use for context search
191ed47fd87SAndreas Gohr     * @param string $sourcePage The page the question was asked on
1920337f47fSAndreas Gohr     * @return array ['question' => $question, 'answer' => $answer, 'sources' => $sources]
1930337f47fSAndreas Gohr     * @throws Exception
1940337f47fSAndreas Gohr     */
195ed47fd87SAndreas Gohr    public function askQuestion($question, $history = [], $contextQuestion = '', $sourcePage = '')
1960337f47fSAndreas Gohr    {
197ed47fd87SAndreas Gohr        if ($sourcePage) {
198*9634d734SAndreas Gohr            // only the current page is context
199ed47fd87SAndreas Gohr            $similar = $this->getEmbeddings()->getPageChunks($sourcePage);
200ed47fd87SAndreas Gohr        } else {
201*9634d734SAndreas Gohr            if ($this->getConf('fullpagecontext')) {
202*9634d734SAndreas Gohr                // match chunks but use full pages as context
203*9634d734SAndreas Gohr                $similar = $this->getEmbeddings()->getSimilarPages(
204*9634d734SAndreas Gohr                    $contextQuestion ?: $question,
205*9634d734SAndreas Gohr                    $this->getLanguageLimit()
206*9634d734SAndreas Gohr                );
207*9634d734SAndreas Gohr            } else {
208*9634d734SAndreas Gohr                // use the chunks as context
209*9634d734SAndreas Gohr                $similar = $this->getEmbeddings()->getSimilarChunks(
210*9634d734SAndreas Gohr                    $contextQuestion ?: $question, $this->getLanguageLimit()
211*9634d734SAndreas Gohr                );
212*9634d734SAndreas Gohr            }
213ed47fd87SAndreas Gohr        }
214ed47fd87SAndreas Gohr
2159e81bea7SAndreas Gohr        if ($similar) {
216441edf84SAndreas Gohr            $context = implode(
217441edf84SAndreas Gohr                "\n",
218441edf84SAndreas Gohr                array_map(static fn(Chunk $chunk) => "\n```\n" . $chunk->getText() . "\n```\n", $similar)
219441edf84SAndreas Gohr            );
220219268b1SAndreas Gohr            $prompt = $this->getPrompt('question', [
221219268b1SAndreas Gohr                'context' => $context,
22259a2a267SAndreas Gohr                'question' => $question,
223666b8ea7SAndreas Gohr                'customprompt' => $this->getConf('customprompt'),
224219268b1SAndreas Gohr            ]);
2259e81bea7SAndreas Gohr        } else {
22659a2a267SAndreas Gohr            $prompt = $this->getPrompt('noanswer', [
22759a2a267SAndreas Gohr                'question' => $question,
22859a2a267SAndreas Gohr            ]);
22934a1c478SAndreas Gohr            $history = [];
2309e81bea7SAndreas Gohr        }
23168908844SAndreas Gohr
23251aa8517SAndreas Gohr        $messages = $this->prepareMessages(
2332071dcedSAndreas Gohr            $this->getChatModel(),
2342071dcedSAndreas Gohr            $prompt,
2352071dcedSAndreas Gohr            $history,
2362071dcedSAndreas Gohr            $this->getConf('chatHistory')
23751aa8517SAndreas Gohr        );
2386a18e0f4SAndreas Gohr        $answer = $this->getChatModel()->getAnswer($messages);
2390337f47fSAndreas Gohr
2400337f47fSAndreas Gohr        return [
2410337f47fSAndreas Gohr            'question' => $question,
24287090e4bSAndreas Gohr            'contextQuestion' => $contextQuestion,
2430337f47fSAndreas Gohr            'answer' => $answer,
2440337f47fSAndreas Gohr            'sources' => $similar,
2450337f47fSAndreas Gohr        ];
2460337f47fSAndreas Gohr    }
2470337f47fSAndreas Gohr
2480337f47fSAndreas Gohr    /**
2490337f47fSAndreas Gohr     * Rephrase a question into a standalone question based on the chat history
2500337f47fSAndreas Gohr     *
2510337f47fSAndreas Gohr     * @param string $question The original user question
2520337f47fSAndreas Gohr     * @param array[] $history The chat history [[user, ai], [user, ai], ...]
2530337f47fSAndreas Gohr     * @return string The rephrased question
2540337f47fSAndreas Gohr     * @throws Exception
2550337f47fSAndreas Gohr     */
2560337f47fSAndreas Gohr    public function rephraseChatQuestion($question, $history)
2570337f47fSAndreas Gohr    {
25859a2a267SAndreas Gohr        $prompt = $this->getPrompt('rephrase', [
25959a2a267SAndreas Gohr            'question' => $question,
26059a2a267SAndreas Gohr        ]);
26151aa8517SAndreas Gohr        $messages = $this->prepareMessages(
2622071dcedSAndreas Gohr            $this->getRephraseModel(),
2632071dcedSAndreas Gohr            $prompt,
2642071dcedSAndreas Gohr            $history,
2652071dcedSAndreas Gohr            $this->getConf('rephraseHistory')
26651aa8517SAndreas Gohr        );
26751aa8517SAndreas Gohr        return $this->getRephraseModel()->getAnswer($messages);
26834a1c478SAndreas Gohr    }
26934a1c478SAndreas Gohr
27034a1c478SAndreas Gohr    /**
27134a1c478SAndreas Gohr     * Prepare the messages for the AI
27234a1c478SAndreas Gohr     *
27351aa8517SAndreas Gohr     * @param ChatInterface $model The used model
27459a2a267SAndreas Gohr     * @param string $promptedQuestion The user question embedded in a prompt
27534a1c478SAndreas Gohr     * @param array[] $history The chat history [[user, ai], [user, ai], ...]
27651aa8517SAndreas Gohr     * @param int $historySize The maximum number of messages to use from the history
27734a1c478SAndreas Gohr     * @return array An OpenAI compatible array of messages
27834a1c478SAndreas Gohr     */
27951aa8517SAndreas Gohr    protected function prepareMessages(
2802071dcedSAndreas Gohr        ChatInterface $model,
2812071dcedSAndreas Gohr        string        $promptedQuestion,
2822071dcedSAndreas Gohr        array         $history,
2832071dcedSAndreas Gohr        int           $historySize
284*9634d734SAndreas Gohr    ): array
285*9634d734SAndreas Gohr    {
28634a1c478SAndreas Gohr        // calculate the space for context
2877be8078eSAndreas Gohr        $remainingContext = $model->getMaxInputTokenLength(); // might be 0
28859a2a267SAndreas Gohr        $remainingContext -= $this->countTokens($promptedQuestion);
2897be8078eSAndreas Gohr        $safetyMargin = abs($remainingContext) * 0.05; // 5% safety margin
2907be8078eSAndreas Gohr        $remainingContext -= $safetyMargin; // may be negative, it will be ignored then
29134a1c478SAndreas Gohr
29251aa8517SAndreas Gohr        $messages = $this->historyMessages($history, $remainingContext, $historySize);
29334a1c478SAndreas Gohr        $messages[] = [
29434a1c478SAndreas Gohr            'role' => 'user',
29559a2a267SAndreas Gohr            'content' => $promptedQuestion
29634a1c478SAndreas Gohr        ];
29734a1c478SAndreas Gohr        return $messages;
29834a1c478SAndreas Gohr    }
29934a1c478SAndreas Gohr
30034a1c478SAndreas Gohr    /**
30134a1c478SAndreas Gohr     * Create an array of OpenAI compatible messages from the given history
30234a1c478SAndreas Gohr     *
30334a1c478SAndreas Gohr     * Only as many messages are used as fit into the token limit
30434a1c478SAndreas Gohr     *
30534a1c478SAndreas Gohr     * @param array[] $history The chat history [[user, ai], [user, ai], ...]
3067be8078eSAndreas Gohr     * @param int $tokenLimit The maximum number of tokens to use, negative limit disables this check
30751aa8517SAndreas Gohr     * @param int $sizeLimit The maximum number of messages to use
30834a1c478SAndreas Gohr     * @return array
30934a1c478SAndreas Gohr     */
31051aa8517SAndreas Gohr    protected function historyMessages(array $history, int $tokenLimit, int $sizeLimit): array
31134a1c478SAndreas Gohr    {
31234a1c478SAndreas Gohr        $remainingContext = $tokenLimit;
31334a1c478SAndreas Gohr
31434a1c478SAndreas Gohr        $messages = [];
3150337f47fSAndreas Gohr        $history = array_reverse($history);
31651aa8517SAndreas Gohr        $history = array_slice($history, 0, $sizeLimit);
3170337f47fSAndreas Gohr        foreach ($history as $row) {
31834a1c478SAndreas Gohr            $length = $this->countTokens($row[0] . $row[1]);
3197be8078eSAndreas Gohr
3207be8078eSAndreas Gohr            if ($tokenLimit > 0 && $length > $remainingContext) {
3210337f47fSAndreas Gohr                break;
3220337f47fSAndreas Gohr            }
32334a1c478SAndreas Gohr            $remainingContext -= $length;
3240337f47fSAndreas Gohr
32534a1c478SAndreas Gohr            $messages[] = [
32634a1c478SAndreas Gohr                'role' => 'assistant',
32734a1c478SAndreas Gohr                'content' => $row[1]
32834a1c478SAndreas Gohr            ];
32934a1c478SAndreas Gohr            $messages[] = [
33034a1c478SAndreas Gohr                'role' => 'user',
33134a1c478SAndreas Gohr                'content' => $row[0]
33234a1c478SAndreas Gohr            ];
33334a1c478SAndreas Gohr        }
33434a1c478SAndreas Gohr        return array_reverse($messages);
3350337f47fSAndreas Gohr    }
3360337f47fSAndreas Gohr
33734a1c478SAndreas Gohr    /**
33834a1c478SAndreas Gohr     * Get an aproximation of the token count for the given text
33934a1c478SAndreas Gohr     *
34034a1c478SAndreas Gohr     * @param $text
34134a1c478SAndreas Gohr     * @return int
34234a1c478SAndreas Gohr     */
34334a1c478SAndreas Gohr    protected function countTokens($text)
34434a1c478SAndreas Gohr    {
34534a1c478SAndreas Gohr        return count($this->getEmbeddings()->getTokenEncoder()->encode($text));
3460337f47fSAndreas Gohr    }
3470337f47fSAndreas Gohr
3480337f47fSAndreas Gohr    /**
3490337f47fSAndreas Gohr     * Load the given prompt template and fill in the variables
3500337f47fSAndreas Gohr     *
3510337f47fSAndreas Gohr     * @param string $type
3520337f47fSAndreas Gohr     * @param string[] $vars
3530337f47fSAndreas Gohr     * @return string
3540337f47fSAndreas Gohr     */
3550337f47fSAndreas Gohr    protected function getPrompt($type, $vars = [])
3560337f47fSAndreas Gohr    {
35759a2a267SAndreas Gohr        $template = file_get_contents($this->localFN($type, 'prompt'));
35834a1c478SAndreas Gohr        $vars['language'] = $this->getLanguagePrompt();
3590337f47fSAndreas Gohr
3607ebc7895Ssplitbrain        $replace = [];
3610337f47fSAndreas Gohr        foreach ($vars as $key => $val) {
3620337f47fSAndreas Gohr            $replace['{{' . strtoupper($key) . '}}'] = $val;
3630337f47fSAndreas Gohr        }
3640337f47fSAndreas Gohr
3650337f47fSAndreas Gohr        return strtr($template, $replace);
3660337f47fSAndreas Gohr    }
367219268b1SAndreas Gohr
368219268b1SAndreas Gohr    /**
369219268b1SAndreas Gohr     * Construct the prompt to define the answer language
370219268b1SAndreas Gohr     *
371219268b1SAndreas Gohr     * @return string
372219268b1SAndreas Gohr     */
373219268b1SAndreas Gohr    protected function getLanguagePrompt()
374219268b1SAndreas Gohr    {
375219268b1SAndreas Gohr        global $conf;
376cfaf6b32SAndreas Gohr        $isoLangnames = include(__DIR__ . '/lang/languages.php');
377cfaf6b32SAndreas Gohr
378cfaf6b32SAndreas Gohr        $currentLang = $isoLangnames[$conf['lang']] ?? 'English';
379219268b1SAndreas Gohr
380e33a1d7aSAndreas Gohr        if ($this->getConf('preferUIlanguage') > AIChat::LANG_AUTO_ALL) {
381219268b1SAndreas Gohr            if (isset($isoLangnames[$conf['lang']])) {
382219268b1SAndreas Gohr                $languagePrompt = 'Always answer in ' . $isoLangnames[$conf['lang']] . '.';
383219268b1SAndreas Gohr                return $languagePrompt;
384219268b1SAndreas Gohr            }
385219268b1SAndreas Gohr        }
386219268b1SAndreas Gohr
387cfaf6b32SAndreas Gohr        $languagePrompt = 'Always answer in the user\'s language. ' .
388cfaf6b32SAndreas Gohr            "If you are unsure about the language, speak $currentLang.";
389219268b1SAndreas Gohr        return $languagePrompt;
390219268b1SAndreas Gohr    }
391e33a1d7aSAndreas Gohr
392e33a1d7aSAndreas Gohr    /**
393e33a1d7aSAndreas Gohr     * Should sources be limited to current language?
394e33a1d7aSAndreas Gohr     *
395e33a1d7aSAndreas Gohr     * @return string The current language code or empty string
396e33a1d7aSAndreas Gohr     */
397e33a1d7aSAndreas Gohr    public function getLanguageLimit()
398e33a1d7aSAndreas Gohr    {
399e33a1d7aSAndreas Gohr        if ($this->getConf('preferUIlanguage') >= AIChat::LANG_UI_LIMITED) {
400e33a1d7aSAndreas Gohr            global $conf;
401e33a1d7aSAndreas Gohr            return $conf['lang'];
402e33a1d7aSAndreas Gohr        } else {
403e33a1d7aSAndreas Gohr            return '';
404e33a1d7aSAndreas Gohr        }
405e33a1d7aSAndreas Gohr    }
406e75dc39fSAndreas Gohr
407e75dc39fSAndreas Gohr    /**
408e75dc39fSAndreas Gohr     * Store info about the last run
409e75dc39fSAndreas Gohr     *
410e75dc39fSAndreas Gohr     * @param array $data
411e75dc39fSAndreas Gohr     * @return void
412e75dc39fSAndreas Gohr     */
413e75dc39fSAndreas Gohr    public function setRunData(array $data)
414e75dc39fSAndreas Gohr    {
415e75dc39fSAndreas Gohr        file_put_contents($this->runDataFile, json_encode($data, JSON_PRETTY_PRINT));
416e75dc39fSAndreas Gohr    }
417e75dc39fSAndreas Gohr
418e75dc39fSAndreas Gohr    /**
419e75dc39fSAndreas Gohr     * Get info about the last run
420e75dc39fSAndreas Gohr     *
421e75dc39fSAndreas Gohr     * @return array
422e75dc39fSAndreas Gohr     */
423e75dc39fSAndreas Gohr    public function getRunData()
424e75dc39fSAndreas Gohr    {
425e75dc39fSAndreas Gohr        if (!file_exists($this->runDataFile)) {
426e75dc39fSAndreas Gohr            return [];
427e75dc39fSAndreas Gohr        }
428e75dc39fSAndreas Gohr        return json_decode(file_get_contents($this->runDataFile), true);
429e75dc39fSAndreas Gohr    }
4300337f47fSAndreas Gohr}
431