xref: /plugin/ragasker/syntax.php (revision ee5a17d9fbcd1782f5b6e74c2d73d7240fd2f789)
1<?php
2require_once(__DIR__ . '/OpenAIHttpClient.php');
3
4class syntax_plugin_ragasker extends DokuWiki_Syntax_Plugin {
5
6    public function getType() { return 'substition'; }
7    public function getSort() { return 155; }
8
9    public function connectTo($mode) {
10        // 多种语法支持
11        $this->Lexer->addSpecialPattern('~~RAGASKER~~', $mode, 'plugin_ragasker');
12    }
13
14    public function handle($match, $state, $pos, Doku_Handler $handler) {
15        // 只传递唯一ID用于渲染
16        $uniqid = uniqid('ragasker_', true);
17        return [$uniqid, null];
18    }
19
20    public function render($mode, Doku_Renderer $renderer, $data) {
21        if($mode !== 'xhtml') return false;
22        list($uniqid, $_) = $data;
23        $inputId = $uniqid . '_input';
24        $btnId = $uniqid . '_btn';
25        $resultId = $uniqid . '_result';
26        $renderer->doc .= '<div class="openai-widget" style="border:1px solid #ccc;padding:10px;margin:10px 0;">';
27        $renderer->doc .= '<input type="text" id="' . hsc($inputId) . '" style="width:60%;" placeholder="' . hsc($this->getLang('input_placeholder')) . '" /> ';
28        $renderer->doc .= '<button id="' . hsc($btnId) . '">' . hsc($this->getLang('submit_btn')) . '</button>';
29        $renderer->doc .= '<div id="' . hsc($resultId) . '" style="margin-top:10px;"></div>';
30        $renderer->doc .= '</div>';
31        $renderer->doc .= '<script type="text/javascript">
32        (function(){
33            var btn = document.getElementById("' . hsc($btnId) . '");
34            var input = document.getElementById("' . hsc($inputId) . '");
35            var result = document.getElementById("' . hsc($resultId) . '");
36            var xhr = null;
37            var running = false;
38            var lastKeywords = "";
39            var lastLinkList = null;
40            var lastContentList = null;
41            // 多语言文本
42            var i18n = {
43                step1: "' . hsc(sprintf($this->getLang('step_title'), 1, $this->getLang('step_extracting'))) . '",
44                step2: "' . hsc(sprintf($this->getLang('step_title'), 2, $this->getLang('step_searching'))) . '",
45                step3: "' . hsc(sprintf($this->getLang('step_title'), 3, $this->getLang('step_summarizing'))) . '",
46                api_error: "' . hsc($this->getLang('error_api')) . '",
47                parse_error: "' . hsc($this->getLang('error_parse')) . '",
48                request_error: "' . hsc($this->getLang('error_request')) . '",
49                network_error: "' . hsc($this->getLang('error_network')) . '",
50                stop: "' . hsc($this->getLang('stop_btn')) . '",
51                submit: "' . hsc($this->getLang('submit_btn')) . '",
52                stopped: "' . hsc($this->getLang('stopped')) . '",
53                input_empty: "' . hsc($this->getLang('error_input_empty')) . '"
54            };
55            function step1() {
56                result.innerHTML = "<em>" + i18n.step1 + "</em>";
57                xhr = new XMLHttpRequest();
58                xhr.open("POST", DOKU_BASE + "lib/exe/ajax.php", true);
59                xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
60                xhr.onreadystatechange = function() {
61                    if(xhr.readyState === 4) {
62                        if(!running) return;
63                        if(xhr.status === 200) {
64                            console.log(xhr.responseText);
65                            try {
66                                var resp = JSON.parse(xhr.responseText);
67                                if(resp && resp.ragasker_response) {
68                                    result.innerHTML = resp.ragasker_response;
69                                    lastKeywords = resp.keywords || "";
70                                    if(resp.step === 1 && lastKeywords) step2();
71                                } else {
72                                    result.innerHTML = "<span style=\'color:red\'>" + i18n.api_error + "</span>";
73                                    running = false;
74                                    btn.innerText = i18n.submit;
75                                }
76                            } catch(e) {
77                                result.innerHTML = "<span style=\'color:red\'>" + i18n.parse_error + "</span>";
78                                running = false;
79                                btn.innerText = i18n.submit;
80                            }
81                        } else {
82                            result.innerHTML = "<span style=\'color:red\'>" + i18n.request_error + "("+xhr.status+")</span>";
83                            running = false;
84                            btn.innerText = i18n.submit;
85                        }
86                    }
87                };
88                xhr.onerror = function(e) {
89                    result.innerHTML = "<span style=\"color:red\">" + i18n.network_error + "</span>";
90                    running = false;
91                    btn.innerText = i18n.submit;
92                };
93                xhr.send("call=ragasker_generate&ragasker_widget=1&prompt=" + encodeURIComponent(input.value) + "&step=1");
94            }
95            function step2() {
96                result.innerHTML += "<br><em>" + i18n.step2 + "</em>";
97                xhr = new XMLHttpRequest();
98                xhr.open("POST", DOKU_BASE + "lib/exe/ajax.php", true);
99                xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
100                xhr.onreadystatechange = function() {
101                    if(xhr.readyState === 4) {
102                        if(!running) return;
103                        if(xhr.status === 200) {
104                            try {
105                                var resp = JSON.parse(xhr.responseText);
106                                if(resp && resp.ragasker_response) {
107                                    result.innerHTML += "<hr>" + resp.ragasker_response;
108                                    lastLinkList = resp.linkList;
109                                    lastContentList = resp.contentList;
110                                    if(resp.step === 2 && lastLinkList && lastContentList) step3();
111                                } else {
112                                    result.innerHTML += "<span style=\'color:red\'>" + i18n.api_error + "</span>";
113                                    running = false;
114                                    btn.innerText = i18n.submit;
115                                }
116                            } catch(e) {
117                                result.innerHTML += "<span style=\'color:red\'>" + i18n.parse_error + "</span>";
118                                running = false;
119                                btn.innerText = i18n.submit;
120                            }
121                        } else {
122                            result.innerHTML += "<span style=\'color:red\'>" + i18n.request_error + "("+xhr.status+")</span>";
123                            running = false;
124                            btn.innerText = i18n.submit;
125                        }
126                    }
127                };
128                xhr.onerror = function(e) {
129                    result.innerHTML += "<span style=\"color:red\">" + i18n.network_error + "</span>";
130                    running = false;
131                    btn.innerText = i18n.submit;
132                };
133                xhr.send(
134                    "call=ragasker_generate&ragasker_widget=1&prompt=" + encodeURIComponent(input.value) +
135                    "&step=2&keywords=" + encodeURIComponent(lastKeywords)
136                );
137            }
138            function step3() {
139                result.innerHTML += "<hr><em>" + i18n.step3 + "</em>";
140                xhr = new XMLHttpRequest();
141                xhr.open("POST", DOKU_BASE + "lib/exe/ajax.php", true);
142                xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
143                xhr.onreadystatechange = function() {
144                    if(xhr.readyState === 4) {
145                        if(!running) return;
146                        if(xhr.status === 200) {
147                            try {
148                                var resp = JSON.parse(xhr.responseText);
149                                if(resp && resp.ragasker_response) {
150                                    result.innerHTML += "<hr>" + resp.ragasker_response;
151                                } else {
152                                    result.innerHTML += "<span style=\'color:red\'>" + i18n.api_error + "</span>";
153                                }
154                            } catch(e) {
155                                result.innerHTML += "<span style=\'color:red\'>" + i18n.parse_error + "</span>";
156                            }
157                        } else {
158                            result.innerHTML += "<span style=\'color:red\'>" + i18n.request_error + "("+xhr.status+")</span>";
159                        }
160                        running = false;
161                        btn.innerText = i18n.submit;
162                    }
163                };
164                xhr.onerror = function(e) {
165                    result.innerHTML += "<span style=\"color:red\">" + i18n.network_error + "</span>";
166                    running = false;
167                    btn.innerText = i18n.submit;
168                };
169                xhr.send(
170                    "call=ragasker_generate&ragasker_widget=1&prompt=" + encodeURIComponent(input.value) +
171                    "&step=3&keywords=" + encodeURIComponent(lastKeywords) +
172                    "&linkList=" + encodeURIComponent(lastLinkList) +
173                    "&contentList=" + encodeURIComponent(lastContentList)
174                );
175            }
176            if(btn && input && result) {
177                btn.addEventListener("click", function() {
178                    if(running) {
179                        running = false;
180                        if(xhr) xhr.abort();
181                        btn.innerText = i18n.submit;
182                        result.innerHTML += "<br><span style=\'color:orange\'>" + i18n.stopped + "</span>";
183                        return;
184                    }
185                    var prompt = input.value;
186                    if(!prompt) { result.innerHTML = "<span style=\'color:red\'>" + i18n.input_empty + "</span>"; return; }
187                    running = true;
188                    btn.innerText = i18n.stop;
189                    lastKeywords = "";
190                    lastLinkList = null;
191                    lastContentList = null;
192                    result.innerHTML = "";
193                    step1();
194                });
195            }
196        })();
197        </script>';
198        return true;
199    }
200
201    // callOpenAI 逻辑将迁移到 action 处理
202    // 保留接口以兼容
203    private function callOpenAI($prompt, $params = []) {
204        return '';
205    }
206
207    private function formatResponse($text) {
208        // 转换 Markdown 到 HTML
209        $text = hsc($text); // HTML 安全转义
210
211        // 基础 Markdown 转换
212        $text = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $text);
213        $text = preg_replace('/\*(.+?)\*/', '<em>$1</em>', $text);
214        $text = preg_replace('/`(.+?)`/', '<code>$1</code>', $text);
215
216        // 转换换行
217        $text = nl2br($text);
218
219        return $text;
220    }
221}
222