feat: 第一季九章完整剧本与引擎(chapters 4-9、相处线索报告替代七维评分、采样覆盖驱动支线插入、consent v2、报告页隐私区块完善)+ 跨 agent 断点修复
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import type { EventStore } from '../../engine/eventStore';
|
||||
import type { JevClient } from '../../services/jev';
|
||||
import { BADGE_MAP } from './badges';
|
||||
import { scoreProfile } from '../../engine/scoring';
|
||||
import type { DimensionScore } from '../../engine/scoring';
|
||||
export type { DimensionScore } from '../../engine/scoring';
|
||||
import { assessRelationships } from '../../engine/scoring';
|
||||
import type { RelationshipArea } from '../../engine/scoring';
|
||||
import { AREA_BADGE_MAP, DEFAULT_BADGE } from './badges';
|
||||
import { ShareCard } from './ShareCard';
|
||||
|
||||
interface Props {
|
||||
@@ -15,9 +14,12 @@ interface Props {
|
||||
onPurge: () => Promise<void>;
|
||||
}
|
||||
|
||||
/** Act 1 结束后的画像报告页:Jev 一次调用并行问 7 个维度 score */
|
||||
/**
|
||||
* 季终相处线索报告:Jev 逐条核对剧情情境,只提出"待本人核对"的候选线索,
|
||||
* 不打人格分数、不做优劣排名(season-design §7:多类别相处方式不能被压成一维分值)。
|
||||
*/
|
||||
export function ReportPage({ store, jev, onReplay, onPurge }: Props) {
|
||||
const [scores, setScores] = useState<DimensionScore[] | null>(null);
|
||||
const [areas, setAreas] = useState<RelationshipArea[] | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [retry, setRetry] = useState(0);
|
||||
const [playerName, setPlayerName] = useState('');
|
||||
@@ -47,9 +49,9 @@ export function ReportPage({ store, jev, onReplay, onPurge }: Props) {
|
||||
// 最近一次身份登记的称呼(旧数据缺 name 字段时回退空串 → 只显示编号)
|
||||
const name = [...events].reverse().find((e) => e.kind === 'profile')?.profile?.name ?? '';
|
||||
if (!cancelled) setPlayerName(name);
|
||||
const result = await scoreProfile(events, jev);
|
||||
const result = await assessRelationships(events, jev);
|
||||
if (cancelled) return;
|
||||
setScores(result);
|
||||
setAreas(result);
|
||||
} catch (e) {
|
||||
if (!cancelled) setError(e instanceof Error ? e.message : String(e));
|
||||
}
|
||||
@@ -62,17 +64,17 @@ export function ReportPage({ store, jev, onReplay, onPurge }: Props) {
|
||||
if (error) {
|
||||
return (
|
||||
<div className="report">
|
||||
<h1>画像报告生成失败</h1>
|
||||
<h1>线索报告生成失败</h1>
|
||||
<p>{error}</p>
|
||||
<button
|
||||
className="primary"
|
||||
onClick={() => {
|
||||
setError(null);
|
||||
setScores(null);
|
||||
setAreas(null);
|
||||
setRetry((r) => r + 1);
|
||||
}}
|
||||
>
|
||||
画像生成失败,点此重试
|
||||
生成失败,点此重试
|
||||
</button>
|
||||
<button className="secondary" onClick={onReplay}>
|
||||
回到剧情
|
||||
@@ -81,55 +83,59 @@ export function ReportPage({ store, jev, onReplay, onPurge }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
if (!scores) {
|
||||
if (!areas) {
|
||||
return (
|
||||
<div className="report">
|
||||
<h1>档案馆正在整理你的记忆……</h1>
|
||||
<p>Jev 正在评估 7 个维度。</p>
|
||||
<h1>档案馆正在整理你的相处线索……</h1>
|
||||
<p>正逐条核对剧情情境(本地 Mock 模式不生成伪随机判断)。</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 勋章 = 置信度最高的维度
|
||||
const top = [...scores].sort((a, b) => b.confidence - a.confidence)[0];
|
||||
const badge = BADGE_MAP[top.id];
|
||||
// 勋章 = 候选线索证据最多的领域;全无证据时给默认称号
|
||||
const signal = (a: RelationshipArea) => a.candidates.reduce((n, c) => n + c.pointIds.length, 0);
|
||||
const top = [...areas].sort((a, b) => signal(b) - signal(a))[0];
|
||||
const badge = signal(top) > 0 ? (AREA_BADGE_MAP[top.id] ?? DEFAULT_BADGE) : DEFAULT_BADGE;
|
||||
|
||||
return (
|
||||
<div className="report">
|
||||
<h1>人格画像报告 · Act 1</h1>
|
||||
<h1>相处线索报告 · 苍梧疑冢</h1>
|
||||
<p className="hint">来自剧情情境的初步线索,不是心理诊断、人格优劣排名或匹配成功保证。真实择偶条件须由你另行确认,目前未开放匹配。</p>
|
||||
<p className="hint">C-2049{playerName ? `「${playerName}」` : ''}的档案</p>
|
||||
<section className="badge-banner">
|
||||
<div className="badge-medal">勋</div>
|
||||
<div>
|
||||
<h2>{badge.title}</h2>
|
||||
<p>{badge.flavor}</p>
|
||||
<p className="badge-note">
|
||||
(取自实测置信度最高的维度:{top.label},置信度 {Math.round(top.confidence * 100)}%)
|
||||
</p>
|
||||
{signal(top) > 0 && (
|
||||
<p className="badge-note">
|
||||
(取自「{top.label}」领域的候选线索,共 {signal(top)} 条情境支持——待你本人核对)
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
<section className="dimension-list">
|
||||
{scores.map((s) => (
|
||||
<div key={s.id} className="dimension-row">
|
||||
<span className="dimension-label">{s.label}</span>
|
||||
<div className="dimension-bar">
|
||||
<div className="dimension-fill" style={{ width: `${(s.score / s.max) * 100}%` }} />
|
||||
</div>
|
||||
{areas.map((a) => (
|
||||
<div key={a.id} className="dimension-row">
|
||||
<span className="dimension-label">{a.label}</span>
|
||||
<span className="dimension-value">
|
||||
{s.score.toFixed(1)}/{s.max} · 置信度 {Math.round(s.confidence * 100)}%
|
||||
{s.confidence < 0.6 && ' · 证据不足'}
|
||||
{a.status === '证据不足'
|
||||
? `剧情情境不足,暂不推断(已采集 ${a.pointIds.length} 条相关情境)`
|
||||
: a.candidates.length
|
||||
? a.candidates.map((c) => `「${c.label}」`).join('、') + ` —— 待本人核对`
|
||||
: '情境已够,暂未出现明确倾向'}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
<ShareCard badgeDimension={top.id} scores={scores} playerName={playerName} />
|
||||
<ShareCard badgeArea={signal(top) > 0 ? top.id : null} areas={areas} playerName={playerName} />
|
||||
<button className="secondary" onClick={onReplay}>
|
||||
重玩 Act 1(新轨迹,历史保留)
|
||||
重玩第一季(新轨迹,历史保留)
|
||||
</button>
|
||||
<section style={{ marginTop: 48, borderTop: '1px solid rgba(201,219,203,.2)', paddingTop: 24 }}>
|
||||
<h2 style={{ fontSize: 18, color: '#f1eee3' }}>数据与隐私</h2>
|
||||
<p className="hint" style={{ maxWidth: 560 }}>
|
||||
本应用只在本机记录你的剧情选择、语音讲述转写与人格画像事件,用于人格评估与伴侣匹配推荐;数据不出设备,删除即彻底消失。
|
||||
剧情进度与行为记录保存在本机;配置在线分析服务时,相关选择与讲述文本会发送至该服务用于线索核对。删除会清空本机记录,不代表撤回已经完成的远程处理。私人幻境路径和结尾心愿不参与画像。
|
||||
</p>
|
||||
<button className="secondary" onClick={() => purgeDialogRef.current?.showModal()}>
|
||||
删除全部数据
|
||||
@@ -151,7 +157,7 @@ export function ReportPage({ store, jev, onReplay, onPurge }: Props) {
|
||||
确定删除全部数据?
|
||||
</h3>
|
||||
<p style={{ lineHeight: 1.8 }}>
|
||||
此操作不可恢复,所有剧情进度、人格画像、讲述内容将被永久删除,且不提供导出留档。
|
||||
此操作不可恢复,所有剧情进度、相处线索、讲述内容将被永久删除,且不提供导出留档。
|
||||
删除后应用将回到初始的告知与同意页面,如同首次安装。
|
||||
</p>
|
||||
<div style={{ display: 'flex', gap: 12, marginTop: 20, flexWrap: 'wrap' }}>
|
||||
|
||||
Reference in New Issue
Block a user