1<?php
2
3use dokuwiki\ErrorHandler;
4use dokuwiki\Extension\ActionPlugin;
5use dokuwiki\Extension\Event;
6use dokuwiki\Extension\EventHandler;
7use dokuwiki\Logger;
8use dokuwiki\plugin\aichat\Chunk;
9
10/**
11 * DokuWiki Plugin aichat (Action Component)
12 *
13 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
14 * @author  Andreas Gohr <gohr@cosmocode.de>
15 */
16class action_plugin_aichat extends ActionPlugin
17{
18    /** @inheritDoc */
19    public function register(EventHandler $controller)
20    {
21        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleQuestion');
22    }
23
24
25    /**
26     * Event handler for AJAX_CALL_UNKNOWN event
27     *
28     * @see https://www.dokuwiki.org/devel:events:ajax_call_unknown
29     * @param Event $event Event object
30     * @param mixed $param optional parameter passed when event was registered
31     * @return void
32     */
33    public function handleQuestion(Event $event, mixed $param)
34    {
35        if ($event->data !== 'aichat') return;
36        $event->preventDefault();
37        $event->stopPropagation();
38        global $INPUT;
39
40        /** @var helper_plugin_aichat $helper */
41        $helper = plugin_load('helper', 'aichat');
42
43        $question = $INPUT->post->str('question');
44        $pagecontext = $INPUT->post->str('pagecontext');
45        $history = json_decode((string)$INPUT->post->str('history'), null, 512, JSON_THROW_ON_ERROR);
46        header('Content-Type: application/json');
47
48        if (!$helper->userMayAccess()) {
49            echo json_encode([
50                'question' => $question,
51                'answer' => $this->getLang('restricted'),
52                'sources' => [],
53            ], JSON_THROW_ON_ERROR);
54            return;
55        }
56
57        try {
58            $result = $helper->askChatQuestion($question, $history, $pagecontext);
59            $sources = [];
60            foreach ($result['sources'] as $source) {
61                /** @var Chunk $source */
62                if (isset($sources[$source->getPage()])) continue; // only show the first occurrence per page
63                $sources[$source->getPage()] = [
64                    'page' => $source->getPage(),
65                    'url' => wl($source->getPage()),
66                    'title' => p_get_first_heading($source->getPage()) ?: $source->getPage(),
67                    'score' => sprintf("%.2f%%", $source->getScore() * 100),
68                ];
69            }
70            $parseDown = new Parsedown();
71            $parseDown->setSafeMode(true);
72
73            echo json_encode([
74                'question' => $result['question'],
75                'answer' => $parseDown->text($result['answer']),
76                'sources' => array_values($sources),
77            ], JSON_THROW_ON_ERROR);
78
79            if ($this->getConf('logging')) {
80                Logger::getInstance('aichat')->log(
81                    $question,
82                    [
83                        'interpretation' => $result['question'],
84                        'answer' => $result['answer'],
85                        'sources' => $sources,
86                        'ip' => $INPUT->server->str('REMOTE_ADDR'),
87                        'user' => $INPUT->server->str('REMOTE_USER'),
88                        'stats' => $helper->getChatModel()->getUsageStats()
89                    ]
90                );
91            }
92        } catch (\Exception $e) {
93            ErrorHandler::logException($e);
94            echo json_encode([
95                'question' => $question,
96                'answer' => 'An error occurred. More info may be available in the error log. ' . $e->getMessage(),
97                'sources' => [],
98            ], JSON_THROW_ON_ERROR);
99        }
100    }
101}
102