feat: 对齐 Jev 真实 API schema、Web Speech 语音输入(优雅降级)、C1-01 语音化身份登记与真 key 冒烟脚本

This commit is contained in:
Jayway Liu
2026-10-03 12:43:41 +08:00
parent bf9d7dad5b
commit bed4d734ca
14 changed files with 623 additions and 95 deletions
+16 -13
View File
@@ -18,15 +18,17 @@ interface Props {
onReplay: () => void;
}
/** 把 append-only 事件轨迹汇总为喂给 Jev 的 state 文本 */
/** 把 append-only 事件轨迹汇总为喂给 Jev 的 state 文本(profile 身份字段是硬过滤字段,不进画像) */
function buildState(events: StoryEvent[]): string {
const lines = events.map((e) => {
if (e.kind === 'choice') {
return `[${e.pointId}] 选择「${e.optionLabel}」(维度: ${e.dimensions.join('/') || '字段采集'})`;
}
const t = e.transcript === null ? '(沉默)' : `(讲述: ${e.transcript})`;
return `[${e.pointId}] 讲述${t} 情绪签名: ${e.emotionSignature ?? '未知'}`;
});
const lines = events
.filter((e) => e.kind !== 'profile')
.map((e) => {
if (e.kind === 'choice') {
return `[${e.pointId}] 选择「${e.optionLabel}」(维度: ${e.dimensions.join('/') || '字段采集'})`;
}
const t = e.transcript === null ? '(沉默)' : `(讲述: ${e.transcript})`;
return `[${e.pointId}] 讲述${t} 情绪签名: ${e.emotionSignature ?? '未知'}`;
});
return `用户在互动剧情《苍梧疑冢》第一幕中的行为轨迹(按时间排序,含初刷与重玩轨迹):\n${lines.join('\n')}`;
}
@@ -40,14 +42,14 @@ export function ReportPage({ store, jev, onReplay }: Props) {
(async () => {
const events = await store.list();
const state = buildState(events);
// 7 维 score 问题,criteria 为 1-10 级有序数组
const criteria = Array.from({ length: 10 }, (_, i) => String(i + 1));
// 7 维 score 问题,criteria 为 10 级有序数组(索引 0-9 对应 1-10 级)
const criteria = Array.from({ length: 10 }, (_, i) => `${i + 1} 级`);
const questions = Object.fromEntries(
PROFILE_DIMENSIONS.map((d) => [
d.id,
{
type: 'score' as const,
description: `根据用户行为轨迹,评估其「${d.label}」维度水平:${d.description}`,
instructions: `根据用户行为轨迹,评估其「${d.label}」维度水平:${d.description}`,
criteria,
},
]),
@@ -58,8 +60,9 @@ export function ReportPage({ store, jev, onReplay }: Props) {
setScores(
PROFILE_DIMENSIONS.map((d) => {
const a = answers[d.id];
// API score 是 [0, criteria.length-1] 的概率加权浮点值,+1 映射到 1-10 级展示
return a?.type === 'score'
? { id: d.id, label: d.label, score: a.score, max: a.max, confidence: a.confidence }
? { id: d.id, label: d.label, score: Math.round((a.score + 1) * 10) / 10, max: 10, confidence: a.confidence }
: { id: d.id, label: d.label, score: 0, max: 10, confidence: 0 };
}),
);
@@ -118,7 +121,7 @@ export function ReportPage({ store, jev, onReplay }: Props) {
<div className="dimension-fill" style={{ width: `${(s.score / s.max) * 100}%` }} />
</div>
<span className="dimension-value">
{s.score}/{s.max} · 置信度 {Math.round(s.confidence * 100)}%
{s.score.toFixed(1)}/{s.max} · 置信度 {Math.round(s.confidence * 100)}%
</span>
</div>
))}