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:
@@ -1847,17 +1847,25 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
|
||||
.birth-time-clock-menu.select-content { width: 108px; min-width: 108px; }
|
||||
.birth-time-clock-menu .select-item { justify-content: flex-start; }
|
||||
|
||||
.rectification-snapshot {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
margin: 8px 0 16px;
|
||||
}
|
||||
.rectification-candidates {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
width: calc(100% - var(--assistant-content-inset));
|
||||
margin: 8px 0 16px;
|
||||
margin-inline-start: var(--assistant-content-inset);
|
||||
margin: 0;
|
||||
padding: 18px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--color-canvas-soft);
|
||||
}
|
||||
.rectification-message-wrap .rectification-candidates {
|
||||
width: calc(100% - var(--assistant-content-inset));
|
||||
margin: var(--space-3) 0 var(--space-4);
|
||||
margin-inline-start: var(--assistant-content-inset);
|
||||
}
|
||||
.rectification-candidates-heading { display: grid; gap: 5px; }
|
||||
.rectification-candidates-heading strong { font-size: var(--type-title-sm); font-family: var(--font-display); font-weight: 600; letter-spacing: -.2px; }
|
||||
.rectification-candidates-heading span,
|
||||
@@ -1905,18 +1913,15 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
|
||||
font-weight: 650;
|
||||
}
|
||||
.rectification-candidate-action { align-self: end; color: var(--color-action); font-size: 13px; font-weight: 650; }
|
||||
.rectification-saved {
|
||||
width: calc(100% - var(--assistant-content-inset));
|
||||
margin: 8px 0 16px;
|
||||
margin-inline-start: var(--assistant-content-inset);
|
||||
color: var(--color-ink);
|
||||
font-size: var(--type-body-sm);
|
||||
}
|
||||
.rectification-saved { margin: 8px 0 16px; color: var(--color-ink); font-size: var(--type-body-sm); }
|
||||
.rectification-house-table {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
margin: 8px 0 16px;
|
||||
padding-inline-start: var(--assistant-content-inset);
|
||||
margin: 0;
|
||||
padding: 18px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--color-canvas-soft);
|
||||
}
|
||||
.rectification-house-table__heading { display: grid; gap: 5px; }
|
||||
.rectification-house-table__heading strong {
|
||||
@@ -1932,6 +1937,7 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
|
||||
}
|
||||
.rectification-house-table .markdown-table { margin: 0; }
|
||||
@media (max-width: 640px) {
|
||||
.rectification-house-table,
|
||||
.rectification-candidates { padding: 14px; }
|
||||
.rectification-candidate-list {
|
||||
grid-template-columns: none;
|
||||
|
||||
@@ -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