fix(rectification): show natal houses and lagna changes in a live right rail
The board was live data sitting under chat, so each new event looked like nothing changed. Keep candidate cards in the transcript and group 换升 by minute for the side panel. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { ArrowUp } from "lucide-react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
|
||||
import { parseAgentReply } from "@/lib/agent-reply";
|
||||
import type { ChatMessage, ChatMessageView } from "@/lib/chat-message-view";
|
||||
import {
|
||||
@@ -17,6 +17,10 @@ import {
|
||||
workingRectificationHouseTable,
|
||||
type RectificationCandidateResult,
|
||||
} from "@/lib/rectification-candidate-result";
|
||||
import {
|
||||
diffRectificationBoard,
|
||||
RECTIFICATION_BOARD_SPLIT_MIN_PX,
|
||||
} from "@/lib/rectification-board-model";
|
||||
import { membershipHref } from "@/lib/membership";
|
||||
import { vargaSentenceFromMethods } from "@/lib/rectification-varga-sentence";
|
||||
import {
|
||||
@@ -30,8 +34,7 @@ import {
|
||||
toggleChatMessageFeedback,
|
||||
} from "./chat-message-actions";
|
||||
import { ModelSelector } from "./model-selector";
|
||||
import { RectificationHouseTableView } from "./rectification-house-table";
|
||||
import { TechniqueAuditDisclosure, ConfirmationGateDisclosure } from "./technique-audit-disclosure";
|
||||
import { RectificationBoard, RectificationBoardPeek } from "./rectification-board";
|
||||
import { Button } from "./ui/button";
|
||||
import { Textarea } from "./ui/textarea";
|
||||
|
||||
@@ -104,75 +107,6 @@ function RectificationCandidateCards({
|
||||
);
|
||||
}
|
||||
|
||||
function RectificationRefinementPanel({
|
||||
result,
|
||||
}: Readonly<{ result: RectificationCandidateResult }>) {
|
||||
const transitions = result.windowTransitions;
|
||||
const ledger = result.eventDashaLedger;
|
||||
const agreement = result.dashaAgreement;
|
||||
const contrast = result.lagnaContrast;
|
||||
const nakshatra = result.nakshatraBoundary;
|
||||
const stage = result.precisionStage;
|
||||
if (
|
||||
transitions.length === 0
|
||||
&& ledger.length === 0
|
||||
&& !agreement
|
||||
&& !contrast
|
||||
&& !nakshatra?.near_boundary
|
||||
&& !stage
|
||||
) return null;
|
||||
return (
|
||||
<section className="rectification-refinement" aria-label="校正收窄观察">
|
||||
{stage && (
|
||||
<p className="rectification-refinement__stage">{stage.user_meaning}</p>
|
||||
)}
|
||||
{transitions.length > 0 && (
|
||||
<div>
|
||||
<strong>换升时刻</strong>
|
||||
<ul>
|
||||
{transitions.map((item) => (
|
||||
<li key={`${item.layer}-${item.at}`}>{item.user_meaning}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{contrast && (
|
||||
<div>
|
||||
<strong>两段本命上升</strong>
|
||||
<p>{contrast.user_meaning}</p>
|
||||
<ul>
|
||||
{contrast.intervals.map((interval) => (
|
||||
<li key={`${interval.start}-${interval.end}`}>
|
||||
{interval.start}-{interval.end}:{interval.lagna}
|
||||
{interval.lords.l1 ? `;一/四/七/十宫主 ${[interval.lords.l1, interval.lords.l4, interval.lords.l7, interval.lords.l10].filter(Boolean).join("、")}` : ""}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{ledger.length > 0 && (
|
||||
<div>
|
||||
<strong>经历与大运对照</strong>
|
||||
<ul>
|
||||
{ledger.map((item) => (
|
||||
<li key={item.summary}>{item.user_meaning}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{agreement && (
|
||||
<p>{agreement.user_meaning}</p>
|
||||
)}
|
||||
{nakshatra?.near_boundary && nakshatra.user_meaning && (
|
||||
<div>
|
||||
<strong>交界节奏</strong>
|
||||
<p>{nakshatra.user_meaning}</p>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function RectificationOosPanel({
|
||||
prompts,
|
||||
}: Readonly<{ prompts: RectificationCandidateResult["oosBlindPrompts"] }>) {
|
||||
@@ -319,9 +253,37 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
const [copiedMessageKey, setCopiedMessageKey] = useState<string | null>(null);
|
||||
const [regeneratingMessageKey, setRegeneratingMessageKey] = useState<string | null>(null);
|
||||
const conversation = useRef<HTMLElement>(null);
|
||||
const workspace = useRef<HTMLDivElement>(null);
|
||||
const composer = useRef<HTMLTextAreaElement>(null);
|
||||
const keyCounter = useRef(0);
|
||||
const openingStarted = useRef(false);
|
||||
const previousBoardResult = useRef<CandidateResult>(null);
|
||||
const [compactBoard, setCompactBoard] = useState(false);
|
||||
const [boardOpen, setBoardOpen] = useState(false);
|
||||
const [boardDiff, setBoardDiff] = useState(() => diffRectificationBoard(null, null));
|
||||
const boardId = useId();
|
||||
const boardTitleId = useId();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const node = workspace.current;
|
||||
if (!node) return;
|
||||
const update = () => {
|
||||
const nextCompact = node.clientWidth < RECTIFICATION_BOARD_SPLIT_MIN_PX;
|
||||
setCompactBoard(nextCompact);
|
||||
if (!nextCompact) setBoardOpen(false);
|
||||
};
|
||||
update();
|
||||
const observer = new ResizeObserver(update);
|
||||
observer.observe(node);
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setBoardDiff(diffRectificationBoard(previousBoardResult.current, candidateResult));
|
||||
previousBoardResult.current = candidateResult;
|
||||
}, [candidateResult]);
|
||||
|
||||
const closeBoard = useCallback(() => setBoardOpen(false), []);
|
||||
|
||||
const setPending = useCallback((value: boolean) => {
|
||||
setBusy(value);
|
||||
@@ -684,9 +646,13 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
const canSend = !busy && !readonly && !regeneratingMessageKey;
|
||||
|
||||
return (
|
||||
<>
|
||||
<section ref={conversation} className="conversation" aria-label="生时校正对话" aria-busy={busy || regeneratingMessageKey !== null}>
|
||||
<div className="message-list" aria-live="polite">
|
||||
<div
|
||||
ref={workspace}
|
||||
className={`rectification-workspace${compactBoard ? " is-compact" : ""}${boardOpen ? " is-board-open" : ""}`}
|
||||
>
|
||||
<div className="rectification-workspace__chat" inert={compactBoard && boardOpen ? true : undefined}>
|
||||
<section ref={conversation} className="conversation is-rectification" aria-label="生时校正对话" aria-busy={busy || regeneratingMessageKey !== null}>
|
||||
<div className="message-list">
|
||||
{pendingConsultationQuestion?.trim() && (
|
||||
<p className="rectification-pending-note">
|
||||
先陪你核对出生时间范围,之后会回到你原来的问题:“{pendingConsultationQuestion.trim()}”
|
||||
@@ -746,19 +712,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{candidateResult?.houseTable && (
|
||||
<div className="rectification-snapshot">
|
||||
{candidateResult.natalRecast && (
|
||||
<p className="rectification-refinement__stage">{candidateResult.natalRecast.user_meaning}</p>
|
||||
)}
|
||||
<RectificationHouseTableView table={candidateResult.houseTable} />
|
||||
<ConfirmationGateDisclosure gate={candidateResult.confirmationGate} />
|
||||
{savedStatus === "accepted" && candidateResult.techniqueAudit.length > 0 && (
|
||||
<TechniqueAuditDisclosure rows={candidateResult.techniqueAudit} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{candidateResult && <RectificationRefinementPanel result={candidateResult} />}
|
||||
{savedTime && (
|
||||
<div className="rectification-saved-wrap">
|
||||
<p className="rectification-saved" role="status">
|
||||
@@ -786,8 +739,16 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="composer-wrap">
|
||||
<form className="composer" onSubmit={submit}>
|
||||
<div className="composer-wrap">
|
||||
{compactBoard && (
|
||||
<RectificationBoardPeek
|
||||
result={candidateResult}
|
||||
expanded={boardOpen}
|
||||
boardId={boardId}
|
||||
onOpen={() => setBoardOpen(true)}
|
||||
/>
|
||||
)}
|
||||
<form className="composer" onSubmit={submit}>
|
||||
<Textarea
|
||||
ref={composer}
|
||||
aria-label={readonly ? "该校正已结束,只能查看历史" : "继续描述你的经历或回答"}
|
||||
@@ -817,7 +778,18 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
onSelect={onSelectModel}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
<RectificationBoard
|
||||
result={candidateResult}
|
||||
savedStatus={savedStatus}
|
||||
diff={boardDiff}
|
||||
compact={compactBoard}
|
||||
open={boardOpen}
|
||||
boardId={boardId}
|
||||
titleId={boardTitleId}
|
||||
onClose={closeBoard}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user