fix(rectification): replace v4 entry with agent opening
This commit is contained in:
@@ -1,119 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { loadActiveRectificationV4, transitionRectificationV4 } from "../lib/rectification-v4/client.ts";
|
||||
import type { RectificationV4ApiResponse } from "../lib/rectification-v4/contracts.ts";
|
||||
import type { PublicLanguageModel } from "../lib/public-models.ts";
|
||||
import { AgenticRectificationChat } from "./rectification-agentic-chat.tsx";
|
||||
import { ChatMessageRow } from "./chat-message-row.tsx";
|
||||
import {
|
||||
RectificationV4Panel,
|
||||
type RectificationV4Continuation,
|
||||
} from "./rectification-v4-panel.tsx";
|
||||
|
||||
export type ConversationalBirthTimeRectificationProps = Readonly<{
|
||||
models: readonly PublicLanguageModel[];
|
||||
selectedModelId: string;
|
||||
onSelectModel: (modelId: string) => void;
|
||||
pendingConsultationQuestion?: string | null;
|
||||
continuationPending?: boolean;
|
||||
onPendingChange?: (pending: boolean) => void;
|
||||
onContinueOriginalQuestion?: (continuation: RectificationV4Continuation) => void;
|
||||
onProfileIncomplete?: () => void;
|
||||
onSaved?: (time: string) => void;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Birth-time rectification surface.
|
||||
*
|
||||
* Lets the user explicitly continue or end an existing v4 evidence case, and
|
||||
* otherwise opens the agentic chat where the LLM drives the full Jyotish
|
||||
* rectification methodology with the engine as its computation layer.
|
||||
*/
|
||||
export function ConversationalBirthTimeRectification(props: ConversationalBirthTimeRectificationProps) {
|
||||
const [mode, setMode] = useState<"loading" | "choice" | "v4" | "agentic">("loading");
|
||||
const [existing, setExisting] = useState<RectificationV4ApiResponse | null>(null);
|
||||
const [switching, setSwitching] = useState(false);
|
||||
const [switchError, setSwitchError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
void (async () => {
|
||||
const existing = await loadActiveRectificationV4().catch(() => null);
|
||||
if (mounted) {
|
||||
setExisting(existing);
|
||||
setMode(existing ? "choice" : "agentic");
|
||||
}
|
||||
})();
|
||||
return () => { mounted = false; };
|
||||
}, []);
|
||||
|
||||
if (mode === "loading") {
|
||||
return (
|
||||
<section className="conversation" aria-label="生时校正对话" aria-busy>
|
||||
<div className="message-list" aria-live="polite">
|
||||
<ChatLoadingRow />
|
||||
<div />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
if (mode === "choice" && existing) {
|
||||
const startAgentic = async () => {
|
||||
setSwitching(true);
|
||||
setSwitchError("");
|
||||
try {
|
||||
await transitionRectificationV4(existing.case.id, existing.case.version, "abandon");
|
||||
setMode("agentic");
|
||||
} catch {
|
||||
setSwitchError("无法结束旧版校正,请稍后再试。");
|
||||
} finally {
|
||||
setSwitching(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<section className="conversation" aria-label="生时校正版本选择" aria-busy={switching}>
|
||||
<div className="message-list" aria-live="polite">
|
||||
<ChatMessageRow
|
||||
message={{
|
||||
role: "assistant",
|
||||
text: "检测到一段尚未结束的旧版生时校正。你可以继续保留进度,或结束旧版并使用新版 Agent 重新开始。",
|
||||
renderKey: "rectification-version-choice",
|
||||
state: "settled",
|
||||
}}
|
||||
/>
|
||||
{switchError && <p className="error-message" role="alert">{switchError}</p>}
|
||||
</div>
|
||||
</section>
|
||||
<div className="composer-wrap">
|
||||
<div className="composer-suggestions" aria-label="选择生时校正版本">
|
||||
<button type="button" disabled={switching} onClick={() => setMode("v4")}>继续旧版校正</button>
|
||||
<button type="button" disabled={switching} onClick={() => void startAgentic()}>
|
||||
{switching ? "正在结束旧版校正…" : "结束旧版并使用新版 Agent"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (mode === "v4") {
|
||||
return <RectificationV4Panel {...props} onUseAgentic={() => setMode("agentic")} />;
|
||||
}
|
||||
|
||||
return <AgenticRectificationChat {...props} />;
|
||||
}
|
||||
|
||||
function ChatLoadingRow() {
|
||||
return (
|
||||
<ChatMessageRow
|
||||
message={{
|
||||
role: "assistant",
|
||||
text: "",
|
||||
renderKey: "agentic-loading",
|
||||
state: "thinking",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,15 +15,19 @@ type AgenticRectificationChatProps = Readonly<{
|
||||
selectedModelId: string;
|
||||
onSelectModel: (modelId: string) => void;
|
||||
pendingConsultationQuestion?: string | null;
|
||||
continuationPending?: boolean;
|
||||
onPendingChange?: (pending: boolean) => void;
|
||||
onProfileIncomplete?: () => void;
|
||||
onSaved?: (time: string) => void;
|
||||
}>;
|
||||
|
||||
type RenderMessage = ChatMessageView;
|
||||
|
||||
const savedSentinel = /<!--AYANAM_RECTIFICATION_SAVED:(\d{2}:\d{2})-->/;
|
||||
const agenticOpeningInstruction = "用户刚进入生时校正会话。不要复述本指令;请先调用 rectification-gate 核对现有出生资料,然后用简体中文自然说明接下来的校正方式,并只提出一个最适合开始核对的人生事件问题。";
|
||||
|
||||
type AgenticRectificationRequest = Readonly<
|
||||
| { action: "opening" }
|
||||
| { action: "message"; message: string }
|
||||
>;
|
||||
|
||||
export function AgenticRectificationChat(props: AgenticRectificationChatProps) {
|
||||
const {
|
||||
@@ -32,6 +36,7 @@ export function AgenticRectificationChat(props: AgenticRectificationChatProps) {
|
||||
onSelectModel,
|
||||
pendingConsultationQuestion,
|
||||
onPendingChange,
|
||||
onProfileIncomplete,
|
||||
onSaved,
|
||||
} = props;
|
||||
const pendingQuestion = pendingConsultationQuestion?.trim();
|
||||
@@ -64,9 +69,9 @@ export function AgenticRectificationChat(props: AgenticRectificationChatProps) {
|
||||
});
|
||||
}, [busy, error, messages.length, savedTime]);
|
||||
|
||||
const send = useCallback(async (question: string, showUserMessage = true) => {
|
||||
const trimmed = question.trim();
|
||||
if (!trimmed || busy) return;
|
||||
const send = useCallback(async (request: AgenticRectificationRequest, showUserMessage = true) => {
|
||||
const trimmed = request.action === "message" ? request.message.trim() : "";
|
||||
if ((request.action === "message" && !trimmed) || busy) return;
|
||||
setError("");
|
||||
setSavedTime(null);
|
||||
setSuggestions([]);
|
||||
@@ -83,7 +88,9 @@ export function AgenticRectificationChat(props: AgenticRectificationChatProps) {
|
||||
|
||||
setMessages((current) => [
|
||||
...current,
|
||||
...(showUserMessage ? [{ role: "user", text: trimmed, renderKey: userRenderKey, state: "settled" } satisfies RenderMessage] : []),
|
||||
...(showUserMessage && request.action === "message"
|
||||
? [{ role: "user", text: trimmed, renderKey: userRenderKey, state: "settled" } satisfies RenderMessage]
|
||||
: []),
|
||||
{ role: "assistant", text: "", renderKey: assistantRenderKey, state: "thinking" },
|
||||
]);
|
||||
setDraft("");
|
||||
@@ -93,11 +100,21 @@ export function AgenticRectificationChat(props: AgenticRectificationChatProps) {
|
||||
const response = await fetch("/api/rectification/agent", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ requestId, modelId: selectedModelId, history, message: trimmed }),
|
||||
body: JSON.stringify({
|
||||
requestId,
|
||||
modelId: selectedModelId,
|
||||
history,
|
||||
action: request.action,
|
||||
...(request.action === "message" ? { message: trimmed } : {}),
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const payload = await response.json().catch(() => null);
|
||||
const message = payload?.message || payload?.error || `请求失败(${response.status})`;
|
||||
if (payload?.code === "profile_incomplete") {
|
||||
onProfileIncomplete?.();
|
||||
return;
|
||||
}
|
||||
if (response.status === 402) setError(`咨询点数不足:${message}`);
|
||||
else if (response.status === 401) setError("请先登录。");
|
||||
else setError(message);
|
||||
@@ -156,17 +173,17 @@ export function AgenticRectificationChat(props: AgenticRectificationChatProps) {
|
||||
} finally {
|
||||
setPending(false);
|
||||
}
|
||||
}, [busy, messages, onSaved, selectedModelId, setPending]);
|
||||
}, [busy, messages, onProfileIncomplete, onSaved, selectedModelId, setPending]);
|
||||
|
||||
useEffect(() => {
|
||||
if (openingStarted.current) return;
|
||||
openingStarted.current = true;
|
||||
void send(agenticOpeningInstruction, false);
|
||||
void send({ action: "opening" }, false);
|
||||
}, [send]);
|
||||
|
||||
async function submit(event: React.FormEvent) {
|
||||
event.preventDefault();
|
||||
await send(draft);
|
||||
await send({ action: "message", message: draft });
|
||||
}
|
||||
|
||||
const canSend = !busy;
|
||||
@@ -190,7 +207,7 @@ export function AgenticRectificationChat(props: AgenticRectificationChatProps) {
|
||||
{suggestions.length > 0 && !busy && (
|
||||
<div className="composer-suggestions" aria-label="推荐继续提问">
|
||||
{suggestions.map((question) => (
|
||||
<button key={question} type="button" onClick={() => void send(question)}>{question}</button>
|
||||
<button key={question} type="button" onClick={() => void send({ action: "message", message: question })}>{question}</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user