1<?php
2/**
3 * DokuWiki Plugin keywords (Action Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Gerry Weißbach <tools@inetsoftware.de>
7 */
8use dokuwiki\HTTP\DokuHTTPClient;
9use \dokuwiki\Logger;
10
11class action_plugin_keywords_keywords extends \dokuwiki\Extension\ActionPlugin
12{
13    private $CHATGPT_API_URL = "https://api.openai.com/v1/chat/completions";
14
15    /** @inheritDoc */
16    public function register(Doku_Event_Handler $controller)
17    {
18        $controller->register_hook('FORM_EDIT_OUTPUT', 'BEFORE', $this, 'handleFormEditOutput');
19        $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'BEFORE', $this, 'handleCommonWikipageSave');
20        $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'handleMetaheaderOutput');
21    }
22
23    /**
24     * Event handler for TPL_METAHEADER_OUTPUT
25     *
26     * @see https://www.dokuwiki.org/devel:event:TPL_METAHEADER_OUTPUT
27     * @param Doku_Event $event Event object
28     * @param mixed $param optional parameter passed when event was registered
29     * @return void
30     */
31    public function handleFormEditOutput(Doku_Event $event, $param) {
32        global $lang;
33
34        // if no API key is set, do not show the option
35        if ( empty( $this->getConf('APIKey') ) ) {
36            return;
37        }
38
39        /** @var Doku_Form||\dokuwiki\Form\Form $form */
40        $form =& $event->data;
41
42        if (is_a($form, \dokuwiki\Form\Form::class) ) {
43            $form->addHTML(' ');
44            $form->addCheckbox('updateKeywords', $this->getLang('updateKeywords'))->id('edit__updateKeywords')->addClass('nowrap')->val('1');
45        }
46
47        if (is_a($form, Doku_Form::class)  ) {
48            $form->addElement( form_makeCheckboxField( 'updateKeywords', '1', $this->getLang('updateKeywords'), 'edit__updateKeywords', 'nowrap' ) );
49        }
50    }
51
52    /**
53     * Event handler for COMMON_WIKIPAGE_SAVE
54     *
55     * @see https://www.dokuwiki.org/devel:event:COMMON_WIKIPAGE_SAVE
56     * @param Doku_Event $event Event object
57     * @param mixed $param optional parameter passed when event was registered
58     * @return void
59     */
60    public function handleCommonWikipageSave(Doku_Event $event, $param) {
61        global $INPUT;
62
63        $hasUpdateKeywords = $INPUT->bool( 'updateKeywords', false );
64        if ( !$hasUpdateKeywords ) {
65            return;
66        }
67
68        $requestContent = $event->data['newContent'];
69        $requestContent = trim( preg_replace( '/\{\{keywords>.*?\}\}/is' , '', $requestContent ) );
70        if ( empty( $requestContent ) ) {
71            $event->data['contentChanged'] = 1;
72            $event->data['newContent'] = '';
73            return;
74        }
75
76//*     // Remove one slash to have a sample of keywords saved instead of checking with chatGPT
77        $httpClient = new DokuHTTPClient();
78        // $httpClient->debug = true;
79        $httpClient->headers = [
80            'Authorization' => 'Bearer ' . $this->getConf('APIKey'),
81            'Content-Type' => 'application/json',
82        ];
83        $status = $httpClient->sendRequest($this->CHATGPT_API_URL, json_encode( [
84            'model' => $this->getConf('APIModel'),
85            'messages' => [
86                [ "role" => "system", "content" => $this->getConf('APIRole')  ],
87                [ "role" => "user", "content" => $requestContent ]
88            ],
89            "functions" => [[
90                "name" => "keywords_for_text",
91                "description" => "Use this function to sent the list of keywords back to the dokuwiki. The Input is the array of keywords",
92                "parameters" => [
93                    "type" => "object",
94                    "properties" => [
95                        "keywords" => [
96                            "type" => "array",
97                            "items" => [
98                                "type" => "string"
99                            ],
100                            "description" => "This is the list of keywords generated from the input. Each keyword is given as string."
101                        ]
102                    ]
103                ]
104            ],[
105                "name" => "no_content",
106                "description" => "Use this function to indicate that no kexwords could be generated.",
107                "parameters" => [
108                    "type" => "object",
109                    "properties" => [
110                        "empty" => [
111                            "type" => "string",
112                            "description" => "This parameter must always be NULL."
113                        ]
114                    ]
115                ]
116            ]]
117        ] ), 'POST');
118
119        $data = json_decode( $httpClient->resp_body );
120
121        if ( $status === false ) {
122            Logger::error( "An error occurred during the Chat GPT call", $httpClient->error, __FILE__, __LINE__ );
123            return;
124        } else if ( $data->error ) {
125            Logger::error( "An error occurred during the Chat GPT call", $data->error->message, __FILE__, __LINE__ );
126            return;
127        }
128
129        if ( $data->choices[0]->message->function_call->name == "keywords_for_text" ) {
130            $arguments = json_decode( $data->choices[0]->message->function_call->arguments );
131            $keywords = $arguments->keywords;
132            Logger::debug( "Chat GPT response", $keywords, __FILE__, __LINE__ );
133        } else {
134            Logger::debug( "INVALID Chat GPT response", $data->choices[0]->message, __FILE__, __LINE__ );
135        }
136
137        if ( empty( $keywords ) || !is_array( $keywords ) ) {
138            // there is no content herre.
139            if ( $requestContent == $event->data['newContent'] ) {
140                // Nothing has changed, return
141                return;
142            }
143
144            // The keywords are empty now.
145            $event->data['contentChanged'] = 1;
146            $event->data['newContent'] = $requestContent;
147            return;
148        }
149/*/
150        $keywords = [ "word1", "word2", "word3", "word4" ];
151//*/
152
153        $event->data['contentChanged'] = 1;
154        $event->data['newContent'] = '{{keywords>' . implode( ", ", $keywords ) . '}}' . "\n\n" . $requestContent;
155    }
156
157    /**
158     * Prints keywords to the meta header
159     * Event handler for TPL_METAHEADER_OUTPUT
160     *
161     * @see https://www.dokuwiki.org/devel:event:TPL_METAHEADER_OUTPUT
162     * @param Doku_Event $event Event object
163     * @param mixed $param optional parameter passed when event was registered
164     * @return void
165     * @author Ilya Lebedev <ilya@lebedev.net>
166     */
167    public function handleMetaheaderOutput(Doku_Event $event, $param) {
168        global $ID;
169        global $conf;
170
171        if (empty($event->data) || empty($event->data['meta'])) return; // nothing to do for us
172
173        //Check if keywords are exists
174        $kw = p_get_metadata($ID,'keywords');
175        if (empty($kw)) return;
176
177        for ($i=0;$i<sizeof($event->data['meta']);$i++) {
178            $h = &$event->data['meta'][$i];
179            if ('keywords' == $h['name']) {
180                $h['content'] .= $kw;
181                break;
182            }
183        }
184    }
185}
186
187