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