feat(canvas): show loading state while agent history loads
Track agent history loading in the workspace and render a spinner with a localized "loading conversation history" message in the assistant feed, so the empty state no longer flashes before history resolves. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -341,6 +341,7 @@
|
|||||||
"conversation": "Chat",
|
"conversation": "Chat",
|
||||||
"newChat": "New chat",
|
"newChat": "New chat",
|
||||||
"conversationHistory": "History",
|
"conversationHistory": "History",
|
||||||
|
"loadingConversationHistory": "Loading conversation history...",
|
||||||
"scrollToLatest": "Scroll to latest",
|
"scrollToLatest": "Scroll to latest",
|
||||||
"searchConversationPlaceholder": "Search conversations",
|
"searchConversationPlaceholder": "Search conversations",
|
||||||
"deleteConversation": "Delete conversation",
|
"deleteConversation": "Delete conversation",
|
||||||
|
|||||||
@@ -341,6 +341,7 @@
|
|||||||
"conversation": "对话",
|
"conversation": "对话",
|
||||||
"newChat": "新对话",
|
"newChat": "新对话",
|
||||||
"conversationHistory": "历史对话",
|
"conversationHistory": "历史对话",
|
||||||
|
"loadingConversationHistory": "正在加载历史对话...",
|
||||||
"scrollToLatest": "回到底部",
|
"scrollToLatest": "回到底部",
|
||||||
"searchConversationPlaceholder": "请输入搜索关键词",
|
"searchConversationPlaceholder": "请输入搜索关键词",
|
||||||
"deleteConversation": "删除对话",
|
"deleteConversation": "删除对话",
|
||||||
|
|||||||
@@ -575,6 +575,7 @@ export function CanvasWorkspace({ projectId, shareAccess = ownerWorkspaceAccess
|
|||||||
const { project, setProject, projectRef, viewport, setViewport, viewportRef, nodes, setNodes, nodesRef, applyProjectDocument } = useCanvasDocument();
|
const { project, setProject, projectRef, viewport, setViewport, viewportRef, nodes, setNodes, nodesRef, applyProjectDocument } = useCanvasDocument();
|
||||||
const { selectedId, selectedIds, setSelectedIds, selectedNodes, selectedNode, selectOnlyNode, selectNodes, toggleNodeSelection } = useCanvasSelection(nodes);
|
const { selectedId, selectedIds, setSelectedIds, selectedNodes, selectedNode, selectOnlyNode, selectNodes, toggleNodeSelection } = useCanvasSelection(nodes);
|
||||||
const [messages, setMessages] = useState<AgentMessage[]>([]);
|
const [messages, setMessages] = useState<AgentMessage[]>([]);
|
||||||
|
const [agentHistoryLoading, setAgentHistoryLoading] = useState(canViewChat);
|
||||||
const [prompt, setPrompt] = useState("");
|
const [prompt, setPrompt] = useState("");
|
||||||
const [agentStarterConfig, setAgentStarterConfig] = useState<AgentStarterConfig>(() => defaultAgentStarterConfig(t));
|
const [agentStarterConfig, setAgentStarterConfig] = useState<AgentStarterConfig>(() => defaultAgentStarterConfig(t));
|
||||||
const [agentThreads, setAgentThreads] = useState<AgentThreadSummary[]>([]);
|
const [agentThreads, setAgentThreads] = useState<AgentThreadSummary[]>([]);
|
||||||
@@ -901,17 +902,20 @@ export function CanvasWorkspace({ projectId, shareAccess = ownerWorkspaceAccess
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
selectedAgentThreadIdRef.current = null;
|
selectedAgentThreadIdRef.current = null;
|
||||||
setSelectedAgentThreadId(null);
|
setSelectedAgentThreadId(null);
|
||||||
|
setAgentHistoryLoading(canViewChat);
|
||||||
setAgentThreads([]);
|
setAgentThreads([]);
|
||||||
setAgentHistorySearch("");
|
setAgentHistorySearch("");
|
||||||
setAgentHistoryOpen(false);
|
setAgentHistoryOpen(false);
|
||||||
autoScrollAgentFeedRef.current = false;
|
autoScrollAgentFeedRef.current = false;
|
||||||
}, [projectId]);
|
}, [canViewChat, projectId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (agentHistoryOpen) void loadAgentThreads();
|
if (agentHistoryOpen) void loadAgentThreads();
|
||||||
}, [agentHistoryOpen, loadAgentThreads]);
|
}, [agentHistoryOpen, loadAgentThreads]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let active = true;
|
||||||
|
setAgentHistoryLoading(canViewChat);
|
||||||
designGateway
|
designGateway
|
||||||
.getProject(projectId)
|
.getProject(projectId)
|
||||||
.then(({ project: nextProject }) => {
|
.then(({ project: nextProject }) => {
|
||||||
@@ -961,7 +965,13 @@ export function CanvasWorkspace({ projectId, shareAccess = ownerWorkspaceAccess
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setError(toUserMessageText(err, "generateError"));
|
setError(toUserMessageText(err, "generateError"));
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (active) setAgentHistoryLoading(false);
|
||||||
});
|
});
|
||||||
|
return () => {
|
||||||
|
active = false;
|
||||||
|
};
|
||||||
}, [applyProject, canViewChat, projectId, startProjectEvents]);
|
}, [applyProject, canViewChat, projectId, startProjectEvents]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -2649,6 +2659,7 @@ export function CanvasWorkspace({ projectId, shareAccess = ownerWorkspaceAccess
|
|||||||
autoScrollAgentFeedRef.current = false;
|
autoScrollAgentFeedRef.current = false;
|
||||||
selectedAgentThreadIdRef.current = newAgentConversationId;
|
selectedAgentThreadIdRef.current = newAgentConversationId;
|
||||||
setSelectedAgentThreadId(newAgentConversationId);
|
setSelectedAgentThreadId(newAgentConversationId);
|
||||||
|
setAgentHistoryLoading(false);
|
||||||
setAgentHistoryOpen(false);
|
setAgentHistoryOpen(false);
|
||||||
setPrompt("");
|
setPrompt("");
|
||||||
setMessages([]);
|
setMessages([]);
|
||||||
@@ -4124,7 +4135,12 @@ export function CanvasWorkspace({ projectId, shareAccess = ownerWorkspaceAccess
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="assistant-feed" ref={assistantFeedRef} onScroll={updateAssistantScrollButton}>
|
<div className="assistant-feed" ref={assistantFeedRef} onScroll={updateAssistantScrollButton}>
|
||||||
{!hasCurrentConversation && !agentTimelineActive ? (
|
{agentHistoryLoading ? (
|
||||||
|
<div className="assistant-history-loading" role="status" aria-live="polite" data-testid="agent-history-loading">
|
||||||
|
<Loader2 className="spin" size={20} aria-hidden="true" />
|
||||||
|
<span>{t("loadingConversationHistory")}</span>
|
||||||
|
</div>
|
||||||
|
) : !hasCurrentConversation && !agentTimelineActive ? (
|
||||||
<CanvasAgentEmptyState config={agentStarterConfig} disabled={!canEdit} onSelectPrompt={selectAgentStarterPrompt} />
|
<CanvasAgentEmptyState config={agentStarterConfig} disabled={!canEdit} onSelectPrompt={selectAgentStarterPrompt} />
|
||||||
) : (
|
) : (
|
||||||
<CanvasAgentTimeline messages={messages} nodes={nodes} active={agentTimelineActive} />
|
<CanvasAgentTimeline messages={messages} nodes={nodes} active={agentTimelineActive} />
|
||||||
|
|||||||
@@ -3789,6 +3789,24 @@ button:disabled {
|
|||||||
background: #dfe0e2;
|
background: #dfe0e2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.assistant-history-loading {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
flex: 1 0 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
color: #8a9099;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assistant-history-loading svg {
|
||||||
|
color: #646a73;
|
||||||
|
}
|
||||||
|
|
||||||
.assistant-scroll-to-end {
|
.assistant-scroll-to-end {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset-inline: 0;
|
inset-inline: 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user