From 059bdf87db284b8225b0aa5f4c49326a3b7f9e62 Mon Sep 17 00:00:00 2001 From: wsp Date: Mon, 3 Aug 2026 10:29:53 +0800 Subject: [PATCH] feat(flow-chat): keep background commands accessible when idle - Allow the background command panel to open without active commands. - Render a localized empty state and hide unavailable bulk actions. - Add coverage for the idle background command panel. --- .../components/modern/FlowChatHeader.scss | 11 ++++++++ .../components/modern/FlowChatHeader.test.tsx | 18 +++++++++++++ .../components/modern/FlowChatHeader.tsx | 25 ++++++++++--------- src/web-ui/src/locales/en-US/flow-chat.json | 1 + src/web-ui/src/locales/zh-CN/flow-chat.json | 1 + src/web-ui/src/locales/zh-TW/flow-chat.json | 1 + 6 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/web-ui/src/flow_chat/components/modern/FlowChatHeader.scss b/src/web-ui/src/flow_chat/components/modern/FlowChatHeader.scss index 8874ab97e..577a42c1f 100644 --- a/src/web-ui/src/flow_chat/components/modern/FlowChatHeader.scss +++ b/src/web-ui/src/flow_chat/components/modern/FlowChatHeader.scss @@ -269,6 +269,17 @@ scrollbar-gutter: stable; } + &__background-command-empty-state { + min-height: 48px; + display: flex; + align-items: center; + justify-content: center; + padding: $size-gap-3; + color: var(--bf-appearance-token-color-text-secondary); + font-size: var(--bf-appearance-token-flowchat-font-size-xs); + text-align: center; + } + &__background-command-list-item-button { display: flex; flex-direction: column; diff --git a/src/web-ui/src/flow_chat/components/modern/FlowChatHeader.test.tsx b/src/web-ui/src/flow_chat/components/modern/FlowChatHeader.test.tsx index e808baf69..af8f9ceac 100644 --- a/src/web-ui/src/flow_chat/components/modern/FlowChatHeader.test.tsx +++ b/src/web-ui/src/flow_chat/components/modern/FlowChatHeader.test.tsx @@ -157,6 +157,24 @@ describe('FlowChatHeader', () => { expect(treeContainer?.nextElementSibling).toBe(commandContainer); }); + it('opens the background command panel when no commands exist', () => { + act(() => { + root.render(); + }); + + const commandButton = container.querySelector( + '[data-testid="flowchat-header-background-commands"]', + ); + expect(commandButton?.disabled).toBe(false); + + act(() => { + commandButton?.click(); + }); + + const panel = container.querySelector('.flowchat-header__background-command-panel'); + expect(panel?.textContent).toContain('flowChatHeader.backgroundCommandEmpty'); + }); + it('renders background command menus in a portal outside the scrollable panel', () => { const onStopBackgroundCommand = vi.fn(); const onStopAllBackgroundCommands = vi.fn(); diff --git a/src/web-ui/src/flow_chat/components/modern/FlowChatHeader.tsx b/src/web-ui/src/flow_chat/components/modern/FlowChatHeader.tsx index 13aad61d5..887d54b1a 100644 --- a/src/web-ui/src/flow_chat/components/modern/FlowChatHeader.tsx +++ b/src/web-ui/src/flow_chat/components/modern/FlowChatHeader.tsx @@ -220,12 +220,11 @@ export const FlowChatHeader: React.FC = ({ }, [searchOpenRequest]); useEffect(() => { - if (!hasBackgroundCommands) { - setIsBackgroundCommandPanelOpen(false); - setIsBackgroundCommandSectionMenuOpen(false); - setOpenBackgroundCommandMenuId(null); - setBackgroundCommandMenuPosition(null); - } + if (hasBackgroundCommands) return; + + setIsBackgroundCommandSectionMenuOpen(false); + setOpenBackgroundCommandMenuId(null); + setBackgroundCommandMenuPosition(null); }, [hasBackgroundCommands]); useEffect(() => { @@ -299,7 +298,6 @@ export const FlowChatHeader: React.FC = ({ ); const handleToggleBackgroundCommandPanel = () => { - if (!hasBackgroundCommands) return; setIsBackgroundCommandSectionMenuOpen(false); setOpenBackgroundCommandMenuId(null); setIsBackgroundCommandPanelOpen(prev => !prev); @@ -535,7 +533,6 @@ export const FlowChatHeader: React.FC = ({ size="xs" onClick={handleToggleBackgroundCommandPanel} tooltip={backgroundCommandLabel} - disabled={!hasBackgroundCommands} aria-label={backgroundCommandLabel} aria-expanded={isBackgroundCommandPanelOpen} aria-haspopup="dialog" @@ -552,7 +549,7 @@ export const FlowChatHeader: React.FC = ({ - {isBackgroundCommandPanelOpen && hasBackgroundCommands && ( + {isBackgroundCommandPanelOpen && (
= ({
{backgroundCommandLabel}
- {onStopAllBackgroundCommands ? ( + {hasBackgroundCommands && onStopAllBackgroundCommands ? ( = ({ data-bf-component="flow-chat-header" data-bf-part="activitySection" > - {displayBackgroundCommands.map((command) => ( + {hasBackgroundCommands ? displayBackgroundCommands.map((command) => (
= ({ {renderBackgroundCommandActions(command)}
- ))} + )) : ( +
+ {t('flowChatHeader.backgroundCommandEmpty')} +
+ )}
)} diff --git a/src/web-ui/src/locales/en-US/flow-chat.json b/src/web-ui/src/locales/en-US/flow-chat.json index 2521dcdca..5689c6700 100644 --- a/src/web-ui/src/locales/en-US/flow-chat.json +++ b/src/web-ui/src/locales/en-US/flow-chat.json @@ -1257,6 +1257,7 @@ "idle": "Idle" }, "backgroundCommands": "Background commands ({{count}})", + "backgroundCommandEmpty": "No background commands", "backgroundCommandUntitled": "Background command", "backgroundCommandSession": "Session #{{id}}", "backgroundCommandStatusRunning": "Active", diff --git a/src/web-ui/src/locales/zh-CN/flow-chat.json b/src/web-ui/src/locales/zh-CN/flow-chat.json index 568bcd63f..fda9e5a86 100644 --- a/src/web-ui/src/locales/zh-CN/flow-chat.json +++ b/src/web-ui/src/locales/zh-CN/flow-chat.json @@ -1257,6 +1257,7 @@ "idle": "空闲" }, "backgroundCommands": "后台命令({{count}})", + "backgroundCommandEmpty": "暂无后台命令", "backgroundCommandUntitled": "后台命令", "backgroundCommandSession": "会话 #{{id}}", "backgroundCommandStatusRunning": "进行中", diff --git a/src/web-ui/src/locales/zh-TW/flow-chat.json b/src/web-ui/src/locales/zh-TW/flow-chat.json index e17b26716..570c704f0 100644 --- a/src/web-ui/src/locales/zh-TW/flow-chat.json +++ b/src/web-ui/src/locales/zh-TW/flow-chat.json @@ -1257,6 +1257,7 @@ "idle": "閒置" }, "backgroundCommands": "背景命令({{count}})", + "backgroundCommandEmpty": "目前沒有背景命令", "backgroundCommandUntitled": "背景命令", "backgroundCommandSession": "會話 #{{id}}", "backgroundCommandStatusRunning": "進行中",