fix(rectification): offer times under the agent bubble
Keep the natal house table as a live snapshot outside chat, and show candidate times only after selection is allowed, attached to the latest settled reply. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -54,6 +54,53 @@ type PersistedTurn = Readonly<{
|
||||
|
||||
type CandidateResult = RectificationCandidateResult | null;
|
||||
|
||||
function RectificationCandidateCards({
|
||||
result,
|
||||
acceptingCandidateId,
|
||||
readonly,
|
||||
onAccept,
|
||||
}: Readonly<{
|
||||
result: RectificationCandidateResult;
|
||||
acceptingCandidateId: string | null;
|
||||
readonly: boolean;
|
||||
onAccept: (candidateId: string) => void;
|
||||
}>) {
|
||||
return (
|
||||
<section className="rectification-candidates" aria-label="生时校正候选时间">
|
||||
<div className="rectification-candidates-heading">
|
||||
<strong>当前可能的出生时间</strong>
|
||||
<span>这次校正的结果是采用一个代表性时间作当前排盘。相邻分钟目前分不开,还不能确认唯一分钟。可以先采用,也可以继续补充事件或改选;新增证据后,候选和相对支持度会重新计算。</span>
|
||||
<span>相对支持度不是统计概率;采用不等于确认出生时间,也不会覆盖原始填报时间。</span>
|
||||
</div>
|
||||
<div className="rectification-candidate-list">
|
||||
{result.candidates.map((candidate) => {
|
||||
const selected = result.selectedTime === candidate.time;
|
||||
const recommended = isRecommendedRectificationCandidate(result, candidate);
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={`rectification-candidate${selected ? " is-selected" : ""}`}
|
||||
key={candidate.candidateId}
|
||||
disabled={selected || Boolean(acceptingCandidateId) || readonly}
|
||||
onClick={() => onAccept(candidate.candidateId)}
|
||||
>
|
||||
<span className="rectification-candidate-time">
|
||||
<strong>{candidate.time}</strong>
|
||||
{selected && <span className="rectification-candidate-badge">已采用</span>}
|
||||
{recommended && <span className="rectification-candidate-badge">当前推荐</span>}
|
||||
</span>
|
||||
<span className="rectification-candidate-support">相对支持度 {candidate.relativeSupport}</span>
|
||||
<span className="rectification-candidate-action">
|
||||
{selected ? "已采用" : acceptingCandidateId === candidate.candidateId ? "正在采用…" : result.selectedTime ? "改选为此时间" : "采用此时间"}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
type RectificationAgenticChatProps = Readonly<{
|
||||
caseId: string;
|
||||
sessionId: string;
|
||||
@@ -194,7 +241,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
top: container.scrollHeight,
|
||||
behavior: busy || reduceMotion ? "auto" : "smooth",
|
||||
});
|
||||
}, [busy, error, messages, savedTime]);
|
||||
}, [busy, candidateResult, error, messages, savedTime]);
|
||||
|
||||
// Candidate snapshot comes from the persisted Candidate Snapshot API, never
|
||||
// from parsing agent text or hidden sentinels.
|
||||
@@ -498,6 +545,20 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
&& Boolean(message.turnId)
|
||||
&& Boolean(message.text)
|
||||
))?.renderKey;
|
||||
const latestOfferMessageKey = [...messages]
|
||||
.reverse()
|
||||
.find((message) => (
|
||||
message.role === "assistant"
|
||||
&& message.state === "settled"
|
||||
&& !message.failed
|
||||
&& Boolean(message.text)
|
||||
))?.renderKey;
|
||||
const showSelectionCards = Boolean(
|
||||
candidateResult?.selectionAllowed
|
||||
&& latestOfferMessageKey
|
||||
&& !busy
|
||||
&& regeneratingMessageKey === null,
|
||||
);
|
||||
const canSend = !busy && !readonly && !regeneratingMessageKey;
|
||||
|
||||
return (
|
||||
@@ -552,45 +613,21 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
onRegenerate={() => void regenerateMessage(message)}
|
||||
/>
|
||||
)}
|
||||
{showSelectionCards && message.renderKey === latestOfferMessageKey && candidateResult && (
|
||||
<RectificationCandidateCards
|
||||
result={candidateResult}
|
||||
acceptingCandidateId={acceptingCandidateId}
|
||||
readonly={readonly}
|
||||
onAccept={(candidateId) => void acceptCandidate(candidateId)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{candidateResult?.houseTable && (
|
||||
<RectificationHouseTableView table={candidateResult.houseTable} />
|
||||
)}
|
||||
{candidateResult?.selectionAllowed && (
|
||||
<section className="rectification-candidates" aria-label="生时校正候选时间">
|
||||
<div className="rectification-candidates-heading">
|
||||
<strong>当前可能的出生时间</strong>
|
||||
<span>这次校正的结果是采用一个代表性时间作当前排盘。相邻分钟目前分不开,还不能确认唯一分钟。可以先采用,也可以继续补充事件或改选;新增证据后,候选和相对支持度会重新计算。</span>
|
||||
<span>相对支持度不是统计概率;采用不等于确认出生时间,也不会覆盖原始填报时间。</span>
|
||||
</div>
|
||||
<div className="rectification-candidate-list">
|
||||
{candidateResult.candidates.map((candidate) => {
|
||||
const selected = candidateResult.selectedTime === candidate.time;
|
||||
const recommended = isRecommendedRectificationCandidate(candidateResult, candidate);
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={`rectification-candidate${selected ? " is-selected" : ""}`}
|
||||
key={candidate.candidateId}
|
||||
disabled={selected || Boolean(acceptingCandidateId) || readonly}
|
||||
onClick={() => void acceptCandidate(candidate.candidateId)}
|
||||
>
|
||||
<span className="rectification-candidate-time">
|
||||
<strong>{candidate.time}</strong>
|
||||
{selected && <span className="rectification-candidate-badge">已采用</span>}
|
||||
{recommended && <span className="rectification-candidate-badge">当前推荐</span>}
|
||||
</span>
|
||||
<span className="rectification-candidate-support">相对支持度 {candidate.relativeSupport}</span>
|
||||
<span className="rectification-candidate-action">
|
||||
{selected ? "已采用" : acceptingCandidateId === candidate.candidateId ? "正在采用…" : candidateResult.selectedTime ? "改选为此时间" : "采用此时间"}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
<div className="rectification-snapshot">
|
||||
<RectificationHouseTableView table={candidateResult.houseTable} />
|
||||
</div>
|
||||
)}
|
||||
{savedTime && (
|
||||
<p className="rectification-saved" role="status">
|
||||
|
||||
@@ -6,12 +6,13 @@ export function RectificationHouseTableView({
|
||||
table,
|
||||
}: Readonly<{ table: RectificationHouseTable }>) {
|
||||
return (
|
||||
<section className="rectification-house-table" aria-label="本命宫位表">
|
||||
<section className="rectification-house-table" aria-label="当前本命宫位" aria-live="polite">
|
||||
<div className="rectification-house-table__heading">
|
||||
<strong>本命宫位</strong>
|
||||
<strong>当前本命宫位</strong>
|
||||
<span>
|
||||
按 {table.time} 排出,上升{table.lagna}。这是当前代表性时间下的盘面,不是已确认出生盘。
|
||||
按 {table.time} 排出,上升{table.lagna}。补充经历后会按新线索重算。
|
||||
</span>
|
||||
<span>这是当前代表性时间下的盘面,不是已确认出生盘。</span>
|
||||
</div>
|
||||
<div className="markdown-table">
|
||||
<table>
|
||||
|
||||
Reference in New Issue
Block a user