Skip to content

Commit

Permalink
Initial AI Module Added
Browse files Browse the repository at this point in the history
  • Loading branch information
daizeyao authored and daizeyao committed Oct 24, 2024
1 parent fde189f commit 9788b8e
Show file tree
Hide file tree
Showing 11 changed files with 2,889 additions and 1 deletion.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v2.7.3
v2.7.4
1 change: 1 addition & 0 deletions docs/init-dev-mac.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ docker run \
--name=hznuoj \
--restart=always \
-p 8877:80 \
-p 11434:11434 \
-v /var/hznuoj/data:/var/hznuoj/data \
-v "$PROJECT_DIR/hznuoj/web:/var/www/web" \
hznuoj/hznuoj:latest
Expand Down
65 changes: 65 additions & 0 deletions web/OJ/chat.php
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,
]);
104 changes: 104 additions & 0 deletions web/OJ/class/Class.ChatGPT.php
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);
}
}
127 changes: 127 additions & 0 deletions web/OJ/class/Class.DFA.php
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";
*/
Loading

0 comments on commit 9788b8e

Please sign in to comment.