fix: complete dynamic choice preview flow

This commit is contained in:
Jesse_Chen
2026-07-19 10:51:49 +08:00
parent c74c0bd70f
commit bfd49760ed
3 changed files with 83 additions and 22 deletions
@@ -183,6 +183,11 @@ export function useBirthTimeGuidedJourney(input: GuidedJourneyInput): BirthTimeG
};
const retryScoring = () => {
const turn = journey;
if (preview && turn?.journeyProtocol === "dynamic-choice-v2"
&& (turn.nextAction.kind === "score_pending" || turn.nextAction.kind === "retry_scoring")) {
operate((current) => Promise.resolve(previewAction(current, { kind: "retry_scoring" })));
return;
}
if (turn?.nextAction.kind === "score_pending") {
setError("");
setPollRun((value) => value + 1);
+63 -5
View File
@@ -147,14 +147,72 @@ export function advanceDynamicBirthTimePreview(
if (action.kind !== "ask_dynamic_choice") return turn;
const option = action.question.options.find((item) => item.optionId === command.optionId);
if (!option) return turn;
const answeredCount = turn.progress.answeredCount + 1;
const effectiveAnswerCount = turn.progress.effectiveAnswerCount + (option.kind === "primary" ? 1 : 0);
switch (option.kind) {
case "unmatched": return dynamicBirthTimePreview("clarification");
case "primary": return dynamicBirthTimePreview("scoring");
case "unknown": return response({ nextAction: { kind: "ask_dynamic_choice", question: questions[1] }, phase: "question", answeredCount: 1, turnVersion: 2 });
case "unmatched": return response({
nextAction: { kind: "clarify_unmatched_answer", questionId: action.question.questionId },
phase: "clarification",
answeredCount,
effectiveAnswerCount,
turnVersion: turn.turnVersion + 1,
});
case "primary": {
if (action.question.questionId === questions[0].questionId) {
return response({
nextAction: { kind: "ask_dynamic_choice", question: questions[1] },
phase: "question",
answeredCount,
effectiveAnswerCount,
turnVersion: turn.turnVersion + 1,
});
}
return response({
nextAction: { kind: "present_medium_result", resultId },
phase: "result",
answeredCount,
effectiveAnswerCount,
candidateResult: candidate,
snapshotState: "candidate",
turnVersion: turn.turnVersion + 1,
});
}
case "unknown": {
if (action.question.questionId === questions[0].questionId) {
return response({
nextAction: { kind: "ask_dynamic_choice", question: questions[1] },
phase: "question",
answeredCount,
effectiveAnswerCount,
turnVersion: turn.turnVersion + 1,
});
}
return response({
nextAction: { kind: "present_low_result", resultId: null },
phase: "result",
answeredCount,
effectiveAnswerCount,
snapshotState: "candidate",
turnVersion: turn.turnVersion + 1,
});
}
}
}
case "reframe": return response({ nextAction: { kind: "ask_dynamic_choice", question: questions[1] }, phase: "question", answeredCount: 1, turnVersion: 3 });
case "finish": return dynamicBirthTimePreview("low");
case "reframe": return response({
nextAction: { kind: "ask_dynamic_choice", question: questions[1] },
phase: "question",
answeredCount: turn.progress.answeredCount,
effectiveAnswerCount: turn.progress.effectiveAnswerCount,
turnVersion: turn.turnVersion + 1,
});
case "finish": return response({
nextAction: { kind: "present_low_result", resultId: null },
phase: "result",
answeredCount: turn.progress.answeredCount,
effectiveAnswerCount: turn.progress.effectiveAnswerCount,
snapshotState: "candidate",
turnVersion: turn.turnVersion + 1,
});
case "pause": return dynamicBirthTimePreview("paused");
case "resume": return dynamicBirthTimePreview("question");
case "retry_scoring": return dynamicBirthTimePreview("medium");
@@ -1,5 +1,4 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import {
choiceQuestionGroups,
@@ -45,33 +44,32 @@ test("unmatched requests clarification without creating scoring evidence", () =>
assert.equal(normalizeUnmatchedNote("甲".repeat(260)).length, 240);
});
test("the rendered component keeps one-click and optional-note semantics", () => {
const source = readFileSync(
new URL("../src/components/birth-time-choice-question.tsx", import.meta.url),
"utf8",
);
assert.match(source, /<fieldset/);
assert.match(source, /<legend/);
assert.match(source, /onSelect\(intent\.optionId\)/);
assert.match(source, /maxLength=\{240\}/);
assert.doesNotMatch(source, /整理为经历草稿|记得的精度|发生时间|第.*\/.*轮/);
assert.doesNotMatch(source, /required/);
});
test("preview choices expose real local interaction states without authentication", () => {
const start = dynamicBirthTimePreview();
const scoring = advanceDynamicBirthTimePreview(start, { kind: "select", optionId: "earlier" });
const nextQuestion = advanceDynamicBirthTimePreview(start, { kind: "select", optionId: "earlier" });
const result = advanceDynamicBirthTimePreview(nextQuestion, { kind: "select", optionId: "school" });
const clarification = advanceDynamicBirthTimePreview(start, { kind: "select", optionId: "unmatched" });
const reframed = advanceDynamicBirthTimePreview(clarification, { kind: "reframe" });
const terminal = advanceDynamicBirthTimePreview(reframed, { kind: "finish" });
assert.equal(scoring.nextAction.kind, "score_pending");
assert.equal(nextQuestion.nextAction.kind, "ask_dynamic_choice");
assert.equal(nextQuestion.progress.effectiveAnswerCount, 1);
assert.equal(result.nextAction.kind, "present_medium_result");
assert.equal(clarification.nextAction.kind, "clarify_unmatched_answer");
assert.equal(reframed.nextAction.kind, "ask_dynamic_choice");
assert.equal(terminal.nextAction.kind, "present_low_result");
});
test("preview unknown answers terminate instead of repeating the same question", () => {
const start = dynamicBirthTimePreview();
const secondQuestion = advanceDynamicBirthTimePreview(start, { kind: "select", optionId: "unknown" });
const terminal = advanceDynamicBirthTimePreview(secondQuestion, { kind: "select", optionId: "unknown-2" });
assert.equal(secondQuestion.nextAction.kind, "ask_dynamic_choice");
assert.equal(terminal.nextAction.kind, "present_low_result");
assert.equal(terminal.progress.answeredCount, 2);
});
test("dynamic previews cover every visible result and recovery state", () => {
const expected = new Map([
["generating", "generate_dynamic_question"],