15f0c5114SCharles Chan<?php 25f0c5114SCharles Chanrequire_once(__DIR__ . '/OpenAIHttpClient.php'); 35f0c5114SCharles Chan 45f0c5114SCharles Chanclass syntax_plugin_ragasker extends DokuWiki_Syntax_Plugin { 55f0c5114SCharles Chan 65f0c5114SCharles Chan public function getType() { return 'substition'; } 75f0c5114SCharles Chan public function getSort() { return 155; } 85f0c5114SCharles Chan 95f0c5114SCharles Chan public function connectTo($mode) { 105f0c5114SCharles Chan // 多种语法支持 115f0c5114SCharles Chan $this->Lexer->addSpecialPattern('~~RAGASKER~~', $mode, 'plugin_ragasker'); 125f0c5114SCharles Chan } 135f0c5114SCharles Chan 145f0c5114SCharles Chan public function handle($match, $state, $pos, Doku_Handler $handler) { 155f0c5114SCharles Chan // 只传递唯一ID用于渲染 165f0c5114SCharles Chan $uniqid = uniqid('ragasker_', true); 175f0c5114SCharles Chan return [$uniqid, null]; 185f0c5114SCharles Chan } 195f0c5114SCharles Chan 205f0c5114SCharles Chan public function render($mode, Doku_Renderer $renderer, $data) { 215f0c5114SCharles Chan if($mode !== 'xhtml') return false; 225f0c5114SCharles Chan list($uniqid, $_) = $data; 235f0c5114SCharles Chan $inputId = $uniqid . '_input'; 245f0c5114SCharles Chan $btnId = $uniqid . '_btn'; 255f0c5114SCharles Chan $resultId = $uniqid . '_result'; 265f0c5114SCharles Chan $renderer->doc .= '<div class="openai-widget" style="border:1px solid #ccc;padding:10px;margin:10px 0;">'; 27*ee5a17d9SCharles Chan $renderer->doc .= '<input type="text" id="' . hsc($inputId) . '" style="width:60%;" placeholder="' . hsc($this->getLang('input_placeholder')) . '" /> '; 28*ee5a17d9SCharles Chan $renderer->doc .= '<button id="' . hsc($btnId) . '">' . hsc($this->getLang('submit_btn')) . '</button>'; 295f0c5114SCharles Chan $renderer->doc .= '<div id="' . hsc($resultId) . '" style="margin-top:10px;"></div>'; 305f0c5114SCharles Chan $renderer->doc .= '</div>'; 315f0c5114SCharles Chan $renderer->doc .= '<script type="text/javascript"> 325f0c5114SCharles Chan (function(){ 335f0c5114SCharles Chan var btn = document.getElementById("' . hsc($btnId) . '"); 345f0c5114SCharles Chan var input = document.getElementById("' . hsc($inputId) . '"); 355f0c5114SCharles Chan var result = document.getElementById("' . hsc($resultId) . '"); 365f0c5114SCharles Chan var xhr = null; 375f0c5114SCharles Chan var running = false; 385f0c5114SCharles Chan var lastKeywords = ""; 395f0c5114SCharles Chan var lastLinkList = null; 405f0c5114SCharles Chan var lastContentList = null; 41*ee5a17d9SCharles Chan // 多语言文本 42*ee5a17d9SCharles Chan var i18n = { 43*ee5a17d9SCharles Chan step1: "' . hsc(sprintf($this->getLang('step_title'), 1, $this->getLang('step_extracting'))) . '", 44*ee5a17d9SCharles Chan step2: "' . hsc(sprintf($this->getLang('step_title'), 2, $this->getLang('step_searching'))) . '", 45*ee5a17d9SCharles Chan step3: "' . hsc(sprintf($this->getLang('step_title'), 3, $this->getLang('step_summarizing'))) . '", 46*ee5a17d9SCharles Chan api_error: "' . hsc($this->getLang('error_api')) . '", 47*ee5a17d9SCharles Chan parse_error: "' . hsc($this->getLang('error_parse')) . '", 48*ee5a17d9SCharles Chan request_error: "' . hsc($this->getLang('error_request')) . '", 49*ee5a17d9SCharles Chan network_error: "' . hsc($this->getLang('error_network')) . '", 50*ee5a17d9SCharles Chan stop: "' . hsc($this->getLang('stop_btn')) . '", 51*ee5a17d9SCharles Chan submit: "' . hsc($this->getLang('submit_btn')) . '", 52*ee5a17d9SCharles Chan stopped: "' . hsc($this->getLang('stopped')) . '", 53*ee5a17d9SCharles Chan input_empty: "' . hsc($this->getLang('error_input_empty')) . '" 54*ee5a17d9SCharles Chan }; 555f0c5114SCharles Chan function step1() { 56*ee5a17d9SCharles Chan result.innerHTML = "<em>" + i18n.step1 + "</em>"; 575f0c5114SCharles Chan xhr = new XMLHttpRequest(); 585f0c5114SCharles Chan xhr.open("POST", DOKU_BASE + "lib/exe/ajax.php", true); 595f0c5114SCharles Chan xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 605f0c5114SCharles Chan xhr.onreadystatechange = function() { 615f0c5114SCharles Chan if(xhr.readyState === 4) { 625f0c5114SCharles Chan if(!running) return; 635f0c5114SCharles Chan if(xhr.status === 200) { 64*ee5a17d9SCharles Chan console.log(xhr.responseText); 655f0c5114SCharles Chan try { 665f0c5114SCharles Chan var resp = JSON.parse(xhr.responseText); 675f0c5114SCharles Chan if(resp && resp.ragasker_response) { 685f0c5114SCharles Chan result.innerHTML = resp.ragasker_response; 695f0c5114SCharles Chan lastKeywords = resp.keywords || ""; 705f0c5114SCharles Chan if(resp.step === 1 && lastKeywords) step2(); 715f0c5114SCharles Chan } else { 72*ee5a17d9SCharles Chan result.innerHTML = "<span style=\'color:red\'>" + i18n.api_error + "</span>"; 735f0c5114SCharles Chan running = false; 74*ee5a17d9SCharles Chan btn.innerText = i18n.submit; 755f0c5114SCharles Chan } 765f0c5114SCharles Chan } catch(e) { 77*ee5a17d9SCharles Chan result.innerHTML = "<span style=\'color:red\'>" + i18n.parse_error + "</span>"; 785f0c5114SCharles Chan running = false; 79*ee5a17d9SCharles Chan btn.innerText = i18n.submit; 805f0c5114SCharles Chan } 815f0c5114SCharles Chan } else { 82*ee5a17d9SCharles Chan result.innerHTML = "<span style=\'color:red\'>" + i18n.request_error + "("+xhr.status+")</span>"; 835f0c5114SCharles Chan running = false; 84*ee5a17d9SCharles Chan btn.innerText = i18n.submit; 855f0c5114SCharles Chan } 865f0c5114SCharles Chan } 875f0c5114SCharles Chan }; 885f0c5114SCharles Chan xhr.onerror = function(e) { 89*ee5a17d9SCharles Chan result.innerHTML = "<span style=\"color:red\">" + i18n.network_error + "</span>"; 905f0c5114SCharles Chan running = false; 91*ee5a17d9SCharles Chan btn.innerText = i18n.submit; 925f0c5114SCharles Chan }; 935f0c5114SCharles Chan xhr.send("call=ragasker_generate&ragasker_widget=1&prompt=" + encodeURIComponent(input.value) + "&step=1"); 945f0c5114SCharles Chan } 955f0c5114SCharles Chan function step2() { 96*ee5a17d9SCharles Chan result.innerHTML += "<br><em>" + i18n.step2 + "</em>"; 975f0c5114SCharles Chan xhr = new XMLHttpRequest(); 985f0c5114SCharles Chan xhr.open("POST", DOKU_BASE + "lib/exe/ajax.php", true); 995f0c5114SCharles Chan xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 1005f0c5114SCharles Chan xhr.onreadystatechange = function() { 1015f0c5114SCharles Chan if(xhr.readyState === 4) { 1025f0c5114SCharles Chan if(!running) return; 1035f0c5114SCharles Chan if(xhr.status === 200) { 1045f0c5114SCharles Chan try { 1055f0c5114SCharles Chan var resp = JSON.parse(xhr.responseText); 1065f0c5114SCharles Chan if(resp && resp.ragasker_response) { 1075f0c5114SCharles Chan result.innerHTML += "<hr>" + resp.ragasker_response; 1085f0c5114SCharles Chan lastLinkList = resp.linkList; 1095f0c5114SCharles Chan lastContentList = resp.contentList; 1105f0c5114SCharles Chan if(resp.step === 2 && lastLinkList && lastContentList) step3(); 1115f0c5114SCharles Chan } else { 112*ee5a17d9SCharles Chan result.innerHTML += "<span style=\'color:red\'>" + i18n.api_error + "</span>"; 1135f0c5114SCharles Chan running = false; 114*ee5a17d9SCharles Chan btn.innerText = i18n.submit; 1155f0c5114SCharles Chan } 1165f0c5114SCharles Chan } catch(e) { 117*ee5a17d9SCharles Chan result.innerHTML += "<span style=\'color:red\'>" + i18n.parse_error + "</span>"; 1185f0c5114SCharles Chan running = false; 119*ee5a17d9SCharles Chan btn.innerText = i18n.submit; 1205f0c5114SCharles Chan } 1215f0c5114SCharles Chan } else { 122*ee5a17d9SCharles Chan result.innerHTML += "<span style=\'color:red\'>" + i18n.request_error + "("+xhr.status+")</span>"; 1235f0c5114SCharles Chan running = false; 124*ee5a17d9SCharles Chan btn.innerText = i18n.submit; 1255f0c5114SCharles Chan } 1265f0c5114SCharles Chan } 1275f0c5114SCharles Chan }; 1285f0c5114SCharles Chan xhr.onerror = function(e) { 129*ee5a17d9SCharles Chan result.innerHTML += "<span style=\"color:red\">" + i18n.network_error + "</span>"; 1305f0c5114SCharles Chan running = false; 131*ee5a17d9SCharles Chan btn.innerText = i18n.submit; 1325f0c5114SCharles Chan }; 1335f0c5114SCharles Chan xhr.send( 1345f0c5114SCharles Chan "call=ragasker_generate&ragasker_widget=1&prompt=" + encodeURIComponent(input.value) + 1355f0c5114SCharles Chan "&step=2&keywords=" + encodeURIComponent(lastKeywords) 1365f0c5114SCharles Chan ); 1375f0c5114SCharles Chan } 1385f0c5114SCharles Chan function step3() { 139*ee5a17d9SCharles Chan result.innerHTML += "<hr><em>" + i18n.step3 + "</em>"; 1405f0c5114SCharles Chan xhr = new XMLHttpRequest(); 1415f0c5114SCharles Chan xhr.open("POST", DOKU_BASE + "lib/exe/ajax.php", true); 1425f0c5114SCharles Chan xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 1435f0c5114SCharles Chan xhr.onreadystatechange = function() { 1445f0c5114SCharles Chan if(xhr.readyState === 4) { 1455f0c5114SCharles Chan if(!running) return; 1465f0c5114SCharles Chan if(xhr.status === 200) { 1475f0c5114SCharles Chan try { 1485f0c5114SCharles Chan var resp = JSON.parse(xhr.responseText); 1495f0c5114SCharles Chan if(resp && resp.ragasker_response) { 1505f0c5114SCharles Chan result.innerHTML += "<hr>" + resp.ragasker_response; 1515f0c5114SCharles Chan } else { 152*ee5a17d9SCharles Chan result.innerHTML += "<span style=\'color:red\'>" + i18n.api_error + "</span>"; 1535f0c5114SCharles Chan } 1545f0c5114SCharles Chan } catch(e) { 155*ee5a17d9SCharles Chan result.innerHTML += "<span style=\'color:red\'>" + i18n.parse_error + "</span>"; 1565f0c5114SCharles Chan } 1575f0c5114SCharles Chan } else { 158*ee5a17d9SCharles Chan result.innerHTML += "<span style=\'color:red\'>" + i18n.request_error + "("+xhr.status+")</span>"; 1595f0c5114SCharles Chan } 1605f0c5114SCharles Chan running = false; 161*ee5a17d9SCharles Chan btn.innerText = i18n.submit; 1625f0c5114SCharles Chan } 1635f0c5114SCharles Chan }; 1645f0c5114SCharles Chan xhr.onerror = function(e) { 165*ee5a17d9SCharles Chan result.innerHTML += "<span style=\"color:red\">" + i18n.network_error + "</span>"; 1665f0c5114SCharles Chan running = false; 167*ee5a17d9SCharles Chan btn.innerText = i18n.submit; 1685f0c5114SCharles Chan }; 1695f0c5114SCharles Chan xhr.send( 1705f0c5114SCharles Chan "call=ragasker_generate&ragasker_widget=1&prompt=" + encodeURIComponent(input.value) + 1715f0c5114SCharles Chan "&step=3&keywords=" + encodeURIComponent(lastKeywords) + 1725f0c5114SCharles Chan "&linkList=" + encodeURIComponent(lastLinkList) + 1735f0c5114SCharles Chan "&contentList=" + encodeURIComponent(lastContentList) 1745f0c5114SCharles Chan ); 1755f0c5114SCharles Chan } 1765f0c5114SCharles Chan if(btn && input && result) { 1775f0c5114SCharles Chan btn.addEventListener("click", function() { 1785f0c5114SCharles Chan if(running) { 1795f0c5114SCharles Chan running = false; 1805f0c5114SCharles Chan if(xhr) xhr.abort(); 181*ee5a17d9SCharles Chan btn.innerText = i18n.submit; 182*ee5a17d9SCharles Chan result.innerHTML += "<br><span style=\'color:orange\'>" + i18n.stopped + "</span>"; 1835f0c5114SCharles Chan return; 1845f0c5114SCharles Chan } 1855f0c5114SCharles Chan var prompt = input.value; 186*ee5a17d9SCharles Chan if(!prompt) { result.innerHTML = "<span style=\'color:red\'>" + i18n.input_empty + "</span>"; return; } 1875f0c5114SCharles Chan running = true; 188*ee5a17d9SCharles Chan btn.innerText = i18n.stop; 1895f0c5114SCharles Chan lastKeywords = ""; 1905f0c5114SCharles Chan lastLinkList = null; 1915f0c5114SCharles Chan lastContentList = null; 1925f0c5114SCharles Chan result.innerHTML = ""; 1935f0c5114SCharles Chan step1(); 1945f0c5114SCharles Chan }); 1955f0c5114SCharles Chan } 1965f0c5114SCharles Chan })(); 1975f0c5114SCharles Chan </script>'; 1985f0c5114SCharles Chan return true; 1995f0c5114SCharles Chan } 2005f0c5114SCharles Chan 2015f0c5114SCharles Chan // callOpenAI 逻辑将迁移到 action 处理 2025f0c5114SCharles Chan // 保留接口以兼容 2035f0c5114SCharles Chan private function callOpenAI($prompt, $params = []) { 2045f0c5114SCharles Chan return ''; 2055f0c5114SCharles Chan } 2065f0c5114SCharles Chan 2075f0c5114SCharles Chan private function formatResponse($text) { 2085f0c5114SCharles Chan // 转换 Markdown 到 HTML 2095f0c5114SCharles Chan $text = hsc($text); // HTML 安全转义 2105f0c5114SCharles Chan 2115f0c5114SCharles Chan // 基础 Markdown 转换 2125f0c5114SCharles Chan $text = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $text); 2135f0c5114SCharles Chan $text = preg_replace('/\*(.+?)\*/', '<em>$1</em>', $text); 2145f0c5114SCharles Chan $text = preg_replace('/`(.+?)`/', '<code>$1</code>', $text); 2155f0c5114SCharles Chan 2165f0c5114SCharles Chan // 转换换行 2175f0c5114SCharles Chan $text = nl2br($text); 2185f0c5114SCharles Chan 2195f0c5114SCharles Chan return $text; 2205f0c5114SCharles Chan } 2215f0c5114SCharles Chan} 222