feat(llm): 启用AI点评功能并新增AI聊天API接口,自动创建相关数据库表

This commit is contained in:
jieer
2026-05-29 17:16:26 +08:00
parent d60458ad35
commit 88706f5d78
7 changed files with 1033 additions and 80 deletions

View File

@@ -215,40 +215,104 @@
$('#llm-review-error').hide();
$('#llm-review-result').hide();
$.ajax({
type: 'GET',
url: 'llm-review.php?sid=<?php echo intval($id); ?>',
dataType: 'json',
timeout: 60000,
success: function(data){
btn.removeClass('loading');
$('#llm-review-loading').hide();
if(data.error){
$('#llm-review-error-msg').text(data.error);
$('#llm-review-error').show();
btn.prop('disabled', false);
} else if(data.review){
$('#llm-review-result').show();
var container = document.getElementById('llm-review-content');
// 先以纯文本放入,再用 Vditor 渲染为 Markdown HTML
container.textContent = data.review;
if(typeof HustOJVditor !== 'undefined' && HustOJVditor.renderMarkdownBlocks){
HustOJVditor.renderMarkdownBlocks('#llm-review-content', {
useTextContent: true
});
}
btn.hide();
var fullText = "";
var container = document.getElementById('llm-review-content');
var resultDiv = document.getElementById('llm-review-result');
var reader = null;
var decoder = new TextDecoder();
// 解析 SSE 事件
var sseBuffer = "";
function processSSE(text) {
sseBuffer += text;
var lines = sseBuffer.split('\n');
sseBuffer = lines.pop(); // 保留未完成的行
var currentEvent = "";
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.indexOf('event: ') === 0) {
currentEvent = line.substring(7);
} else if (line.indexOf('data: ') === 0) {
var dataStr = line.substring(6);
try {
var data = JSON.parse(dataStr);
if (currentEvent === 'chunk') {
fullText += data.text;
container.textContent = fullText;
resultDiv.style.display = 'block';
} else if (currentEvent === 'cached') {
fullText = data.text;
container.textContent = fullText;
resultDiv.style.display = 'block';
} else if (currentEvent === 'done') {
finishReview();
} else if (currentEvent === 'error') {
showError(data.message || '未知错误');
}
} catch(e) {}
currentEvent = "";
}
},
error: function(xhr, status){
btn.removeClass('loading').prop('disabled', false);
$('#llm-review-loading').hide();
var msg = '请求失败';
if(status === 'timeout') msg = '请求超时,请稍后重试';
$('#llm-review-error-msg').text(msg);
$('#llm-review-error').show();
}
});
}
function finishReview() {
btn.removeClass('loading').hide();
document.getElementById('llm-review-loading').style.display = 'none';
resultDiv.style.display = 'block';
// 渲染 Markdown
if (fullText && typeof HustOJVditor !== 'undefined' && HustOJVditor.renderMarkdownBlocks) {
HustOJVditor.renderMarkdownBlocks('#llm-review-content', { useTextContent: true });
}
}
function showError(msg) {
btn.removeClass('loading').prop('disabled', false);
document.getElementById('llm-review-loading').style.display = 'none';
document.getElementById('llm-review-error-msg').textContent = msg;
document.getElementById('llm-review-error').style.display = 'block';
}
if (typeof fetch === 'undefined') {
showError('浏览器不支持,请升级浏览器');
return;
}
fetch('llm-review.php?sid=<?php echo intval($id); ?>')
.then(function(response) {
if (!response.ok) {
return response.text().then(function(text) {
try {
var d = JSON.parse(text);
showError(d.error || '请求失败 (HTTP ' + response.status + ')');
} catch(e) {
showError('请求失败 (HTTP ' + response.status + ')');
}
});
}
reader = response.body.getReader();
function read() {
reader.read().then(function(result) {
if (result.done) {
if (fullText) finishReview();
return;
}
var text = decoder.decode(result.value, { stream: true });
processSSE(text);
read();
}).catch(function(err) {
if (fullText) {
finishReview();
} else {
showError('读取响应失败: ' + err.message);
}
});
}
read();
})
.catch(function(err) {
showError('网络请求失败: ' + err.message);
});
}
</script>
<?php } ?>