feat: 新增管理员查看 AI 请求历史记录功能

- 在 AI 内容查询页面底部添加管理员可见的历史记录列表
- 显示最近 50 条 AI 文章摘要和垃圾评论检测记录
- 支持点击记录查看详细信息的弹窗
- 记录按时间倒序排列,包含类型、识别码、标题和时间
- 弹窗显示完整的 AI 请求详情,包括生成内容、模型信息等
- 仅管理员可见,普通用户不受影响
This commit is contained in:
2026-01-22 18:23:05 +08:00
parent 86e9336149
commit 54f2214a6a

View File

@@ -605,6 +605,365 @@ html.darkmode .ai-verify-subtitle { color: #aaa; }
<?php endif; ?> <?php endif; ?>
</article> </article>
<?php
// ==========================================================================
// 管理员查看全部 AI 请求记录
// ==========================================================================
if (current_user_can('manage_options')):
global $wpdb;
// 获取所有 AI 摘要记录
$summaries = $wpdb->get_results("
SELECT pm.post_id, pm.meta_value as code, pm2.meta_value as time
FROM {$wpdb->postmeta} pm
LEFT JOIN {$wpdb->postmeta} pm2 ON pm.post_id = pm2.post_id AND pm2.meta_key = '_argon_ai_summary_time'
WHERE pm.meta_key = '_argon_ai_summary_code'
ORDER BY pm2.meta_value DESC
LIMIT 50
");
// 获取所有垃圾评论检测记录
$spam_detections = $wpdb->get_results("
SELECT cm.comment_id, cm.meta_value as code, cm2.meta_value as time
FROM {$wpdb->commentmeta} cm
LEFT JOIN {$wpdb->commentmeta} cm2 ON cm.comment_id = cm2.comment_id AND cm2.meta_key = '_argon_spam_detection_time'
WHERE cm.meta_key = '_argon_spam_detection_code'
ORDER BY cm2.meta_value DESC
LIMIT 50
");
// 合并并按时间排序
$all_records = [];
foreach ($summaries as $summary) {
$post = get_post($summary->post_id);
if ($post) {
$all_records[] = [
'type' => 'summary',
'code' => $summary->code,
'time' => $summary->time ? intval($summary->time) : 0,
'title' => get_the_title($summary->post_id),
'url' => get_permalink($summary->post_id),
'id' => $summary->post_id,
'data' => [
'post_id' => $summary->post_id,
'post_title' => get_the_title($summary->post_id),
'post_url' => get_permalink($summary->post_id),
'summary' => get_post_meta($summary->post_id, '_argon_ai_summary', true),
'model' => get_post_meta($summary->post_id, '_argon_ai_summary_model', true),
'provider' => get_post_meta($summary->post_id, '_argon_ai_summary_provider', true),
'generated_time' => $summary->time
]
];
}
}
foreach ($spam_detections as $detection) {
$comment = get_comment($detection->comment_id);
if ($comment) {
$detection_result = get_comment_meta($detection->comment_id, '_argon_spam_detection_result', true);
$all_records[] = [
'type' => 'spam_detection',
'code' => $detection->code,
'time' => $detection->time ? intval($detection->time) : 0,
'title' => sprintf(__('评论 #%d - %s', 'argon'), $detection->comment_id, $comment->comment_author),
'url' => get_comment_link($comment),
'id' => $detection->comment_id,
'data' => [
'comment_id' => $detection->comment_id,
'comment_author' => $comment->comment_author,
'comment_content' => $comment->comment_content,
'comment_date' => $comment->comment_date,
'post_title' => get_the_title($comment->comment_post_ID),
'post_url' => get_permalink($comment->comment_post_ID),
'is_spam' => isset($detection_result['is_spam']) ? $detection_result['is_spam'] : false,
'reason' => isset($detection_result['reason']) ? $detection_result['reason'] : '',
'action' => isset($detection_result['action']) ? $detection_result['action'] : '',
'detection_time' => $detection->time
]
];
}
}
// 按时间倒序排序
usort($all_records, function($a, $b) {
return $b['time'] - $a['time'];
});
// 只保留前50条
$all_records = array_slice($all_records, 0, 50);
if (!empty($all_records)):
?>
<article class="post card shadow-sm bg-white border-0 ai-verify-card" style="margin-top: 16px;">
<h3 class="ai-verify-section-title">
<i class="fa fa-database"></i><?php _e('AI 请求历史记录', 'argon'); ?>
<span style="font-size: 13px; font-weight: 400; color: var(--color-text-muted); margin-left: 8px;">
(<?php _e('仅管理员可见', 'argon'); ?>)
</span>
</h3>
<div class="ai-alert ai-alert-info" style="margin-bottom: 20px;">
<span class="ai-alert-icon"></span>
<span><?php _e('显示最近 50 条 AI 请求记录,点击查看详情', 'argon'); ?></span>
</div>
<style>
.ai-records-table { width: 100%; border-collapse: collapse; }
.ai-records-table th { background: rgba(var(--themecolor-rgbstr), 0.05); padding: 12px; text-align: left; font-size: 13px; font-weight: 600; color: var(--color-text-deeper); border-bottom: 2px solid var(--color-border-on-foreground); }
.ai-records-table td { padding: 12px; border-bottom: 1px solid var(--color-border-on-foreground); font-size: 14px; color: var(--color-text); }
.ai-records-table tbody tr { cursor: pointer; transition: background var(--animation-fast) var(--ease-standard); }
.ai-records-table tbody tr:hover { background: rgba(var(--themecolor-rgbstr), 0.03); }
.ai-record-type-badge { display: inline-block; padding: 3px 8px; border-radius: 4px; font-size: 12px; font-weight: 500; }
.ai-record-type-summary { background: rgba(59, 130, 246, 0.1); color: #3b82f6; }
.ai-record-type-spam { background: rgba(239, 68, 68, 0.1); color: #ef4444; }
.ai-record-code { font-family: 'Consolas', 'Monaco', monospace; font-size: 13px; color: var(--themecolor); font-weight: 600; }
.ai-record-time { color: var(--color-text-muted); font-size: 13px; }
.ai-modal { display: none; position: fixed; z-index: 9999; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); overflow: auto; }
.ai-modal.active { display: flex; align-items: center; justify-content: center; }
.ai-modal-content { background: var(--color-foreground); border-radius: var(--card-radius); max-width: 800px; width: 90%; max-height: 90vh; overflow-y: auto; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); }
.ai-modal-header { padding: 20px 24px; border-bottom: 1px solid var(--color-border-on-foreground); display: flex; align-items: center; justify-content: space-between; }
.ai-modal-title { font-size: 18px; font-weight: 600; color: var(--color-text-deeper); margin: 0; }
.ai-modal-close { background: none; border: none; font-size: 24px; color: var(--color-text-muted); cursor: pointer; padding: 0; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; border-radius: 4px; transition: all var(--animation-fast) var(--ease-standard); }
.ai-modal-close:hover { background: rgba(var(--themecolor-rgbstr), 0.1); color: var(--themecolor); }
.ai-modal-body { padding: 24px; }
@media (max-width: 768px) {
.ai-records-table { display: block; overflow-x: auto; }
.ai-records-table th, .ai-records-table td { white-space: nowrap; }
.ai-modal-content { width: 95%; max-height: 95vh; }
.ai-modal-header { padding: 16px; }
.ai-modal-body { padding: 16px; }
}
</style>
<div style="overflow-x: auto;">
<table class="ai-records-table">
<thead>
<tr>
<th style="width: 100px;"><?php _e('类型', 'argon'); ?></th>
<th style="width: 120px;"><?php _e('识别码', 'argon'); ?></th>
<th><?php _e('标题', 'argon'); ?></th>
<th style="width: 160px;"><?php _e('时间', 'argon'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($all_records as $record): ?>
<tr class="ai-record-row" data-record='<?php echo esc_attr(json_encode($record)); ?>'>
<td>
<span class="ai-record-type-badge ai-record-type-<?php echo esc_attr($record['type']); ?>">
<?php echo $record['type'] === 'summary' ? __('文章摘要', 'argon') : __('垃圾评论', 'argon'); ?>
</span>
</td>
<td><span class="ai-record-code"><?php echo esc_html($record['code']); ?></span></td>
<td><?php echo esc_html($record['title']); ?></td>
<td class="ai-record-time">
<?php echo $record['time'] ? date('Y-m-d H:i:s', $record['time']) : __('未知', 'argon'); ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</article>
<!-- 详情弹窗 -->
<div id="aiRecordModal" class="ai-modal">
<div class="ai-modal-content">
<div class="ai-modal-header">
<h4 class="ai-modal-title"><?php _e('AI 请求详情', 'argon'); ?></h4>
<button class="ai-modal-close" onclick="closeAIModal()">&times;</button>
</div>
<div class="ai-modal-body" id="aiModalBody">
<!-- 动态内容 -->
</div>
</div>
</div>
<script data-pjax>
(function() {
const modal = document.getElementById('aiRecordModal');
const modalBody = document.getElementById('aiModalBody');
window.closeAIModal = function() {
modal.classList.remove('active');
};
// 点击背景关闭
modal.addEventListener('click', function(e) {
if (e.target === modal) {
closeAIModal();
}
});
// ESC 键关闭
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && modal.classList.contains('active')) {
closeAIModal();
}
});
// 行点击事件
document.querySelectorAll('.ai-record-row').forEach(function(row) {
row.addEventListener('click', function() {
const record = JSON.parse(this.getAttribute('data-record'));
showRecordDetail(record);
});
});
function showRecordDetail(record) {
let html = '';
if (record.type === 'summary') {
html = `
<div class="ai-code-display">${escapeHtml(record.code)}</div>
<h4 style="font-size: 15px; font-weight: 600; margin: 0 0 12px; color: var(--color-text-deeper);"><?php _e('关联文章', 'argon'); ?></h4>
<div class="ai-info-grid" style="margin-bottom: 24px;">
<div class="ai-info-item">
<span class="ai-info-label"><?php _e('文章标题', 'argon'); ?></span>
<a href="${escapeHtml(record.data.post_url)}" class="ai-info-value ai-info-value-link" target="_blank">
${escapeHtml(record.data.post_title)}
</a>
</div>
<div class="ai-info-item">
<span class="ai-info-label"><?php _e('文章 ID', 'argon'); ?></span>
<span class="ai-info-value ai-info-value-mono">${escapeHtml(record.data.post_id)}</span>
</div>
<div class="ai-info-item">
<span class="ai-info-label"><?php _e('生成时间', 'argon'); ?></span>
<span class="ai-info-value">${record.data.generated_time ? new Date(record.data.generated_time * 1000).toLocaleString('zh-CN') : '<?php _e('未知', 'argon'); ?>'}</span>
</div>
</div>
<h4 style="font-size: 15px; font-weight: 600; margin: 0 0 12px; color: var(--color-text-deeper);"><?php _e('AI 生成内容', 'argon'); ?></h4>
<div class="ai-content-box">
${escapeHtml(record.data.summary)}
</div>
<h4 style="font-size: 15px; font-weight: 600; margin: 0 0 12px; color: var(--color-text-deeper);"><?php _e('生成信息', 'argon'); ?></h4>
<div class="ai-info-grid">
<div class="ai-info-item">
<span class="ai-info-label"><?php _e('AI 提供商', 'argon'); ?></span>
<span class="ai-info-value">${escapeHtml(record.data.provider)}</span>
</div>
<div class="ai-info-item">
<span class="ai-info-label"><?php _e('使用模型', 'argon'); ?></span>
<span class="ai-info-value ai-info-value-mono">${escapeHtml(record.data.model)}</span>
</div>
</div>
<div class="ai-actions">
<a href="${escapeHtml(record.data.post_url)}" class="btn btn-primary" target="_blank">
<?php _e('查看原文', 'argon'); ?>
</a>
<a href="<?php echo home_url('/ai-query'); ?>?code=${escapeHtml(record.code)}" class="btn btn-outline-primary">
<?php _e('公开查询页面', 'argon'); ?>
</a>
</div>
`;
} else {
const isSpam = record.data.is_spam;
html = `
<div class="ai-code-display">${escapeHtml(record.code)}</div>
<div class="ai-alert ${isSpam ? 'ai-alert-warning' : 'ai-alert-info'}" style="margin-bottom: 20px;">
<span class="ai-alert-icon">${isSpam ? '⚠' : '✓'}</span>
<span>
<strong><?php _e('AI 识别结果', 'argon'); ?></strong>
${isSpam ? '<?php _e('疑似垃圾评论', 'argon'); ?>' : '<?php _e('正常评论', 'argon'); ?>'}
</span>
</div>
<h4 style="font-size: 15px; font-weight: 600; margin: 0 0 12px; color: var(--color-text-deeper);"><?php _e('评论信息', 'argon'); ?></h4>
<div class="ai-info-grid" style="margin-bottom: 24px;">
<div class="ai-info-item">
<span class="ai-info-label"><?php _e('评论 ID', 'argon'); ?></span>
<span class="ai-info-value ai-info-value-mono">${escapeHtml(record.data.comment_id)}</span>
</div>
<div class="ai-info-item">
<span class="ai-info-label"><?php _e('评论者', 'argon'); ?></span>
<span class="ai-info-value">${escapeHtml(record.data.comment_author)}</span>
</div>
<div class="ai-info-item">
<span class="ai-info-label"><?php _e('评论时间', 'argon'); ?></span>
<span class="ai-info-value">${escapeHtml(record.data.comment_date)}</span>
</div>
<div class="ai-info-item">
<span class="ai-info-label"><?php _e('所属文章', 'argon'); ?></span>
<a href="${escapeHtml(record.data.post_url)}" class="ai-info-value ai-info-value-link" target="_blank">
${escapeHtml(record.data.post_title)}
</a>
</div>
</div>
<h4 style="font-size: 15px; font-weight: 600; margin: 0 0 12px; color: var(--color-text-deeper);"><?php _e('评论内容', 'argon'); ?></h4>
<div class="ai-content-box">
${escapeHtml(record.data.comment_content)}
</div>
<h4 style="font-size: 15px; font-weight: 600; margin: 0 0 12px; color: var(--color-text-deeper);"><?php _e('AI 检测结果', 'argon'); ?></h4>
<div class="ai-info-grid" style="margin-bottom: 24px;">
<div class="ai-info-item">
<span class="ai-info-label"><?php _e('识别结果', 'argon'); ?></span>
<span class="ai-info-value">
<span class="ai-status-badge ${isSpam ? 'ai-status-modified' : 'ai-status-valid'}">
${isSpam ? '<?php _e('疑似垃圾评论', 'argon'); ?>' : '<?php _e('正常评论', 'argon'); ?>'}
</span>
</span>
</div>
<div class="ai-info-item">
<span class="ai-info-label"><?php _e('识别理由', 'argon'); ?></span>
<span class="ai-info-value">${escapeHtml(record.data.reason)}</span>
</div>
${record.data.action ? `
<div class="ai-info-item">
<span class="ai-info-label"><?php _e('自动处理', 'argon'); ?></span>
<span class="ai-info-value">${escapeHtml(record.data.action)}</span>
</div>
` : ''}
<div class="ai-info-item">
<span class="ai-info-label"><?php _e('检测时间', 'argon'); ?></span>
<span class="ai-info-value">${record.data.detection_time ? new Date(record.data.detection_time * 1000).toLocaleString('zh-CN') : '<?php _e('未知', 'argon'); ?>'}</span>
</div>
</div>
<div class="ai-actions">
<a href="${escapeHtml(record.url)}" class="btn btn-primary" target="_blank">
<?php _e('查看评论', 'argon'); ?>
</a>
<a href="${escapeHtml(record.data.post_url)}" class="btn btn-outline-primary" target="_blank">
<?php _e('查看文章', 'argon'); ?>
</a>
<a href="<?php echo home_url('/ai-query'); ?>?code=${escapeHtml(record.code)}" class="btn btn-outline-secondary">
<?php _e('公开查询页面', 'argon'); ?>
</a>
</div>
`;
}
modalBody.innerHTML = html;
modal.classList.add('active');
}
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
})();
</script>
<?php
endif; // !empty($all_records)
endif; // current_user_can('manage_options')
?>
</main>
<?php <?php
get_footer(); get_footer();
?> ?>