feat: add click-first birth time questions
This commit is contained in:
@@ -18,13 +18,14 @@ const confidenceLabels = {
|
||||
export function BirthTimeCandidateResult({ journey, controller }: CandidateResultProps) {
|
||||
const result = journey.candidateResult;
|
||||
const action = journey.nextAction;
|
||||
const dynamic = journey.journeyProtocol === "dynamic-choice-v2";
|
||||
const terminalPath = guidedTerminalPath(journey);
|
||||
if (!result && action.kind === "present_low_result") {
|
||||
return (
|
||||
<div className="birth-time-candidate-result" aria-live="polite">
|
||||
<p className="birth-time-assessment-unavailable">目前证据不足,只能保留已填报的时间范围 {journey.snapshot.reportedRange.label}。</p>
|
||||
<p className="birth-time-assessment-unavailable">目前没有足够的新信息继续稳定缩小范围,本次评估已结束并保存当前候选范围。</p>
|
||||
<p className="birth-time-evidence-boundary">系统不会选择或应用未经证据支持的具体分钟,<span className="phrase-nowrap">当前排盘使用时间</span>保持不变。</p>
|
||||
{terminalPath && <button className="button-secondary birth-time-guided-action" disabled={controller.pending} type="button" onClick={controller.editBirthTimeDetails}>修改填报时间并重新评估</button>}
|
||||
{terminalPath && <NewAssessmentAction controller={controller} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -34,15 +35,15 @@ export function BirthTimeCandidateResult({ journey, controller }: CandidateResul
|
||||
return (
|
||||
<div className="birth-time-candidate-result" aria-live="polite">
|
||||
<div className="birth-time-candidate-heading">
|
||||
<b>关键经历评分结果</b>
|
||||
<b>{dynamic ? "候选范围评估结果" : "关键经历评分结果"}</b>
|
||||
<span>{confidenceLabels[result.confidence]}</span>
|
||||
</div>
|
||||
{winner ? (
|
||||
<dl className="birth-time-candidate-grid">
|
||||
<div><dt>候选时间范围</dt><dd>{winner.startTime}—{winner.endTime}</dd></div>
|
||||
<div><dt>候选代表时间</dt><dd>{winner.representativeTime}</dd></div>
|
||||
<div><dt>有效经历</dt><dd>{result.eventCount} 条 / {result.domainCount} 个领域</dd></div>
|
||||
<div><dt>领先幅度</dt><dd>{result.marginPercent}%</dd></div>
|
||||
{!dynamic && <div><dt>有效经历</dt><dd>{result.eventCount} 条 / {result.domainCount} 个领域</dd></div>}
|
||||
{!dynamic && <div><dt>领先幅度</dt><dd>{result.marginPercent}%</dd></div>}
|
||||
</dl>
|
||||
) : (
|
||||
<p className="birth-time-assessment-unavailable">候选仍然并列或缺少必要计算层,系统不会选择具体分钟。</p>
|
||||
@@ -51,11 +52,17 @@ export function BirthTimeCandidateResult({ journey, controller }: CandidateResul
|
||||
|
||||
{action.kind === "present_low_result" && (
|
||||
<div className="birth-time-candidate-terminal" role="status">
|
||||
<p>本轮校正已安全结束,只保留候选范围,<span className="phrase-nowrap">当前排盘使用时间</span>保持不变。</p>
|
||||
{terminalPath && <button className="button-secondary birth-time-guided-action" disabled={controller.pending} type="button" onClick={controller.editBirthTimeDetails}>修改填报时间并重新评估</button>}
|
||||
<p>{dynamic ? "目前没有足够的新信息继续稳定缩小范围,本次评估已结束并保存当前候选范围。" : "本轮校正已安全结束,只保留候选范围,当前排盘使用时间保持不变。"}</p>
|
||||
{terminalPath && <NewAssessmentAction controller={controller} />}
|
||||
</div>
|
||||
)}
|
||||
{action.kind === "present_medium_result" && winner && (
|
||||
{action.kind === "present_medium_result" && winner && dynamic && (
|
||||
<div className="birth-time-candidate-terminal" role="status">
|
||||
<p>已形成较窄的候选范围,本次评估已结束;它不会自动改动<span className="phrase-nowrap">当前排盘使用时间</span>。</p>
|
||||
<NewAssessmentAction controller={controller} />
|
||||
</div>
|
||||
)}
|
||||
{action.kind === "present_medium_result" && winner && !dynamic && (
|
||||
<div className="birth-time-candidate-terminal">
|
||||
<p>可以保存候选时间范围,代表时间不会<span className="phrase-nowrap">用于</span>排盘。</p>
|
||||
<button className="button-primary birth-time-guided-action" disabled={controller.pending} type="button" onClick={() => controller.saveCandidate(result.resultId)}>
|
||||
@@ -66,7 +73,7 @@ export function BirthTimeCandidateResult({ journey, controller }: CandidateResul
|
||||
{action.kind === "candidate_saved" && (
|
||||
<div className="birth-time-candidate-terminal" role="status">
|
||||
<p className="birth-time-success-note">候选时间范围已保存。<span className="phrase-nowrap">当前排盘使用时间</span>没有改变。</p>
|
||||
{terminalPath && <button className="button-secondary birth-time-guided-action" disabled={controller.pending} type="button" onClick={controller.editBirthTimeDetails}>修改填报时间并重新评估</button>}
|
||||
{terminalPath && <NewAssessmentAction controller={controller} />}
|
||||
</div>
|
||||
)}
|
||||
{action.kind === "request_candidate_confirmation" && winner && (
|
||||
@@ -87,3 +94,14 @@ export function BirthTimeCandidateResult({ journey, controller }: CandidateResul
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function NewAssessmentAction({ controller }: {
|
||||
readonly controller: BirthTimeGuidedController;
|
||||
}) {
|
||||
return (
|
||||
<div className="birth-time-new-assessment">
|
||||
<button className="button-secondary birth-time-guided-action" disabled={controller.pending} onClick={controller.editBirthTimeDetails} type="button">开始新的评估</button>
|
||||
<small>会建立新的记录,当前结果仍会保留。</small>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import {
|
||||
choiceQuestionGroups,
|
||||
choiceSelectionIntent,
|
||||
normalizeUnmatchedNote,
|
||||
rangeLabel,
|
||||
} from "@/lib/birth-time-choice-question-model";
|
||||
import type {
|
||||
DynamicJourneyProgress,
|
||||
PublicDynamicChoiceQuestion,
|
||||
} from "@/lib/birth-time-journey-turn-protocol";
|
||||
|
||||
type SharedQuestionProps = {
|
||||
readonly progress: DynamicJourneyProgress;
|
||||
readonly pending: boolean;
|
||||
readonly error: string;
|
||||
readonly onPause: () => void;
|
||||
readonly onFinish: () => void;
|
||||
};
|
||||
|
||||
type ChoiceQuestionProps = SharedQuestionProps & {
|
||||
readonly question: PublicDynamicChoiceQuestion;
|
||||
readonly onSelect: (optionId: string) => void;
|
||||
};
|
||||
|
||||
type ClarificationProps = SharedQuestionProps & {
|
||||
readonly onReframe: (note: string) => void;
|
||||
};
|
||||
|
||||
export function BirthTimeQuestionProgress({ progress }: {
|
||||
readonly progress: DynamicJourneyProgress;
|
||||
}) {
|
||||
return (
|
||||
<div className="birth-time-question-progress">
|
||||
<b>已完成 {progress.effectiveAnswerCount} 个有效判断</b>
|
||||
<span>当前候选范围:{rangeLabel(progress.currentRange)}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function BirthTimeChoiceQuestion(props: ChoiceQuestionProps) {
|
||||
const groups = choiceQuestionGroups(props.question);
|
||||
const [selectedId, setSelectedId] = useState("");
|
||||
const select = (option: PublicDynamicChoiceQuestion["options"][number]) => {
|
||||
const intent = choiceSelectionIntent(option);
|
||||
setSelectedId(intent.optionId);
|
||||
props.onSelect(intent.optionId);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="birth-time-choice-surface" aria-busy={props.pending}>
|
||||
<BirthTimeQuestionProgress progress={props.progress} />
|
||||
<fieldset className="birth-time-choice-question" disabled={props.pending}>
|
||||
<legend>{props.question.prompt}</legend>
|
||||
<div className="birth-time-primary-choices">
|
||||
{groups.primary.map((option) => (
|
||||
<button
|
||||
className="birth-time-choice-option is-primary"
|
||||
data-selected={selectedId === option.optionId}
|
||||
key={option.optionId}
|
||||
onClick={() => select(option)}
|
||||
type="button"
|
||||
>
|
||||
{option.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="birth-time-special-choices">
|
||||
{[groups.unknown, groups.unmatched].map((option) => (
|
||||
<button
|
||||
className="birth-time-choice-option is-secondary"
|
||||
data-selected={selectedId === option.optionId}
|
||||
key={option.optionId}
|
||||
onClick={() => select(option)}
|
||||
type="button"
|
||||
>
|
||||
{option.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
{props.pending && <p className="birth-time-choice-pending" role="status">正在缩小候选范围…</p>}
|
||||
<JourneyControls {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function BirthTimeUnmatchedClarification(props: ClarificationProps) {
|
||||
const [note, setNote] = useState("");
|
||||
return (
|
||||
<div className="birth-time-choice-surface" aria-busy={props.pending}>
|
||||
<BirthTimeQuestionProgress progress={props.progress} />
|
||||
<fieldset className="birth-time-choice-question" disabled={props.pending}>
|
||||
<legend>这道题都不符合你的情况吗?</legend>
|
||||
<label className="birth-time-unmatched-note">
|
||||
<span>补充一句(可选)</span>
|
||||
<textarea
|
||||
maxLength={240}
|
||||
onChange={(event) => setNote(event.target.value)}
|
||||
placeholder="例如:这段变化发生得更早,或情况不太一样"
|
||||
value={note}
|
||||
/>
|
||||
</label>
|
||||
<div className="birth-time-reframe-actions">
|
||||
<button className="button-secondary birth-time-guided-action" onClick={() => props.onReframe("")} type="button">换一道题</button>
|
||||
<button className="button-primary birth-time-guided-action" onClick={() => props.onReframe(normalizeUnmatchedNote(note))} type="button">提交补充并换题</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
{props.pending && <p className="birth-time-choice-pending" role="status">正在换一道更合适的问题…</p>}
|
||||
<JourneyControls {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function JourneyControls(props: SharedQuestionProps) {
|
||||
return (
|
||||
<>
|
||||
<div className="birth-time-journey-controls">
|
||||
<button className="button-text birth-time-guided-action" disabled={props.pending} onClick={props.onPause} type="button">暂停,稍后继续</button>
|
||||
<button className="button-text birth-time-guided-action" disabled={props.pending} onClick={props.onFinish} type="button">结束并保存当前范围</button>
|
||||
</div>
|
||||
{props.error && <p className="form-error" role="alert">{props.error}</p>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
"use client";
|
||||
|
||||
import { BirthTimeCandidateResult } from "@/components/birth-time-candidate-result";
|
||||
import { BirthTimeEvidenceDraftCard } from "@/components/birth-time-evidence-draft-card";
|
||||
import { BirthTimeGuideTurn } from "@/components/birth-time-guide-turn";
|
||||
import type { BirthTimeGuidedController } from "@/hooks/use-birth-time-guided-journey";
|
||||
import { assistantIntentCopy } from "@/lib/birth-time-intake-model";
|
||||
import type { LegacyJourneyClientResponse } from "@/lib/birth-time-journey-response-schema";
|
||||
import { guidedTurnIdentity } from "@/lib/birth-time-guided-turn-identity";
|
||||
import type { NextAction } from "@/lib/birth-time-journey-turn";
|
||||
|
||||
type LegacyRectificationProps = {
|
||||
readonly journey: LegacyJourneyClientResponse;
|
||||
readonly controller: BirthTimeGuidedController;
|
||||
readonly externalError: string;
|
||||
};
|
||||
|
||||
function actionHeading(action: NextAction): { readonly title: string; readonly badge: string } {
|
||||
switch (action.kind) {
|
||||
case "ask_baseline_evidence": return { title: "回想一条关键经历", badge: "基础证据" };
|
||||
case "ask_adaptive_evidence": return { title: "继续缩小候选范围", badge: "自适应校正" };
|
||||
case "review_evidence_draft": return { title: "确认经历草稿", badge: "待确认" };
|
||||
case "score_pending": return { title: "正在比较候选时间", badge: "评分中" };
|
||||
case "retry_scoring": return { title: "评分需要重试", badge: "可恢复" };
|
||||
case "present_low_result": return { title: "候选范围已保存", badge: "证据不足" };
|
||||
case "present_medium_result": return { title: "候选范围已形成", badge: "中等置信" };
|
||||
case "candidate_saved": return { title: "候选范围已保存", badge: "已保存" };
|
||||
case "request_candidate_confirmation": return { title: "确认候选时间", badge: "待确认" };
|
||||
case "ready": return { title: "排盘时间已更新", badge: "已完成" };
|
||||
case "paused": return { title: "校正已暂停", badge: "已保存" };
|
||||
}
|
||||
}
|
||||
|
||||
export function BirthTimeLegacyRectification(props: LegacyRectificationProps) {
|
||||
const action = props.journey.nextAction;
|
||||
const heading = actionHeading(action);
|
||||
const asksQuestion = action.kind === "ask_baseline_evidence" || action.kind === "ask_adaptive_evidence";
|
||||
const showsCandidate = action.kind === "present_low_result"
|
||||
|| action.kind === "present_medium_result"
|
||||
|| action.kind === "candidate_saved"
|
||||
|| action.kind === "request_candidate_confirmation"
|
||||
|| action.kind === "ready";
|
||||
const error = props.controller.error || props.externalError;
|
||||
|
||||
return (
|
||||
<section className="birth-time-rectification onboarding-card" aria-labelledby="birth-time-assessment-title">
|
||||
<div className="birth-time-assessment-heading">
|
||||
<div><span>出生时间评估</span><h2 id="birth-time-assessment-title">{heading.title}</h2></div>
|
||||
<span className="birth-time-status-badge">{heading.badge}</span>
|
||||
</div>
|
||||
<dl className="birth-time-range-summary">
|
||||
<div><dt>当前范围</dt><dd>{props.journey.snapshot.reportedRange.label}</dd></div>
|
||||
<div><dt>应用状态</dt><dd>{action.kind === "ready" ? `当前使用 ${action.activeTime}` : "尚未更新排盘时间"}</dd></div>
|
||||
</dl>
|
||||
<p className="birth-time-assistant-intent" role="status">{assistantIntentCopy(props.journey.snapshot.assistantIntent)}</p>
|
||||
{asksQuestion && (
|
||||
<BirthTimeGuideTurn
|
||||
key={guidedTurnIdentity(props.journey.turnVersion, action.question.questionId)}
|
||||
pending={props.controller.pending}
|
||||
progress={props.journey.progress}
|
||||
question={props.controller.question}
|
||||
onPause={props.controller.pause}
|
||||
onSkip={props.controller.skip}
|
||||
onSubmit={props.controller.submitMessage}
|
||||
/>
|
||||
)}
|
||||
{action.kind === "review_evidence_draft" && props.journey.evidenceDraft && (
|
||||
<BirthTimeEvidenceDraftCard
|
||||
key={props.journey.evidenceDraft.draftId}
|
||||
draft={props.journey.evidenceDraft}
|
||||
pending={props.controller.pending}
|
||||
onConfirm={props.controller.confirmDraft}
|
||||
onSkip={props.controller.skip}
|
||||
/>
|
||||
)}
|
||||
{action.kind === "score_pending" && <p className="birth-time-scoring-status" role="status">正在使用已确认的关键经历评分…</p>}
|
||||
{action.kind === "retry_scoring" && <button className="button-primary birth-time-guided-action" disabled={props.controller.pending} onClick={props.controller.retryScoring} type="button">重新评分</button>}
|
||||
{action.kind === "paused" && <button className="button-primary birth-time-guided-action" disabled={props.controller.pending} onClick={props.controller.resume} type="button">继续校正</button>}
|
||||
{showsCandidate && <BirthTimeCandidateResult controller={props.controller} journey={props.journey} />}
|
||||
{error && <p className="form-error" role="alert">{error}</p>}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { BirthTimeCandidateResult } from "@/components/birth-time-candidate-result";
|
||||
import { BirthTimeEvidenceDraftCard } from "@/components/birth-time-evidence-draft-card";
|
||||
import { BirthTimeGuideTurn } from "@/components/birth-time-guide-turn";
|
||||
import {
|
||||
BirthTimeChoiceQuestion,
|
||||
BirthTimeQuestionProgress,
|
||||
BirthTimeUnmatchedClarification,
|
||||
} from "@/components/birth-time-choice-question";
|
||||
import { BirthTimeLegacyRectification } from "@/components/birth-time-legacy-rectification";
|
||||
import type { BirthTimeGuidedController } from "@/hooks/use-birth-time-guided-journey";
|
||||
import { assistantIntentCopy } from "@/lib/birth-time-intake-model";
|
||||
import type { JourneyClientResponse } from "@/lib/birth-time-journey-client";
|
||||
import { guidedTurnIdentity } from "@/lib/birth-time-guided-turn-identity";
|
||||
import type { NextAction } from "@/lib/birth-time-journey-turn";
|
||||
import type { DynamicNextAction } from "@/lib/birth-time-journey-turn-protocol";
|
||||
|
||||
type BirthTimeRectificationProps = {
|
||||
@@ -16,72 +17,50 @@ type BirthTimeRectificationProps = {
|
||||
readonly externalError: string;
|
||||
};
|
||||
|
||||
function actionHeading(action: NextAction): { readonly title: string; readonly badge: string } {
|
||||
function dynamicHeading(action: DynamicNextAction): { readonly title: string; readonly badge: string } {
|
||||
switch (action.kind) {
|
||||
case "ask_baseline_evidence": return { title: "回想一条关键经历", badge: "基础证据" };
|
||||
case "ask_adaptive_evidence": return { title: "继续缩小候选范围", badge: "自适应校正" };
|
||||
case "review_evidence_draft": return { title: "确认经历草稿", badge: "待确认" };
|
||||
case "score_pending": return { title: "正在比较候选时间", badge: "评分中" };
|
||||
case "generate_dynamic_question": return { title: "准备下一道问题", badge: "正在生成" };
|
||||
case "retry_question_generation": return { title: "换一道问题", badge: "可重试" };
|
||||
case "ask_dynamic_choice": return { title: "选择更接近的情况", badge: "点击作答" };
|
||||
case "clarify_unmatched_answer": return { title: "换一道更合适的问题", badge: "可选补充" };
|
||||
case "score_pending": return { title: "正在缩小候选范围", badge: "评分中" };
|
||||
case "retry_scoring": return { title: "评分需要重试", badge: "可恢复" };
|
||||
case "present_low_result": return { title: "候选范围已保存", badge: "证据不足" };
|
||||
case "present_medium_result": return { title: "候选范围已形成", badge: "中等置信" };
|
||||
case "candidate_saved": return { title: "候选范围已保存", badge: "已保存" };
|
||||
case "present_low_result": return { title: "本次评估已结束", badge: "范围已保存" };
|
||||
case "present_medium_result": return { title: "候选范围已形成", badge: "评估已结束" };
|
||||
case "request_candidate_confirmation": return { title: "确认候选时间", badge: "待确认" };
|
||||
case "ready": return { title: "排盘时间已更新", badge: "已完成" };
|
||||
case "paused": return { title: "校正已暂停", badge: "已保存" };
|
||||
default: {
|
||||
const exhaustive: never = action;
|
||||
return exhaustive;
|
||||
}
|
||||
case "paused": return { title: "评估已暂停", badge: "进度已保存" };
|
||||
}
|
||||
}
|
||||
|
||||
function dynamicStatus(action: DynamicNextAction): string {
|
||||
function statusCopy(action: DynamicNextAction): string {
|
||||
switch (action.kind) {
|
||||
case "generate_dynamic_question": return "正在准备下一道候选区分问题…";
|
||||
case "retry_question_generation": return "问题暂时未能生成,可以重试当前步骤。";
|
||||
case "ask_dynamic_choice": return action.question.prompt;
|
||||
case "clarify_unmatched_answer": return "可以补充一句,再换一道更合适的问题。";
|
||||
case "score_pending": return "正在根据刚才的选择缩小候选范围…";
|
||||
case "retry_scoring": return "评分暂时未完成,可以重试同一任务。";
|
||||
case "present_low_result": return "本次评估已结束并保存当前候选范围。";
|
||||
case "present_medium_result": return "已形成较窄候选范围,本次评估已结束。";
|
||||
case "request_candidate_confirmation": return "请确认是否使用当前候选时间。";
|
||||
case "generate_dynamic_question": return "系统正在根据当前候选范围准备更有区分度的问题。";
|
||||
case "retry_question_generation": return "这道问题暂时没有生成成功,可以原地重试。";
|
||||
case "ask_dynamic_choice": return "点击最接近的选项即可,不需要再填写日期或时间。";
|
||||
case "clarify_unmatched_answer": return "补充说明完全可选,也可以直接换一道题。";
|
||||
case "score_pending": return "正在应用刚才的选择,完成后会自动进入下一步。";
|
||||
case "retry_scoring": return "刚才的选择已经保存,可以安全重试同一评分任务。";
|
||||
case "present_low_result": return "目前没有足够的新信息继续稳定缩小范围,本次评估已结束并保存当前候选范围。";
|
||||
case "present_medium_result": return "已形成较窄的候选范围,本次评估已结束;它不会自动改动当前排盘时间。";
|
||||
case "request_candidate_confirmation": return "只有明确确认后,候选时间才会用于当前排盘。";
|
||||
case "ready": return `当前排盘使用时间已更新为 ${action.activeTime}。`;
|
||||
case "paused": return "当前问题和候选范围已保存。";
|
||||
default: {
|
||||
const exhaustive: never = action;
|
||||
return exhaustive;
|
||||
}
|
||||
case "paused": return "当前问题和候选范围已保存,可以稍后从这里继续。";
|
||||
}
|
||||
}
|
||||
|
||||
export function BirthTimeRectification(props: BirthTimeRectificationProps) {
|
||||
if (props.journey.journeyProtocol === "dynamic-choice-v2") {
|
||||
const generationFailed = Boolean(props.controller.error || props.externalError)
|
||||
&& (props.journey.nextAction.kind === "generate_dynamic_question"
|
||||
|| props.journey.nextAction.kind === "retry_question_generation");
|
||||
return (
|
||||
<section className="birth-time-rectification onboarding-card" aria-labelledby="birth-time-assessment-title">
|
||||
<div className="birth-time-assessment-heading">
|
||||
<div><span>出生时间评估</span><h2 id="birth-time-assessment-title">动态候选评估</h2></div>
|
||||
<span className="birth-time-status-badge">动态评估</span>
|
||||
</div>
|
||||
<p className="birth-time-assistant-intent" role="status">{dynamicStatus(props.journey.nextAction)}</p>
|
||||
{generationFailed && <button className="button-secondary birth-time-guided-action" type="button" onClick={props.controller.retryQuestionGeneration}>重试当前问题</button>}
|
||||
{(props.controller.error || props.externalError) && <p className="form-error" role="alert">{props.controller.error || props.externalError}</p>}
|
||||
</section>
|
||||
);
|
||||
if (props.journey.journeyProtocol !== "dynamic-choice-v2") {
|
||||
return <BirthTimeLegacyRectification {...props} journey={props.journey} />;
|
||||
}
|
||||
const action = props.journey.nextAction;
|
||||
const heading = actionHeading(action);
|
||||
const asksQuestion = action.kind === "ask_baseline_evidence" || action.kind === "ask_adaptive_evidence";
|
||||
const heading = dynamicHeading(action);
|
||||
const error = props.controller.error || props.externalError;
|
||||
const showsProgress = action.kind !== "ask_dynamic_choice" && action.kind !== "clarify_unmatched_answer";
|
||||
const showsCandidate = action.kind === "present_low_result"
|
||||
|| action.kind === "present_medium_result"
|
||||
|| action.kind === "candidate_saved"
|
||||
|| action.kind === "request_candidate_confirmation"
|
||||
|| action.kind === "ready";
|
||||
const error = props.controller.error || props.externalError;
|
||||
|
||||
return (
|
||||
<section className="birth-time-rectification onboarding-card" aria-labelledby="birth-time-assessment-title">
|
||||
@@ -89,53 +68,43 @@ export function BirthTimeRectification(props: BirthTimeRectificationProps) {
|
||||
<div><span>出生时间评估</span><h2 id="birth-time-assessment-title">{heading.title}</h2></div>
|
||||
<span className="birth-time-status-badge">{heading.badge}</span>
|
||||
</div>
|
||||
<dl className="birth-time-range-summary">
|
||||
<div><dt>当前范围</dt><dd>{props.journey.snapshot.reportedRange.label}</dd></div>
|
||||
<div><dt>应用状态</dt><dd>{action.kind === "ready" ? `当前使用 ${action.activeTime}` : "尚未更新排盘时间"}</dd></div>
|
||||
</dl>
|
||||
<p className="birth-time-assistant-intent" role="status">{assistantIntentCopy(props.journey.snapshot.assistantIntent)}</p>
|
||||
|
||||
{asksQuestion && (
|
||||
<BirthTimeGuideTurn
|
||||
key={guidedTurnIdentity(props.journey.turnVersion, action.question.questionId)}
|
||||
{showsProgress && <BirthTimeQuestionProgress progress={props.journey.progress} />}
|
||||
<p className="birth-time-assistant-intent" role="status">{statusCopy(action)}</p>
|
||||
{action.kind === "ask_dynamic_choice" && (
|
||||
<BirthTimeChoiceQuestion
|
||||
key={`${props.journey.turnVersion}:${action.question.questionId}`}
|
||||
error={error}
|
||||
onFinish={props.controller.finish}
|
||||
onPause={props.controller.pause}
|
||||
onSelect={props.controller.selectOption}
|
||||
pending={props.controller.pending}
|
||||
progress={props.journey.progress}
|
||||
question={props.controller.question}
|
||||
onPause={props.controller.pause}
|
||||
onSkip={props.controller.skip}
|
||||
onSubmit={props.controller.submitMessage}
|
||||
question={action.question}
|
||||
/>
|
||||
)}
|
||||
{action.kind === "review_evidence_draft" && props.journey.evidenceDraft && (
|
||||
<BirthTimeEvidenceDraftCard
|
||||
key={props.journey.evidenceDraft.draftId}
|
||||
draft={props.journey.evidenceDraft}
|
||||
{action.kind === "clarify_unmatched_answer" && (
|
||||
<BirthTimeUnmatchedClarification
|
||||
error={error}
|
||||
onFinish={props.controller.finish}
|
||||
onPause={props.controller.pause}
|
||||
onReframe={props.controller.submitUnmatchedContext}
|
||||
pending={props.controller.pending}
|
||||
onConfirm={props.controller.confirmDraft}
|
||||
onSkip={props.controller.skip}
|
||||
progress={props.journey.progress}
|
||||
/>
|
||||
)}
|
||||
{(action.kind === "generate_dynamic_question" || action.kind === "retry_question_generation") && error && (
|
||||
<button className="button-secondary birth-time-guided-action" disabled={props.controller.pending} onClick={props.controller.retryQuestionGeneration} type="button">重试当前问题</button>
|
||||
)}
|
||||
{action.kind === "score_pending" && (
|
||||
<div className="birth-time-scoring-status" aria-live="polite">
|
||||
<b>正在使用已确认的关键经历评分</b>
|
||||
<p>完成后会自动显示下一题或候选结果,无需再点击比较按钮。</p>
|
||||
{props.controller.pollRecoverable && <button className="button-secondary birth-time-guided-action" type="button" onClick={props.controller.retryScoring}>重新检查评分状态</button>}
|
||||
</div>
|
||||
)}
|
||||
{action.kind === "retry_scoring" && (
|
||||
<div className="birth-time-scoring-status" role="status">
|
||||
<p>已确认的关键经历仍然安全保存,可以<span className="phrase-nowrap">重试</span>同一评分任务。</p>
|
||||
<button className="button-primary birth-time-guided-action" disabled={props.controller.pending} type="button" onClick={props.controller.retryScoring}>重新评分</button>
|
||||
</div>
|
||||
)}
|
||||
{action.kind === "paused" && (
|
||||
<div className="birth-time-candidate-terminal" role="status">
|
||||
<p>当前问题和证据已保存,可以现在继续,也可以稍后回来。</p>
|
||||
<button className="button-primary birth-time-guided-action" disabled={props.controller.pending} type="button" onClick={props.controller.resume}>继续校正</button>
|
||||
<b>正在缩小候选范围…</b>
|
||||
{props.controller.pollRecoverable && <button className="button-secondary birth-time-guided-action" onClick={props.controller.retryScoring} type="button">重新检查评分状态</button>}
|
||||
</div>
|
||||
)}
|
||||
{action.kind === "retry_scoring" && <button className="button-primary birth-time-guided-action" disabled={props.controller.pending} onClick={props.controller.retryScoring} type="button">重新评分</button>}
|
||||
{action.kind === "paused" && <button className="button-primary birth-time-guided-action" disabled={props.controller.pending} onClick={props.controller.resume} type="button">继续本次评估</button>}
|
||||
{showsCandidate && <BirthTimeCandidateResult controller={props.controller} journey={props.journey} />}
|
||||
{error && <p className="form-error" role="alert">{error}</p>}
|
||||
{error && action.kind !== "ask_dynamic_choice" && action.kind !== "clarify_unmatched_answer" && <p className="form-error" role="alert">{error}</p>}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user