// ===== page-report.jsx =====
const { useState: useSR, useMemo: useMR, useRef: useRR, useEffect: useER } = React;
const LEVEL_META = {
low: { label: '低风险', desc: '文本呈现明显的人类写作特征,AI 生成概率较低。可放心提交。' },
mid: { label: '中风险', desc: '部分段落呈现可疑的 AI 生成痕迹,建议针对性核查并改写。' },
'mid-high': { label: '中高风险', desc: '检测发现明显 AI 生成特征,建议对标记段落进行人工改写后再次自查。' },
high: { label: '高风险', desc: '文本呈现强烈的 AI 生成统计特征,建议对全文进行系统性人工改写。' },
};
function adaptApiReport(apiReport) {
const mm = apiReport.model_match || {};
const models = [
{ name: 'ChatGPT (GPT-4)', match: Math.round(mm.chatgpt || 0) },
{ name: 'Claude', match: Math.round(mm.claude || 0) },
{ name: 'DeepSeek', match: Math.round(mm.deepseek || 0) },
{ name: '文心一言', match: Math.round(mm.wenxin || 0) },
{ name: '通义千问', match: Math.round(mm.qwen || 0) },
{ name: '豆包', match: Math.round(mm.doubao || 0) },
].sort((a, b) => b.match - a.match);
const meta = LEVEL_META[apiReport.risk_level] || LEVEL_META.mid;
const dims = apiReport.dimensions || {};
const metaStats = apiReport.meta_stats || {};
return {
id: apiReport.id,
orderId: apiReport.order_id,
title: (apiReport.file_name || '我的检测报告').replace(/\.[^.]+$/, ''),
author: '私密用户',
submitted: new Date(apiReport.created_at).toLocaleString('zh-CN'),
words: apiReport.word_count || 0,
durationSec: apiReport.duration_sec || 0,
aigc: Math.round(apiReport.aigc_probability || 0),
level: apiReport.risk_level,
levelLabel: meta.label,
levelDesc: meta.desc,
summary: apiReport.summary || '',
keyFindings: apiReport.key_findings || [],
metaStats,
clicheTop: metaStats.cliche_top || [],
voiceTop: metaStats.voice_top || [],
sampleInfo: metaStats.sample_info || {},
dims: {
perplexity: Math.round(dims.perplexity || 0),
burstiness: Math.round(dims.burstiness || 0),
cliche: Math.round(dims.cliche || 0),
regularity: Math.round(dims.regularity || 0),
voice: Math.round(dims.voice || 0),
},
models,
paragraphs: apiReport.paragraphs || [],
suggestions: apiReport.suggestions || [],
};
}
function ReportPage({ navigate, params }) {
const requestedId = params?.id || 'r-1085';
const urlToken = params?.token || '';
const isDemo = requestedId.startsWith('r-');
// URL 自带 token 时立即落盘到 localStorage(覆盖旧值),保证后续刷新/再访问都能用
if (!isDemo && requestedId && urlToken) {
window.API.saveToken(requestedId, urlToken);
}
const [reportId, setReportId] = useSR(requestedId);
const [realReport, setRealReport] = useSR(null);
const [loading, setLoading] = useSR(!isDemo);
const [error, setError] = useSR(null);
useER(() => {
setReportId(requestedId);
window.scrollTo({ top: 0, behavior: 'instant' });
if (requestedId.startsWith('r-')) {
setRealReport(null);
setLoading(false);
setError(null);
} else {
setLoading(true);
setError(null);
// URL token 优先,否则 fall back 到 localStorage
window.API.getReport(requestedId, urlToken || undefined)
.then(data => setRealReport(adaptApiReport(data)))
.catch(err => setError(err))
.finally(() => setLoading(false));
}
}, [requestedId, urlToken]);
// Demo mode allows switching between samples
const handleSwitchSample = (id) => {
if (!id.startsWith('r-')) return;
setReportId(id);
};
if (loading) {
return (
);
}
if (error) {
const isNotReady = error.code === 'report_not_ready';
const isAuthFail = error.code === 'invalid_token' || error.status === 403;
return (
{isNotReady ? '报告尚未生成' : isAuthFail ? '无权访问' : '报告加载失败'}
{isNotReady ? '请等待管理员确认收款,报告生成后页面会自动更新。' :
isAuthFail ? '访问令牌已失效。如果你是订单本人,请通过订单页返回查看。' :
(error.message || '未知错误')}
);
}
const report = isDemo
? (MOCK.REPORTS_BY_ID[reportId] || MOCK.REPORTS[1])
: realReport;
if (!report) return null;
const mountKey = `${reportId}`;
const risk = getRisk(report.aigc);
const pdfUrl = isDemo
? null
: window.API.getReportPdfUrl(requestedId);
return (
{/* 仅 demo 模式显示示例切换 */}
{isDemo && (
示例样本
{MOCK.REPORTS.map(r => (
))}
)}
AIGC 智能检测报告
·
{(report.id || '').toUpperCase()}
{report.title}
} label="作者" value={report.author} />
} label="提交时间" value={report.submitted} />
} label="字数" value={`${(report.words || 0).toLocaleString()} 字`} />
{report.durationSec > 0 && } label="检测耗时" value={`${report.durationSec} 秒`} />}
} label="引擎版本" value="Mojian v1.4" />
{pdfUrl ? (
} onClick={() => window.open(pdfUrl, '_blank')}>下载 PDF 报告
) : (
} disabled>下载 PDF(示例)
)}
{report.levelDesc}
0 · 安全
30 · 低
60 · 中
100 · 高
p.risk !== 'safe').length} / ${report.paragraphs.length}`} tone="brand" />
{report.summary && (
<>
>
)}
{report.keyFindings && report.keyFindings.length > 0 && (
<>
>
)}
{(report.metaStats && report.metaStats.char_count) && (
<>
{(report.clicheTop.length > 0 || report.voiceTop.length > 0) && (
)}
{report.sampleInfo && report.sampleInfo.strategy === 'smart_sample' && (
※ 全文较长(共 {report.sampleInfo.total} 段),段落级分析采用智能抽样(头尾 + 最长/最短 + 中间均匀采样)。
完整全文的 5 维统计指标基于全文真算(非抽样),上方数据为全文统计结果。
)}
>
)}
} />
最高匹配模型
{report.models[0]?.name || '-'}
匹配度 {report.models[0]?.match ?? 0}%
匹配度高仅表明文本与该模型生成样本在统计特征上接近,不构成"该模型生成"的判定。
用作改写参考即可。
模型指纹库
8 大主流模型 · 更新于 2026-05-18
{report.suggestions.map((s, i) => (
-
{i + 1}
{s}
))}
{report.suggestions.length === 0 && (
- 本次检测未发现需要专项改写的高风险段落。
)}
改写小提示:
不要用 AI 反向"降 AIGC"——这往往会引入新的套话。更可靠的方法是替换为你自己的研究细节、反例和情绪化判断,
然后用墨鉴再次检测对照。
报告声明:
本报告基于多维度统计模型分析,结果仅供参考;检测算法持续迭代中,与各类官方查重系统的结果可能存在差异。
学术成果的最终判定请以所在机构的官方系统为准。
本平台不保留原文,所有上传文件在检测完成 24 小时内自动从服务器删除。
);
}
function MetaItem({ icon, label, value }) {
return (
{icon}
{label}
{value}
);
}
function SectionTitle({ n, title, subtitle, extra }) {
return (
{n} · SECTION
{title}
{subtitle &&
{subtitle}
}
{extra}
);
}
function MetricChip({ label, value, tone = 'ink' }) {
const tones = {
ink: 'bg-ink-50',
ok: 'bg-ok-50 text-ok-600 ring-ok-100',
brand: 'bg-brand-50 text-brand-600 ring-brand-100',
};
return (
);
}
function getRisk(aigc) {
if (aigc < 30) return { label: '低风险', color: '#059669', dotClass: 'bg-ok-500', tone: 'ok' };
if (aigc < 55) return { label: '中风险', color: '#D97706', dotClass: 'bg-warn-500', tone: 'warn' };
if (aigc < 75) return { label: '中高风险', color: '#EF4444', dotClass: 'bg-bad-500', tone: 'bad' };
return { label: '高风险', color: '#DC2626', dotClass: 'bg-bad-600', tone: 'bad' };
}
function RiskIcon({ level }) {
if (level === 'low') return
;
if (level === 'mid') return
;
return
;
}
function ReportHeader({ report, navigate, pdfUrl }) {
return (
);
}
function DimensionList({ dims }) {
const [open, setOpen] = useSR(null);
return (
{MOCK.DIMENSIONS.map(d => {
const val = dims[d.key];
const isOpen = open === d.key;
const tone = d.key === 'voice'
? (val >= 60 ? 'ok' : val >= 40 ? 'warn' : 'bad')
: (val < 40 ? 'ok' : val < 65 ? 'warn' : 'bad');
const accent = tone === 'ok' ? '#10B981' : tone === 'warn' ? '#F59E0B' : '#EF4444';
return (
);
})}
);
}
function RiskLegend() {
return (
{[
{ color: '#10B981', bg: '#ECFDF5', label: '安全' },
{ color: '#F59E0B', bg: '#FFFBEB', label: '中风险' },
{ color: '#EF4444', bg: '#FEF2F2', label: '高风险' },
].map(l => (
{l.label}
))}
);
}
function ParagraphSection({ paragraphs, metaStats }) {
// 计数:用 prose_total(参与判定)而不是 total(含 skip)
const dist = (metaStats && metaStats.paragraph_dist) || {};
const prose_total = dist.prose_total ?? paragraphs.filter(p => p.risk !== 'skip').length;
const skipCount = dist.skip ?? paragraphs.filter(p => p.risk === 'skip').length;
const high = dist.high ?? paragraphs.filter(p => p.risk === 'high').length;
const med = dist.med ?? paragraphs.filter(p => p.risk === 'med').length;
const safe = dist.safe ?? paragraphs.filter(p => p.risk === 'safe').length;
// 过滤器:默认只看 med + high(信号最强的部分)
const [filter, setFilter] = useSR('risky'); // 'risky' | 'high' | 'med' | 'safe' | 'all' | 'skip'
const [renderLimit, setRenderLimit] = useSR(200);
const filtered = useMR(() => {
let arr = paragraphs;
if (filter === 'risky') arr = paragraphs.filter(p => p.risk === 'med' || p.risk === 'high');
else if (filter === 'all') arr = paragraphs.filter(p => p.risk !== 'skip');
else arr = paragraphs.filter(p => p.risk === filter);
return [...arr].sort((a, b) => (b.score ?? -1) - (a.score ?? -1));
}, [paragraphs, filter]);
const shown = filtered.slice(0, renderLimit);
const hiddenCount = filtered.length - shown.length;
return (
{/* 风险分布概览 */}
{skipCount > 0 && (
※ 另有 {skipCount} 段为标题、列表项、代码段、参考条目等,由于样本特征不适合风险评分而被排除。
)}
{/* 过滤器 */}
查看
{[
{ k: 'risky', label: '中/高风险段', count: high + med, recommend: true },
{ k: 'high', label: '仅高风险', count: high },
{ k: 'med', label: '仅中风险', count: med },
{ k: 'safe', label: '低风险段', count: safe },
{ k: 'all', label: '全部论述段', count: prose_total },
...(skipCount > 0 ? [{ k: 'skip', label: '非论述段', count: skipCount }] : []),
].map(opt => (
))}
{/* 列表 */}
{filtered.length === 0 ? (
无符合筛选条件的段落
{filter === 'risky' && '本地算法未在全文中发现中/高风险段。'}
) : (
{shown.map((p, i) =>
)}
{hiddenCount > 0 && (
)}
)}
);
}
function DistChip({ count, total, label, tone }) {
const tones = {
bad: { bg: 'bg-bad-50', color: 'text-bad-600', bar: 'bg-bad-500' },
warn: { bg: 'bg-warn-50', color: 'text-warn-600', bar: 'bg-warn-500' },
ok: { bg: 'bg-ok-50', color: 'text-ok-600', bar: 'bg-ok-500' },
ink: { bg: 'bg-ink-50', color: 'text-ink-700', bar: 'bg-ink-400' },
}[tone] || { bg: 'bg-ink-50', color: 'text-ink-700', bar: 'bg-ink-400' };
const pct = total > 0 ? Math.round((count / total) * 100) : 0;
return (
{count}
/ {total}
{label}
{pct}%
);
}
function ParagraphList({ paragraphs }) {
// 兼容老用法(demo 模式直接传 paragraphs 数组)
return (
{paragraphs.map((p, i) =>
)}
);
}
function Para({ para, idx }) {
const [hover, setHover] = useSR(false);
const TYPE_LABELS = { short: '标题/短段', list: '列表项', code: '代码/英文段', citation: '参考条目', prose: '论述段' };
const isSkip = para.risk === 'skip';
const tone = isSkip ? { bg: 'bg-ink-50', border: 'border-ink-200', bar: '#94A3B8', text: 'text-ink-500', label: TYPE_LABELS[para.type] || '非论述段', icon: }
: para.risk === 'safe' ? { bg: 'bg-ok-50', border: 'border-ok-100', bar: '#10B981', text: 'text-ok-600', label: '低风险', icon: }
: para.risk === 'med' ? { bg: 'bg-warn-50', border: 'border-warn-100', bar: '#F59E0B', text: 'text-warn-600', label: '中风险', icon: }
: { bg: 'bg-bad-50', border: 'border-bad-100', bar: '#EF4444', text: 'text-bad-600', label: '高风险', icon: };
const tooShort = isSkip;
const hasReason = para.llm_reviewed && para.reason;
return (
setHover(true)} onMouseLeave={() => setHover(false)}
className={`relative rounded-lg border ${tone.border} ${tone.bg} transition-shadow ${hover ? 'shadow-card' : ''} ${tooShort ? 'opacity-60' : ''}`}>
§{idx}
{para.text}
{tooShort && (段落过短,未参与判定)}
{tone.icon}{tone.label}
{!isSkip && para.score != null && (
AIGC 得分 {para.score}
/ 100
)}
{isSkip && (
不参与风险评分
)}
{para.char_count != null && (
<>
·
{para.char_count} 字
>
)}
{hasReason && (
<>
·
LLM 已点评
>
)}
{hasReason && (
)}
);
}
function StatBox({ label, value, unit, highlight }) {
return (
{label}
{value}{unit && {unit}}
);
}
function HitsList({ title, tone, items, emptyText }) {
const accent = tone === 'bad' ? 'text-bad-600 bg-bad-50' : 'text-ok-600 bg-ok-50';
return (
{title}
{items && items.length > 0 ? (
{items.map(([word, count], i) => (
{word}
×{count}
))}
) : (
{emptyText}
)}
);
}
window.ReportPage = ReportPage;