fix(rectification): expose agentic route and runtime trace
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { loadActiveRectificationV4 } from "../lib/rectification-v4/client.ts";
|
||||
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";
|
||||
@@ -24,19 +25,24 @@ export type ConversationalBirthTimeRectificationProps = Readonly<{
|
||||
/**
|
||||
* Birth-time rectification surface.
|
||||
*
|
||||
* Resumes an existing v4 evidence case when one is still in progress (so users
|
||||
* never lose a saved candidate range), and otherwise opens the agentic chat
|
||||
* where the LLM drives the full Jyotish rectification methodology with the
|
||||
* engine as its computation layer.
|
||||
* 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" | "v4" | "agentic">("loading");
|
||||
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) setMode(existing ? "v4" : "agentic");
|
||||
if (mounted) {
|
||||
setExisting(existing);
|
||||
setMode(existing ? "choice" : "agentic");
|
||||
}
|
||||
})();
|
||||
return () => { mounted = false; };
|
||||
}, []);
|
||||
@@ -52,8 +58,48 @@ export function ConversationalBirthTimeRectification(props: ConversationalBirthT
|
||||
);
|
||||
}
|
||||
|
||||
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} />;
|
||||
return <RectificationV4Panel {...props} onUseAgentic={() => setMode("agentic")} />;
|
||||
}
|
||||
|
||||
return <AgenticRectificationChat {...props} />;
|
||||
|
||||
@@ -28,6 +28,7 @@ type RectificationV4PanelProps = Readonly<{
|
||||
continuationPending?: boolean;
|
||||
onPendingChange?: (pending: boolean) => void;
|
||||
onContinueOriginalQuestion?: (continuation: RectificationV4Continuation) => void;
|
||||
onUseAgentic?: () => void;
|
||||
}>;
|
||||
|
||||
type RectificationChatMessageView = ChatMessageView & Readonly<{
|
||||
@@ -358,8 +359,9 @@ export function RectificationV4Panel(props: RectificationV4PanelProps) {
|
||||
&& handoff?.status === "pending"
|
||||
&& props.onContinueOriginalQuestion,
|
||||
);
|
||||
const canUseAgentic = Boolean(caseValue && caseValue.status !== "abandoned" && props.onUseAgentic);
|
||||
const showControls = Boolean(caseValue && caseValue.status !== "abandoned" && (
|
||||
canAnswer || canAcceptRange || canContinue || caseValue.status === "paused"
|
||||
canAnswer || canAcceptRange || canContinue || caseValue.status === "paused" || canUseAgentic
|
||||
));
|
||||
|
||||
useEffect(() => {
|
||||
@@ -410,6 +412,11 @@ export function RectificationV4Panel(props: RectificationV4PanelProps) {
|
||||
});
|
||||
}
|
||||
|
||||
async function startAgentic() {
|
||||
const result = await controller.abandon();
|
||||
if (result) props.onUseAgentic?.();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="conversation" aria-label="生时校正对话" aria-busy={processing || controller.pending}>
|
||||
@@ -482,6 +489,7 @@ export function RectificationV4Panel(props: RectificationV4PanelProps) {
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{controller.data?.runtimeTrace && <RectificationRuntimeDetails trace={controller.data.runtimeTrace} />}
|
||||
{controller.error && <p className="error-message" role="alert">{controller.error}</p>}
|
||||
<div ref={conversationEnd} />
|
||||
</div>
|
||||
@@ -489,7 +497,7 @@ export function RectificationV4Panel(props: RectificationV4PanelProps) {
|
||||
|
||||
{showControls && (
|
||||
<div className="composer-wrap">
|
||||
{(canAcceptRange || canContinue || caseValue?.status === "paused") && (
|
||||
{(canAcceptRange || canContinue || caseValue?.status === "paused" || canUseAgentic) && (
|
||||
<div className="composer-suggestions" aria-label="生时校正操作">
|
||||
{canAcceptRange && (
|
||||
<button type="button" disabled={controller.pending} onClick={() => void controller.acceptRange()}>
|
||||
@@ -506,6 +514,11 @@ export function RectificationV4Panel(props: RectificationV4PanelProps) {
|
||||
继续校正
|
||||
</button>
|
||||
)}
|
||||
{canUseAgentic && (
|
||||
<button type="button" disabled={controller.pending} onClick={() => void startAgentic()}>
|
||||
结束旧版并使用新版 Agent
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -544,3 +557,28 @@ export function RectificationV4Panel(props: RectificationV4PanelProps) {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function RectificationRuntimeDetails({ trace }: Readonly<{ trace: RectificationV4ApiResponse["runtimeTrace"] }>) {
|
||||
const rows = [
|
||||
["Runtime", trace.deploymentMode, "当前校正路由"],
|
||||
["Execution", trace.executionMode, trace.fallbackCode ?? "无 fallback code"],
|
||||
["Model", trace.modelId ?? "default / unavailable", "本轮实际选择"],
|
||||
["Skill", trace.skillVersion, "已注册版本"],
|
||||
["Deployment SHA", trace.deploymentSha ?? "unknown", "当前部署"],
|
||||
] as const;
|
||||
return (
|
||||
<details className="evidence-audit-panel" open={trace.executionMode === "deterministic_fallback"}>
|
||||
<summary>运行信息 · {trace.executionMode}</summary>
|
||||
<div className="evidence-audit-table" role="table" aria-label="生时校正运行信息">
|
||||
{rows.map(([label, value, note]) => (
|
||||
<div className="evidence-audit-row" role="row" key={label}>
|
||||
<span role="cell">{label}</span>
|
||||
<b role="cell">{value}</b>
|
||||
<small role="cell">{note}</small>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user