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="请输入你的问题..." /> '; 28 $renderer->doc .= '<button id="' . hsc($btnId) . '">提交</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 function step1() { 42 result.innerHTML = "<em>步骤1:正在提取关键词...</em>"; 43 xhr = new XMLHttpRequest(); 44 xhr.open("POST", DOKU_BASE + "lib/exe/ajax.php", true); 45 xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 46 xhr.onreadystatechange = function() { 47 if(xhr.readyState === 4) { 48 if(!running) return; 49 if(xhr.status === 200) { 50 try { 51 var resp = JSON.parse(xhr.responseText); 52 if(resp && resp.ragasker_response) { 53 result.innerHTML = resp.ragasker_response; 54 lastKeywords = resp.keywords || ""; 55 if(resp.step === 1 && lastKeywords) step2(); 56 } else { 57 result.innerHTML = "<span style=\'color:red\'>API返回异常</span>"; 58 running = false; 59 btn.innerText = "提交"; 60 } 61 } catch(e) { 62 result.innerHTML = "<span style=\'color:red\'>解析响应失败</span>"; 63 running = false; 64 btn.innerText = "提交"; 65 } 66 } else { 67 result.innerHTML = "<span style=\'color:red\'>请求失败("+xhr.status+")</span>"; 68 running = false; 69 btn.innerText = "提交"; 70 } 71 } 72 }; 73 xhr.onerror = function(e) { 74 result.innerHTML = "<span style=\"color:red\">网络错误</span>"; 75 running = false; 76 btn.innerText = "提交"; 77 }; 78 xhr.send("call=ragasker_generate&ragasker_widget=1&prompt=" + encodeURIComponent(input.value) + "&step=1"); 79 } 80 function step2() { 81 result.innerHTML += "<br><em>步骤2:正在搜索页面...</em>"; 82 xhr = new XMLHttpRequest(); 83 xhr.open("POST", DOKU_BASE + "lib/exe/ajax.php", true); 84 xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 85 xhr.onreadystatechange = function() { 86 if(xhr.readyState === 4) { 87 if(!running) return; 88 if(xhr.status === 200) { 89 try { 90 var resp = JSON.parse(xhr.responseText); 91 if(resp && resp.ragasker_response) { 92 result.innerHTML += "<hr>" + resp.ragasker_response; 93 // 这里 linkList/contentList 是 JSON 字符串 94 lastLinkList = resp.linkList; 95 lastContentList = resp.contentList; 96 if(resp.step === 2 && lastLinkList && lastContentList) step3(); 97 } else { 98 result.innerHTML += "<span style=\'color:red\'>API返回异常</span>"; 99 running = false; 100 btn.innerText = "提交"; 101 } 102 } catch(e) { 103 result.innerHTML += "<span style=\'color:red\'>解析响应失败</span>"; 104 running = false; 105 btn.innerText = "提交"; 106 } 107 } else { 108 result.innerHTML += "<span style=\'color:red\'>请求失败("+xhr.status+")</span>"; 109 running = false; 110 btn.innerText = "提交"; 111 } 112 } 113 }; 114 xhr.onerror = function(e) { 115 result.innerHTML += "<span style=\"color:red\">网络错误</span>"; 116 running = false; 117 btn.innerText = "提交"; 118 }; 119 xhr.send( 120 "call=ragasker_generate&ragasker_widget=1&prompt=" + encodeURIComponent(input.value) + 121 "&step=2&keywords=" + encodeURIComponent(lastKeywords) 122 ); 123 } 124 function step3() { 125 result.innerHTML += "<hr><em>步骤3:AI总结回答...</em>"; 126 xhr = new XMLHttpRequest(); 127 xhr.open("POST", DOKU_BASE + "lib/exe/ajax.php", true); 128 xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 129 xhr.onreadystatechange = function() { 130 if(xhr.readyState === 4) { 131 if(!running) return; 132 if(xhr.status === 200) { 133 try { 134 var resp = JSON.parse(xhr.responseText); 135 if(resp && resp.ragasker_response) { 136 result.innerHTML += "<hr>" + resp.ragasker_response; 137 } else { 138 result.innerHTML += "<span style=\'color:red\'>API返回异常</span>"; 139 } 140 } catch(e) { 141 result.innerHTML += "<span style=\'color:red\'>解析响应失败</span>"; 142 } 143 } else { 144 result.innerHTML += "<span style=\'color:red\'>请求失败("+xhr.status+")</span>"; 145 } 146 running = false; 147 btn.innerText = "提交"; 148 } 149 }; 150 xhr.onerror = function(e) { 151 result.innerHTML += "<span style=\"color:red\">网络错误</span>"; 152 running = false; 153 btn.innerText = "提交"; 154 }; 155 // 这里 linkList/contentList 需保证为 JSON 字符串 156 xhr.send( 157 "call=ragasker_generate&ragasker_widget=1&prompt=" + encodeURIComponent(input.value) + 158 "&step=3&keywords=" + encodeURIComponent(lastKeywords) + 159 "&linkList=" + encodeURIComponent(lastLinkList) + 160 "&contentList=" + encodeURIComponent(lastContentList) 161 ); 162 } 163 if(btn && input && result) { 164 btn.addEventListener("click", function() { 165 if(running) { 166 // 终止 167 running = false; 168 if(xhr) xhr.abort(); 169 btn.innerText = "提交"; 170 result.innerHTML += "<br><span style=\'color:orange\'>已终止</span>"; 171 return; 172 } 173 var prompt = input.value; 174 if(!prompt) { result.innerHTML = "<span style=\'color:red\'>请输入内容</span>"; return; } 175 running = true; 176 btn.innerText = "终止"; 177 lastKeywords = ""; 178 lastLinkList = null; 179 lastContentList = null; 180 result.innerHTML = ""; 181 step1(); 182 }); 183 } 184 })(); 185 </script>'; 186 return true; 187 } 188 189 // callOpenAI 逻辑将迁移到 action 处理 190 // 保留接口以兼容 191 private function callOpenAI($prompt, $params = []) { 192 return ''; 193 } 194 195 private function formatResponse($text) { 196 // 转换 Markdown 到 HTML 197 $text = hsc($text); // HTML 安全转义 198 199 // 基础 Markdown 转换 200 $text = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $text); 201 $text = preg_replace('/\*(.+?)\*/', '<em>$1</em>', $text); 202 $text = preg_replace('/`(.+?)`/', '<code>$1</code>', $text); 203 204 // 转换换行 205 $text = nl2br($text); 206 207 return $text; 208 } 209} 210