fix: stabilize agent UI and SCADA card
This commit is contained in:
@@ -43,7 +43,6 @@ import { cn } from "@/lib/utils";
|
||||
import type { AgentChatSessionSummary } from "../api/client";
|
||||
import { AgentPersona } from "./agent-persona";
|
||||
import {
|
||||
agentLayoutTransition,
|
||||
agentPanelItemVariants,
|
||||
agentPanelListVariants,
|
||||
agentPanelSectionVariants
|
||||
@@ -157,6 +156,7 @@ export function AgentCommandPanel({
|
||||
return (
|
||||
<MotionConfig reducedMotion="user">
|
||||
<aside
|
||||
aria-label="Agent 命令面板"
|
||||
className={cn(
|
||||
"pointer-events-auto flex h-full min-h-0 w-full flex-col overflow-hidden",
|
||||
"agent-panel-shell",
|
||||
@@ -166,7 +166,7 @@ export function AgentCommandPanel({
|
||||
)}
|
||||
>
|
||||
<Agent className="flex h-full min-h-0 flex-col border-0 bg-transparent">
|
||||
<div className="agent-panel-band border-b border-slate-200">
|
||||
<header className="agent-panel-header acrylic-panel">
|
||||
<div className="flex min-h-16 items-center justify-between gap-2 px-3 py-2">
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2.5 text-slate-900">
|
||||
<AgentPersona
|
||||
@@ -274,7 +274,7 @@ export function AgentCommandPanel({
|
||||
</motion.div>
|
||||
) : null}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<AgentContent className="flex min-h-0 flex-1 flex-col gap-0 p-0">
|
||||
<Conversation className="agent-panel-conversation min-h-0">
|
||||
@@ -310,8 +310,6 @@ export function AgentCommandPanel({
|
||||
activeSessionId={activeSessionId}
|
||||
messages={messages}
|
||||
scrollRequestId={scrollRequestId}
|
||||
streaming={streaming}
|
||||
uiResults={uiResults}
|
||||
/>
|
||||
<ConversationScrollButton className="bottom-3" />
|
||||
</Conversation>
|
||||
@@ -382,33 +380,27 @@ type AgentConversationScrollSnapshot = {
|
||||
activeSessionId?: string | null;
|
||||
lastUserMessageId: string | null;
|
||||
scrollRequestId: number;
|
||||
signature: string;
|
||||
};
|
||||
|
||||
function AgentConversationScrollManager({
|
||||
activeSessionId,
|
||||
messages,
|
||||
scrollRequestId,
|
||||
streaming,
|
||||
uiResults
|
||||
scrollRequestId
|
||||
}: {
|
||||
activeSessionId?: string | null;
|
||||
messages: AgentChatMessage[];
|
||||
scrollRequestId: number;
|
||||
streaming: boolean;
|
||||
uiResults: AgentUiResult[];
|
||||
}) {
|
||||
const { escapedFromLock, isAtBottom, scrollToBottom, state } = useStickToBottomContext();
|
||||
const { scrollToBottom } = useStickToBottomContext();
|
||||
const previousSnapshotRef = useRef<AgentConversationScrollSnapshot | null>(null);
|
||||
const wasPinnedToBottom = !escapedFromLock && (isAtBottom || state.isAtBottom || state.isNearBottom);
|
||||
const lastUserMessageId = getLastUserMessageId(messages);
|
||||
const snapshot = useMemo<AgentConversationScrollSnapshot>(
|
||||
() => ({
|
||||
activeSessionId,
|
||||
lastUserMessageId: getLastUserMessageId(messages),
|
||||
scrollRequestId,
|
||||
signature: createAgentConversationScrollSignature(messages, streaming, uiResults)
|
||||
lastUserMessageId,
|
||||
scrollRequestId
|
||||
}),
|
||||
[activeSessionId, messages, scrollRequestId, streaming, uiResults]
|
||||
[activeSessionId, lastUserMessageId, scrollRequestId]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -420,22 +412,21 @@ function AgentConversationScrollManager({
|
||||
previousSnapshot.activeSessionId !== snapshot.activeSessionId ||
|
||||
previousSnapshot.scrollRequestId !== snapshot.scrollRequestId ||
|
||||
(Boolean(snapshot.lastUserMessageId) && previousSnapshot.lastUserMessageId !== snapshot.lastUserMessageId);
|
||||
const contentChanged = !previousSnapshot || previousSnapshot.signature !== snapshot.signature;
|
||||
|
||||
if (!forceScroll && (!contentChanged || !wasPinnedToBottom)) {
|
||||
if (!forceScroll) {
|
||||
return;
|
||||
}
|
||||
|
||||
const animationFrameId = window.requestAnimationFrame(() => {
|
||||
void scrollToBottom({
|
||||
animation: "instant",
|
||||
ignoreEscapes: forceScroll,
|
||||
wait: !forceScroll
|
||||
ignoreEscapes: true,
|
||||
wait: false
|
||||
});
|
||||
});
|
||||
|
||||
return () => window.cancelAnimationFrame(animationFrameId);
|
||||
}, [scrollToBottom, snapshot, wasPinnedToBottom]);
|
||||
}, [scrollToBottom, snapshot]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -450,30 +441,6 @@ function getLastUserMessageId(messages: AgentChatMessage[]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function createAgentConversationScrollSignature(
|
||||
messages: AgentChatMessage[],
|
||||
streaming: boolean,
|
||||
uiResults: AgentUiResult[]
|
||||
) {
|
||||
return [
|
||||
streaming ? "streaming" : "idle",
|
||||
messages.map(createAgentMessageScrollSignature).join("|"),
|
||||
uiResults.map((result) => result.id).join("|")
|
||||
].join("#");
|
||||
}
|
||||
|
||||
function createAgentMessageScrollSignature(message: AgentChatMessage) {
|
||||
return [
|
||||
message.id,
|
||||
message.role,
|
||||
message.content.length,
|
||||
message.progress?.map((item) => `${item.id}:${item.status}:${item.title.length}:${item.detail?.length ?? 0}`).join(",") ?? "",
|
||||
message.todos?.todos.map((todo) => `${todo.id}:${todo.status}:${todo.content.length}`).join(",") ?? "",
|
||||
message.permissions?.map((permission) => `${permission.requestId}:${permission.status}:${permission.error?.length ?? 0}`).join(",") ?? "",
|
||||
message.questions?.map((question) => `${question.requestId}:${question.status}:${question.error?.length ?? 0}`).join(",") ?? ""
|
||||
].join(":");
|
||||
}
|
||||
|
||||
function AgentModelSelect({
|
||||
models,
|
||||
value,
|
||||
@@ -675,13 +642,11 @@ function BackendMessageList({
|
||||
{messages.map((message, index) => (
|
||||
<motion.div
|
||||
key={message.id}
|
||||
layout
|
||||
className={cn("w-full", message.role === "user" && "flex justify-end")}
|
||||
initial="initial"
|
||||
animate="animate"
|
||||
exit="exit"
|
||||
variants={agentPanelItemVariants}
|
||||
transition={agentLayoutTransition}
|
||||
>
|
||||
<Message
|
||||
from={message.role}
|
||||
|
||||
Reference in New Issue
Block a user