Skip to content

feat(agentflow): add chat panel for real-time flow testing#6316

Draft
j-sanaa wants to merge 2 commits intofeat/agentflow-executionfrom
feat-agentflow-chatpanel
Draft

feat(agentflow): add chat panel for real-time flow testing#6316
j-sanaa wants to merge 2 commits intofeat/agentflow-executionfrom
feat-agentflow-chatpanel

Conversation

@j-sanaa
Copy link
Copy Markdown
Contributor

@j-sanaa j-sanaa commented Apr 29, 2026

Add TestFlowDialog — an SSE-driven chat panel for testing agentflows. Messages stream in real time using @microsoft/fetch-event-source, with node execution status badges updating live as the flow runs.

Key additions:

  • TestFlowDialog: streaming chat, HITL action buttons, history load
  • TestFlowButton container: FAB with expand/clear controls
  • StyledFab moved from node-palette to atoms (shared use)
  • Agentflow.tsx: flowId prop + isChatOpen to hide toolbar while testing
  • chatflows.ts: abortMessage for stopping in-progress streams
  • jest mocks for react-markdown, remark-gfm, uuid, and image assets
  • vite proxy config fixed for cross-origin dev

Add TestFlowDialog — an SSE-driven chat panel for testing agentflows.
Messages stream in real time using @microsoft/fetch-event-source, with
node execution status badges updating live as the flow runs.

Key additions:
- TestFlowDialog: streaming chat, HITL action buttons, history load
- TestFlowButton container: FAB with expand/clear controls
- StyledFab moved from node-palette to atoms (shared use)
- Agentflow.tsx: flowId prop + isChatOpen to hide toolbar while testing
- chatflows.ts: abortMessage for stopping in-progress streams
- jest mocks for react-markdown, remark-gfm, uuid, and image assets
- vite proxy config fixed for cross-origin dev

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a chat-based testing interface for Agentflow, enabling real-time interaction with flows through a new floating action button. Key additions include the TestFlowButton and TestFlowDialog components, which leverage Server-Sent Events (SSE) for streaming responses and include support for chat history retrieval and message abortion. The build and test configurations have been updated with necessary proxies, dependencies (like react-markdown), and Jest mocks. Review feedback highlights the need to reset history state when the chatflowId changes to prevent stale data, the addition of a missing onClick handler for the 'Expand Chat' button, and the inclusion of handleSendCore in several useCallback dependency arrays to avoid stale closures.

Comment on lines +97 to +146
useEffect(() => {
if (!open || historyLoaded) return

async function loadHistory() {
try {
const page = await executionsApi.getAllExecutions({ agentflowId: chatflowId, limit: HISTORY_LIMIT })
const executions = page.data
if (!executions?.length) return

// Restore the most recent session's chatId so the user continues it
const latestSessionId = executions[0]?.sessionId
if (latestSessionId) chatIdRef.current = latestSessionId

const historyMessages: ChatMessage[] = []
// executions arrive newest-first; reverse to show oldest first
for (const execution of [...executions].reverse()) {
let executionData: ExecutionDataItem[] = []
try {
executionData =
typeof execution.executionData === 'string' ? (JSON.parse(execution.executionData) as ExecutionDataItem[]) : []
} catch {
continue
}

const startNode = executionData.find((e) => e.data?.name === 'startAgentflow')
const question = (startNode?.data?.input?.question ?? startNode?.data?.output?.question) as string | undefined

const lastWithContent = [...executionData].reverse().find((e) => e.data?.output?.content || e.data?.output?.question)
const answer = (lastWithContent?.data?.output?.content ?? lastWithContent?.data?.output?.question) as string | undefined

if (question) {
historyMessages.push({ id: `${execution.id}-user`, type: 'user', text: question })
if (answer && answer !== question) {
historyMessages.push({ id: `${execution.id}-bot`, type: 'bot', text: answer })
}
}
}

if (historyMessages.length > 0) {
setMessages((prev) => [...prev, ...historyMessages])
}
} catch {
// history load is best-effort
} finally {
setHistoryLoaded(true)
}
}

void loadHistory()
}, [open, chatflowId, executionsApi, historyLoaded])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The chat history loading logic does not account for changes to chatflowId while the component remains mounted. If the user switches to a different flow, historyLoaded will still be true, preventing the new flow's history from loading. Reset the state in the main useEffect body when chatflowId changes to avoid stale data. Additionally, ensure that any API failures during history retrieval are handled with user-facing feedback rather than just console logging.

    useEffect(() => {
        setHistoryLoaded(false)
        setMessages([{ id: uuidv4(), type: 'bot', text: WELCOME_MESSAGE }])
        chatIdRef.current = uuidv4()
    }, [chatflowId])

    useEffect(() => {
        if (!open || historyLoaded) return
References
  1. Avoid calling setState in a useEffect cleanup function for components that do not unmount. Reset state in the main useEffect body instead.
  2. Implement user-facing error handling for all API calls to provide feedback to the user when requests fail, rather than just logging errors to the console.

Comment on lines +53 to +55
<StyledFab size='small' color='primary' aria-label='expand' title='Expand Chat'>
<IconArrowsMaximize />
</StyledFab>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The 'Expand Chat' button is currently non-functional as it lacks an onClick handler. If this feature is intended for a future update, consider hiding the button or adding a placeholder action to avoid a broken user experience.

Comment on lines +160 to +172
const handleActionClick = useCallback(
(elem: ActionElement, action: ActionPayload) => {
// Clear the action buttons from the last bot message
setMessages((prev) => {
const last = prev[prev.length - 1]
if (!last || last.type !== 'bot') return prev
return [...prev.slice(0, -1), { ...last, action: undefined }]
})
void handleSendCore(elem.label, action)
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The handleActionClick callback is missing handleSendCore in its dependency array. Since handleSendCore is created via useCallback and depends on external state (like token), omitting it can lead to stale closures if those dependencies change. Please include handleSendCore in the dependency array and remove the lint suppression.

    const handleActionClick = useCallback(
        (elem: ActionElement, action: ActionPayload) => {
            // Clear the action buttons from the last bot message
            setMessages((prev) => {
                const last = prev[prev.length - 1]
                if (!last || last.type !== 'bot') return prev
                return [...prev.slice(0, -1), { ...last, action: undefined }]
            })
            void handleSendCore(elem.label, action)
        },
        [handleSendCore]
    )

Comment on lines +174 to +180
const handleSend = useCallback(() => {
const input = userInput.trim()
if (!input || loading) return
setFollowUpPrompts([])
void handleSendCore(input)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [userInput, loading])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The handleSend callback is missing handleSendCore in its dependency array. This can cause the function to use a stale version of the core sending logic if the component re-renders with updated context values. Please add handleSendCore to the dependencies.

    const handleSend = useCallback(() => {
        const input = userInput.trim()
        if (!input || loading) return
        setFollowUpPrompts([])
        void handleSendCore(input)
    }, [userInput, loading, handleSendCore])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant