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