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