Files
hustoj/web/template/syzoj/reinfo.php
T
klarkxy 2b699a185b ♻️ refactor(reinfo): 简化判错提示并新增自动刷新
- 移除 PHP 中冗余的 verdict_hints 数组及其填充逻辑
- 移除模板中 friendly-explain 区与 errexp-list 错误模式匹配脚本(28 条规则)
- 标题提示简化为单行 verdict-tip,详细的修复建议交由 AI 点评
- 调整页面顺序:AI 点评区提前,原始错误折叠下移
- 新增判题中自动刷新状态指示:轮询 status-ajax.php,完成后自动 reload,并加入后台标签暂停与退避策略
2026-07-08 21:04:50 +08:00

543 lines
20 KiB
PHP

<?php $show_title=$id." - $MSG_ERROR_INFO - $OJ_NAME"; ?>
<?php include("template/$OJ_TEMPLATE/header.php");?>
<script src="include/vditor-adapter.js"></script>
<script src="template/<?php echo $OJ_TEMPLATE?>/js/textFit.min.js"></script>
<style>
.single-subtask {
box-shadow: none !important;
}
.single-subtask > .title {
display: none;
}
.single-subtask > .content {
padding: 0 !important;
}
.accordion > .content > .accordion {
margin-top: 0;
margin-bottom: 0;
}
.accordion > .content > .accordion > .content {
margin-top: 0;
margin-bottom: 14px;
}
.accordion > .content > .accordion > .content > :last-child {
margin-bottom: -10px !important;
}
/* === reinfo 友好化样式 === */
#result-banner {
margin-bottom: 18px;
border-left: 5px solid;
}
#result-banner.green { border-left-color: #21ba45; }
#result-banner.red { border-left-color: #db2828; }
#result-banner.yellow{ border-left-color: #fbbd08; }
#result-banner.blue { border-left-color: #2185d0; }
#result-banner.grey { border-left-color: #767676; }
#result-banner h2.ui.header {
margin: 0;
}
#result-banner .verdict-stats {
margin-top: 6px;
font-size: 0.95em;
color: rgba(0,0,0,0.6);
}
#result-banner .verdict-stats .stat {
margin-right: 16px;
}
#result-banner .verdict-stats i.icon {
margin-right: 4px;
}
#verdict-tip {
margin-bottom: 18px;
font-size: 1.05em;
padding: 8px 12px;
background: rgba(33,150,243,0.08);
border-radius: 6px;
border-left: 3px solid #2185d0;
}
#raw-info-accordion {
margin-bottom: 18px;
}
#raw-info-accordion > .content {
padding: 8px 14px;
}
#raw-info-accordion pre#errtxt {
margin: 0;
padding: 12px;
background: #f7f7f9;
border-radius: 4px;
font-family: 'Fira Mono', 'Cascadia Code', Consolas, monospace;
font-size: 0.9em;
white-space: pre-wrap;
word-break: break-all;
}
#raw-info-accordion .title .dedup-label {
margin-left: 8px;
}
#raw-info-accordion .empty-notice {
color: #999;
font-style: italic;
}
.quick-actions {
text-align: center;
margin-top: 24px;
margin-bottom: 12px;
}
.quick-actions .ui.buttons {
box-shadow: none;
}
/* AI 点评区 */
#llm-section {
margin-top: 20px;
margin-bottom: 18px;
}
#llm-section .ui.header {
margin-top: 0;
}
#llm-review-result {
word-break: break-word;
}
/* 错误解释列表(由 JS explain() 填充) - 已弃用,由 AI 点评替代 */
/* 自动刷新状态 */
i.icon.spinning {
animation: spin 1.2s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
#auto-refresh-status {
color: #2185d0;
font-weight: 600;
}
</style>
<div class="padding">
<?php
// 兼容老变量:$row 是 solution 行(reinfo.php 已查询过)
$verdict_name = isset($judge_result[$row['result']]) ? $judge_result[$row['result']] : (isset($MSG_UNKNOWN) ? $MSG_UNKNOWN : 'Unknown');
$verdict_lang = isset($language_name[$lang]) ? $language_name[$lang] : '?';
$verdict_memory = isset($row['memory']) ? intval($row['memory']) : 0;
$verdict_time = isset($row['time']) ? intval($row['time']) : 0;
$verdict_mark = isset($mark) ? $mark : (isset($row['pass_rate']) ? round($row['pass_rate']*100, 1) : 0);
?>
<!-- 1. 顶部判题结果横幅 -->
<div class="ui segment <?php echo isset($verdict_color_class) ? $verdict_color_class : 'blue'; ?>" id="result-banner">
<h2 class="ui header">
<i class="<?php echo isset($result_icon) ? $result_icon : 'info circle' ?> icon" style="color: inherit;"></i>
<div class="content">
<?php echo htmlspecialchars($verdict_name, ENT_QUOTES, 'UTF-8') ?>
<div class="verdict-stats">
<span class="stat"><i class="chart bar icon"></i><?php echo $MSG_MARK ?>: <?php echo $verdict_mark ?>%</span>
<span class="stat"><i class="microchip icon"></i><?php echo $verdict_memory ?> KB</span>
<span class="stat"><i class="clock icon"></i><?php echo $verdict_time ?> ms</span>
<span class="stat"><i class="code icon"></i><?php echo htmlspecialchars($verdict_lang, ENT_QUOTES, 'UTF-8') ?></span>
<span class="stat" id="auto-refresh-status" style="display:none;">
<i class="refresh icon spinning"></i> <span id="auto-refresh-text"></span>
</span>
</div>
</div>
</h2>
</div>
<!-- 2. 判题结果标题提示(仅一句话,具体修复建议交给 AI) -->
<div class="ui info message" id="verdict-tip">
<i class="info circle icon"></i>
<?php echo isset($verdict_tip) ? htmlspecialchars($verdict_tip, ENT_QUOTES, 'UTF-8') : ''; ?>
</div>
<!-- 3. AI 点评区(直接显示按钮,提到原始错误上面) -->
<?php if (isset($OJ_LLM_ENABLED) && $OJ_LLM_ENABLED) { ?>
<div class="ui raised segment" id="llm-section">
<h3 class="ui header">
<i class="magic icon"></i>
<div class="content">
AI 智能点评
<div class="sub header"><?php echo isset($MSG_AI_REVIEW_DESC) ? $MSG_AI_REVIEW_DESC : '基于你的代码和判题结果分析'; ?></div>
</div>
</h3>
<button class="ui labeled icon primary button" id="llm-review-btn" onclick="fetchLLMReview()">
<i class="magic icon"></i> 获取 AI 点评
</button>
<div id="llm-review-loading" class="ui active inline text loader" style="display:none; margin-left: 1em;">
AI 正在分析你的代码,请稍候...
</div>
<div id="llm-review-result" style="display:none; margin-top: 15px;">
<div class="ui existing segment" id="llm-review-content"></div>
</div>
<div id="llm-review-error" style="display:none; margin-top: 15px;">
<div class="ui negative message">
<p id="llm-review-error-msg"></p>
</div>
</div>
</div>
<script>
function fetchLLMReview(){
var btn = $('#llm-review-btn');
btn.prop('disabled', true).addClass('loading');
$('#llm-review-loading').show();
$('#llm-review-error').hide();
$('#llm-review-result').hide();
var fullText = "";
var container = document.getElementById('llm-review-content');
var resultDiv = document.getElementById('llm-review-result');
var reader = null;
var decoder = new TextDecoder();
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 = "";
}
}
}
function finishReview() {
btn.removeClass('loading').hide();
document.getElementById('llm-review-loading').style.display = 'none';
resultDiv.style.display = 'block';
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 } ?>
<!-- 4. 原始错误折叠(默认收起) -->
<div class="ui styled accordion" id="raw-info-accordion">
<div class="title">
<i class="dropdown icon"></i>
<?php echo isset($MSG_RAW_ERROR) ? $MSG_RAW_ERROR : '查看原始错误信息'; ?>
<?php if (!empty($view_reinfo_dedup_count)): ?>
<span class="ui tiny teal label dedup-label">
<?php echo sprintf(isset($MSG_DEDUP_HINT) ? $MSG_DEDUP_HINT : '已折叠 %d 个相同测试点', intval($view_reinfo_dedup_count)); ?>
</span>
<?php endif; ?>
<?php if (trim($view_reinfo) === ''): ?>
<span class="dedup-label empty-notice">(无)</span>
<?php endif; ?>
</div>
<div class="content">
<?php if (trim($view_reinfo) === ''): ?>
<p class="empty-notice">当前判题结果没有原始错误信息(如 TLE/MLE 通常不会写 runtimeinfo)。</p>
<?php else: ?>
<pre id="errtxt"><?php
// 普通用户:折叠后;管理员/源浏览者:原文
if (isset($is_admin_session) && $is_admin_session) {
echo htmlspecialchars($view_reinfo_raw, ENT_QUOTES, 'UTF-8');
} elseif (isset($is_source_browser) && $is_source_browser) {
echo htmlspecialchars($view_reinfo_raw, ENT_QUOTES, 'UTF-8');
} else {
echo htmlspecialchars($view_reinfo, ENT_QUOTES, 'UTF-8');
}
?></pre>
<?php endif; ?>
</div>
</div>
<!-- 5. 快捷操作区 -->
<div class="quick-actions">
<div class="ui horizontal divider">
<?php echo isset($MSG_QUICK_ACTIONS) ? $MSG_QUICK_ACTIONS : '快捷操作'; ?>
</div>
<div class="ui buttons">
<a class="ui blue button" href="submitpage.php?id=<?php echo intval($row['problem_id']) ?>">
<i class="edit icon"></i> <?php echo isset($MSG_RESUBMIT) ? $MSG_RESUBMIT : '重新提交'; ?>
</a>
<a class="ui button" href="showsource.php?id=<?php echo intval($id) ?>">
<i class="code icon"></i> <?php echo isset($MSG_VIEW_SOURCE) ? $MSG_VIEW_SOURCE : '查看我的代码'; ?>
</a>
<a class="ui button" href="problem.php?id=<?php echo intval($row['problem_id']) ?>">
<i class="info circle icon"></i> <?php echo isset($MSG_VIEW_PROBLEM) ? $MSG_VIEW_PROBLEM : '查看题目'; ?>
</a>
<?php if (isset($OJ_DOWNLOAD) && $OJ_DOWNLOAD && ($isRE || $row['result']==6)) { ?>
<a class="ui orange button" href="download.php?sid=<?php echo intval($id) ?>">
<i class="download icon"></i> <?php echo isset($MSG_DOWNLOAD_DATA) ? $MSG_DOWNLOAD_DATA : '下载测试数据'; ?>
</a>
<?php } ?>
</div>
</div>
</div>
<?php if(isset($OJ_MARKDOWN) && $OJ_MARKDOWN){ ?>
<script>
$(document).ready(function(){
// 初始化折叠面板
$('#raw-info-accordion').accordion();
// 管理员/superuser 默认展开原始错误
<?php if (isset($is_admin_session) && $is_admin_session) { ?>
$('#raw-info-accordion').accordion('open', 0);
<?php } elseif (isset($is_source_browser) && $is_source_browser) { ?>
$('#raw-info-accordion').accordion('open', 0);
<?php } ?>
HustOJVditor.renderMarkdownBlocks('#errtxt', {
getSource: function (element) {
var pre = element.querySelector ? element.querySelector('pre') : null;
return pre ? pre.textContent : element.textContent || '';
}
}).then(function () {
for(let i=1;i<10;i++){
$(".language-input"+i).parent().before("<div><?php echo $MSG_Input?>"+i+":</div>");
$(".language-output"+i).parent().before("<div><?php echo $MSG_Output?>"+i+":</div>");
}
$("#errtxt table").addClass("ui mini-table cell striped");
$("#errtxt table tr:odd td").css({
"border": "1px solid grey",
"text-align": "center",
"width": "200px",
"background-color": "#8521d022",
"height": "30px"
});
$("#errtxt table tr:even td").css({
"border": "1px solid grey",
"text-align": "center",
"width": "200px",
"background-color": "#2185d022",
"height": "30px"
});
$("#errtxt table th").css({
"border": "1px solid grey",
"width": "200px",
"height": "30px",
"background-color": "#2185d088",
"text-align": "center"
});
<?php
if(isset($OJ_DOWNLOAD) && $OJ_DOWNLOAD){
if(isset($OJ_DL_1ST_WA_ONLY) && $OJ_DL_1ST_WA_ONLY){
?>
let down=$($("#errtxt").find("h2")[0]);
let filename=down.text();
down.html("<a href='download.php?sid=<?php echo $id?>&name=" + filename+ "'>" + filename+ "</a>");
<?php
}else{
?>
$("#errtxt").find("h2").each(function(){
let down=$(this);
let filename=down.text();
console.log(filename);
down.html("<a href='download.php?sid=<?php echo $id?>&name=" + filename+ "'>" + filename+ "</a>");
});
<?php
}
}
?>
$("th").each(function(){
let html=$(this).html();
html=html.replace("Expected","<?php echo $MSG_EXPECTED ?>");
html=html.replace("Yours","<?php echo $MSG_YOURS ?>");
html=html.replace("filename","<?php echo $MSG_FILENAME ?>");
html=html.replace("size","<?php echo $MSG_SIZE ?>");
html=html.replace("result","<?php echo $MSG_RESULT ?>");
html=html.replace("memory","<?php echo $MSG_MEMORY?>");
html=html.replace("time","<?php echo $MSG_TIME ?>");
$(this).html(html);
});
}).catch(function (error) {
console.error('Failed to render reinfo markdown.', error);
});
});
</script>
<?php } else { ?>
<script>
// OJ_MARKDOWN 未开启时也要初始化折叠
$(document).ready(function(){
$('#raw-info-accordion').accordion();
<?php if (isset($is_admin_session) && $is_admin_session) { ?>
$('#raw-info-accordion').accordion('open', 0);
<?php } elseif (isset($is_source_browser) && $is_source_browser) { ?>
$('#raw-info-accordion').accordion('open', 0);
<?php } ?>
});
</script>
<?php } ?>
<!-- 自动刷新:判题中(result < 4 或 > 14)时定时拉取最新状态 -->
<?php
$result_code_js = isset($row['result']) ? intval($row['result']) : 4;
$sid_js = isset($id) ? intval($id) : 0;
// 0=Pending, 1=Pending Rejudging, 2=Compiling, 3=Running/Judging, 14=Manual, 15=Submitting, 16/17=Remote
$is_pending = ($result_code_js < 4) || ($result_code_js > 14);
?>
<?php if ($sid_js > 0 && $is_pending): ?>
<script>
(function(){
var sid = <?php echo $sid_js ?>;
var currentResult = <?php echo $result_code_js ?>;
var interval = 1500; // 初始 1.5s
var maxInterval = 8000; // 最长 8s
var countdown = 0;
var stopped = false;
function showStatus(text){
var el = document.getElementById('auto-refresh-status');
var t = document.getElementById('auto-refresh-text');
if (el && t) {
el.style.display = '';
t.textContent = text;
}
}
function hideStatus(){
var el = document.getElementById('auto-refresh-status');
if (el) el.style.display = 'none';
}
function tick(){
if (stopped) return;
countdown--;
if (countdown > 0) {
showStatus('将在 ' + countdown + ' 秒后自动刷新');
setTimeout(tick, 1000);
return;
}
fetch('status-ajax.php?solution_id=' + sid + '&t=json', {credentials: 'same-origin'})
.then(function(r){ return r.json(); })
.then(function(data){
if (!data || typeof data.result === 'undefined') {
// 解析失败,下次重试
interval = Math.min(interval * 1.5, maxInterval);
countdown = Math.ceil(interval / 1000);
tick();
return;
}
var newResult = parseInt(data.result);
if (newResult >= 4 && newResult <= 14) {
// 判题完成,刷新页面拿到完整错误信息
showStatus('判题完成,正在刷新...');
setTimeout(function(){ location.reload(); }, 400);
stopped = true;
return;
}
// 仍在评判中
interval = Math.min(interval * 1.3, maxInterval);
countdown = Math.ceil(interval / 1000);
showStatus('判题中,将在 ' + countdown + ' 秒后再次查询');
setTimeout(tick, 1000);
})
.catch(function(err){
// 网络错误,重试
console.warn('auto refresh failed:', err);
interval = Math.min(interval * 1.5, maxInterval);
countdown = Math.ceil(interval / 1000);
showStatus('查询失败,' + countdown + ' 秒后重试');
setTimeout(tick, 1000);
});
}
// 用户切到后台标签时停止轮询,避免无意义请求
document.addEventListener('visibilitychange', function(){
if (document.hidden) {
stopped = true;
hideStatus();
} else if (!stopped) {
countdown = 0;
tick();
}
});
// 启动
countdown = Math.ceil(interval / 1000);
showStatus('判题中,将在 ' + countdown + ' 秒后自动刷新');
setTimeout(tick, 1000);
})();
</script>
<?php endif; ?>
<?php include("template/$OJ_TEMPLATE/footer.php");?>