forked from hznuoj-dev/hznuoj
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
daizeyao
authored and
daizeyao
committed
Oct 24, 2024
1 parent
fde189f
commit 9788b8e
Showing
11 changed files
with
2,889 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
v2.7.3 | ||
v2.7.4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
<?php | ||
|
||
// 设置时区为东八区 | ||
date_default_timezone_set('PRC'); | ||
|
||
// 这行代码用于关闭输出缓冲。关闭后,脚本的输出将立即发送到浏览器,而不是等待缓冲区填满或脚本执行完毕。 | ||
ini_set('output_buffering', 'off'); | ||
|
||
// 这行代码禁用了 zlib 压缩。通常情况下,启用 zlib 压缩可以减小发送到浏览器的数据量,但对于服务器发送事件来说,实时性更重要,因此需要禁用压缩。 | ||
ini_set('zlib.output_compression', false); | ||
|
||
// 这行代码使用循环来清空所有当前激活的输出缓冲区。ob_end_flush() 函数会刷新并关闭最内层的输出缓冲区,@ 符号用于抑制可能出现的错误或警告。 | ||
while (@ob_end_flush()) { | ||
} | ||
|
||
// 这行代码设置 HTTP 响应的 Content-Type 为 text/event-stream,这是服务器发送事件(SSE)的 MIME 类型。 | ||
header('Content-Type: text/event-stream'); | ||
|
||
// 这行代码设置 HTTP 响应的 Cache-Control 为 no-cache,告诉浏览器不要缓存此响应。 | ||
header('Cache-Control: no-cache'); | ||
|
||
// 这行代码设置 HTTP 响应的自定义头部 X-Accel-Buffering 为 no,用于禁用某些代理或 Web 服务器(如 Nginx)的缓冲。 | ||
header('X-Accel-Buffering: no'); | ||
|
||
require_once './include/static.php'; | ||
|
||
// 引入敏感词检测类 | ||
require './class/Class.DFA.php'; | ||
|
||
// 引入流处理类 | ||
require './class/Class.StreamHandler.php'; | ||
|
||
// 引入调用 OpenAI 接口类 | ||
require './class/Class.ChatGPT.php'; | ||
|
||
echo 'data: ' . json_encode(['time' => date('Y-m-d H:i:s'), 'content' => '']) . PHP_EOL . PHP_EOL; | ||
flush(); | ||
|
||
$question = urldecode($_GET['q'] ?? ''); | ||
if (empty($question)) { | ||
echo "event: close" . PHP_EOL; | ||
echo "data: Connection closed" . PHP_EOL . PHP_EOL; | ||
flush(); | ||
exit(); | ||
} | ||
$question = str_ireplace('{[$add$]}', '+', $question); | ||
|
||
// api 和 模型选择 | ||
$chat = new OllamaChat( | ||
"http://$DB_HOST:11434/api/generate", | ||
"$AI_MODEL" | ||
); | ||
|
||
$DOCUMENT_ROOT = $_SERVER['DOCUMENT_ROOT']; | ||
$dfa = new DFA([ | ||
'words_file' => "$DOCUMENT_ROOT/OJ/plugins/code-helper/dict.txt", | ||
]); | ||
$chat->set_dfa($dfa); | ||
|
||
|
||
// 开始提问 | ||
$chat->qa([ | ||
'system' => '你是HznuOnlineJudge的智能代码助手,只负责和代码相关的问题', | ||
'question' => $question, | ||
]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
<?php | ||
|
||
class OllamaChat | ||
{ | ||
private $api_url = ''; | ||
private $streamHandler; | ||
private $question; | ||
private $dfa = NULL; | ||
private $check_sensitive = TRUE; | ||
private $model = ''; | ||
|
||
public function __construct($url, $model) | ||
{ | ||
$this->api_url = $url; | ||
$this->model = $model; | ||
} | ||
|
||
public function set_dfa(&$dfa) | ||
{ | ||
$this->dfa = $dfa; | ||
if (!empty($this->dfa) && $this->dfa->is_available()) { | ||
$this->check_sensitive = TRUE; | ||
} | ||
} | ||
|
||
public function qa($params) | ||
{ | ||
|
||
$this->question = $params['question']; | ||
$this->streamHandler = new StreamHandler([ | ||
'qmd5' => md5($this->question . '' . time()) | ||
]); | ||
if ($this->check_sensitive) { | ||
$this->streamHandler->set_dfa($this->dfa); | ||
} | ||
|
||
// 开启检测且提问包含敏感词 | ||
if ($this->check_sensitive && $this->dfa->containsSensitiveWords($this->question)) { | ||
$this->streamHandler->end('您的问题不合适,AI暂时无法回答'); | ||
return; | ||
} | ||
|
||
// 根据Ollama API的要求构建请求正文 | ||
$json = json_encode([ | ||
'prompt' => $this->question, | ||
'model' => $this->model, | ||
]); | ||
|
||
$headers = array( | ||
"Content-Type: application/json", | ||
); | ||
|
||
$this->ollamaApiCall($json, $headers); | ||
} | ||
|
||
private function buildCurlCommand($json, $headers) | ||
{ | ||
$command = "curl"; | ||
|
||
// 添加 URL | ||
$command .= " '" . $this->api_url . "'"; | ||
|
||
// 添加请求头 | ||
foreach ($headers as $header) { | ||
$command .= " -H '" . str_replace("'", "\'", $header) . "'"; | ||
} | ||
|
||
// 添加 POST 数据 | ||
if ($json) { | ||
$command .= " -d '" . str_replace("'", "\'", $json) . "'"; | ||
} | ||
|
||
// 你可以继续添加其他 cURL 选项,如需要 | ||
|
||
return $command; | ||
} | ||
|
||
private function ollamaApiCall($json, $headers) | ||
{ // 修改后的方法名 | ||
$ch = curl_init(); | ||
curl_setopt($ch, CURLOPT_URL, $this->api_url); | ||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | ||
curl_setopt($ch, CURLOPT_HEADER, false); | ||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); | ||
// curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 如果不是HTTPS请求,可以注释或删除此行 | ||
// curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 如果不是HTTPS请求,可以注释或删除此行 | ||
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); | ||
curl_setopt($ch, CURLOPT_POSTFIELDS, $json); | ||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); | ||
|
||
curl_setopt($ch, CURLOPT_WRITEFUNCTION, [$this->streamHandler, 'callback']); | ||
|
||
// $curlCommand = $this->buildCurlCommand($json, $headers); | ||
// echo $curlCommand . PHP_EOL; | ||
|
||
$response = curl_exec($ch); | ||
|
||
if (curl_errno($ch)) { | ||
file_put_contents('./log/curl.error.log', curl_error($ch) . PHP_EOL . PHP_EOL, FILE_APPEND); | ||
} | ||
|
||
curl_close($ch); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
|
||
<?php | ||
|
||
class DFA | ||
{ | ||
private $root; | ||
private $words_file; | ||
private $words_count = 0; | ||
|
||
public function __construct($params) | ||
{ | ||
$this->words_file = $params['words_file'] ?? ''; | ||
|
||
$this->root = new DFA_Node(); | ||
|
||
$this->load_words_file(); | ||
} | ||
|
||
private function load_words_file(){ | ||
if(!file_exists($this->words_file)){ | ||
echo "words file not found: $this->words_file\n"; | ||
return; | ||
} | ||
$lines = file($this->words_file); | ||
foreach ($lines as $line) { | ||
$words = preg_split('/\s+/', trim($line)); | ||
foreach ($words as $word) { | ||
$word = trim($word); | ||
if (empty($word)) { | ||
continue; | ||
} | ||
$this->words_count += 1; | ||
$this->addWord($word); | ||
} | ||
} | ||
} | ||
|
||
public function is_available(){ | ||
return $this->words_count>0; | ||
} | ||
|
||
public function addWord($word) | ||
{ | ||
$node = $this->root; | ||
for ($i = 0; $i < strlen($word); $i++) { | ||
$char = $word[$i]; | ||
if (!isset($node->children[$char])) { | ||
$node->children[$char] = new DFA_Node(); | ||
} | ||
$node = $node->children[$char]; | ||
} | ||
$node->isEndOfWord = true; | ||
} | ||
|
||
public function replaceWords($text) | ||
{ | ||
$result = ''; | ||
$length = strlen($text); | ||
for ($i = 0; $i < $length;) { | ||
$node = $this->root; | ||
$j = $i; | ||
$lastMatched = -1; | ||
while ($j < $length && isset($node->children[$text[$j]])) { | ||
$node = $node->children[$text[$j]]; | ||
if ($node->isEndOfWord) { | ||
$lastMatched = $j; | ||
} | ||
$j++; | ||
} | ||
|
||
if ($lastMatched >= 0) { | ||
$result .= '\*\*\*'; | ||
$i = $lastMatched + 1; | ||
} else { | ||
$result .= $text[$i]; | ||
$i++; | ||
} | ||
} | ||
return $result; | ||
} | ||
|
||
public function containsSensitiveWords($text) | ||
{ | ||
$length = strlen($text); | ||
for ($i = 0; $i < $length;) { | ||
$node = $this->root; | ||
$j = $i; | ||
while ($j < $length && isset($node->children[$text[$j]])) { | ||
$node = $node->children[$text[$j]]; | ||
if ($node->isEndOfWord) { | ||
return true; | ||
} | ||
$j++; | ||
} | ||
$i++; | ||
} | ||
return false; | ||
} | ||
} | ||
|
||
class DFA_Node | ||
{ | ||
public $isEndOfWord; | ||
public $children; | ||
|
||
public function __construct() | ||
{ | ||
$this->isEndOfWord = false; | ||
$this->children = []; | ||
} | ||
} | ||
|
||
|
||
|
||
/* | ||
$inputText = "需要检测的句子"; | ||
$isContain = $dfa->containsSensitiveWords($inputText); | ||
echo "Original Text: \n" . $inputText . "\n"; | ||
echo "isContain: " . json_encode($isContain) . "\n"; | ||
$outputText = $dfa->replaceWords($inputText); | ||
echo "Original Text: \n" . $inputText . "\n"; | ||
echo "Replaced Text: \n" . $outputText . "\n"; | ||
*/ |
Oops, something went wrong.