feat(frontend): Update chat service and UI to support Orchestrator architecture and native subgraph streaming
This commit is contained in:
@@ -9,9 +9,9 @@ interface ExecutionStatusProps {
|
||||
|
||||
const PHASE_CONFIG = [
|
||||
{ label: "Analyzing query...", match: "Query analysis complete." },
|
||||
{ label: "Generating strategic plan...", match: "Strategic plan generated." },
|
||||
{ label: "Writing analysis code...", match: "Analysis code generated." },
|
||||
{ label: "Performing data analysis...", match: "Data analysis and visualization complete." }
|
||||
{ label: "Generating high-level plan...", match: "Checklist generated." },
|
||||
{ label: "Delegating to specialists...", match: "Task assigned." },
|
||||
{ label: "Synthesizing final answer...", match: "Final synthesis complete." }
|
||||
]
|
||||
|
||||
export function ExecutionStatus({ steps, isComplete, className }: ExecutionStatusProps) {
|
||||
|
||||
@@ -3,21 +3,21 @@ import { ChatService, type ChatEvent, type MessageResponse } from "./chat"
|
||||
|
||||
describe("ChatService SSE Parsing", () => {
|
||||
it("should correctly parse a text stream chunk", () => {
|
||||
const rawChunk = `data: {"type": "on_chat_model_stream", "name": "summarizer", "data": {"chunk": "Hello"}}\n\n`
|
||||
const rawChunk = `data: {"type": "on_chat_model_stream", "name": "synthesizer", "data": {"chunk": "Hello"}}\n\n`
|
||||
const events = ChatService.parseSSEChunk(rawChunk)
|
||||
|
||||
expect(events).toHaveLength(1)
|
||||
expect(events[0]).toEqual({
|
||||
type: "on_chat_model_stream",
|
||||
name: "summarizer",
|
||||
name: "synthesizer",
|
||||
data: { chunk: "Hello" }
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle multiple events in one chunk", () => {
|
||||
const rawChunk =
|
||||
`data: {"type": "on_chat_model_stream", "name": "summarizer", "data": {"chunk": "Hello"}}\n\n` +
|
||||
`data: {"type": "on_chat_model_stream", "name": "summarizer", "data": {"chunk": " World"}}\n\n`
|
||||
`data: {"type": "on_chat_model_stream", "name": "synthesizer", "data": {"chunk": "Hello"}}\n\n` +
|
||||
`data: {"type": "on_chat_model_stream", "name": "synthesizer", "data": {"chunk": " World"}}\n\n`
|
||||
|
||||
const events = ChatService.parseSSEChunk(rawChunk)
|
||||
|
||||
@@ -25,8 +25,8 @@ describe("ChatService SSE Parsing", () => {
|
||||
expect(events[1].data!.chunk).toBe(" World")
|
||||
})
|
||||
|
||||
it("should parse encoded plots from executor node", () => {
|
||||
const rawChunk = `data: {"type": "on_chain_end", "name": "executor", "data": {"encoded_plots": ["base64data"]}}\n\n`
|
||||
it("should parse encoded plots from data_analyst_worker node", () => {
|
||||
const rawChunk = `data: {"type": "on_chain_end", "name": "data_analyst_worker", "data": {"encoded_plots": ["base64data"]}}\n\n`
|
||||
const events = ChatService.parseSSEChunk(rawChunk)
|
||||
|
||||
expect(events[0].data!.encoded_plots).toEqual(["base64data"])
|
||||
@@ -45,7 +45,7 @@ describe("ChatService Message State Management", () => {
|
||||
const messages: MessageResponse[] = [{ id: "1", role: "assistant", content: "Initial", created_at: new Date().toISOString() }]
|
||||
const event: ChatEvent = {
|
||||
type: "on_chat_model_stream",
|
||||
node: "summarizer",
|
||||
node: "synthesizer",
|
||||
data: { chunk: { content: " text" } }
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ describe("ChatService Message State Management", () => {
|
||||
const messages: MessageResponse[] = [{ id: "1", role: "assistant", content: "Analysis", created_at: new Date().toISOString(), plots: [] }]
|
||||
const event: ChatEvent = {
|
||||
type: "on_chain_end",
|
||||
name: "executor",
|
||||
name: "data_analyst_worker",
|
||||
data: { encoded_plots: ["plot1"] }
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,8 @@ export const ChatService = {
|
||||
const { type, name, node, data } = event
|
||||
|
||||
// 1. Handle incremental LLM chunks for terminal nodes
|
||||
if (type === "on_chat_model_stream" && (node === "summarizer" || node === "researcher" || node === "clarification")) {
|
||||
// Now using 'synthesizer' for the final user response
|
||||
if (type === "on_chat_model_stream" && (node === "synthesizer" || node === "clarification")) {
|
||||
const chunk = data?.chunk?.content || ""
|
||||
if (!chunk) return messages
|
||||
|
||||
@@ -110,7 +111,7 @@ export const ChatService = {
|
||||
if (!lastMsg || lastMsg.role !== "assistant") return messages
|
||||
|
||||
// Terminal nodes final text
|
||||
if (name === "summarizer" || name === "researcher" || name === "clarification") {
|
||||
if (name === "synthesizer" || name === "clarification") {
|
||||
const messages_list = data?.output?.messages
|
||||
const msg = messages_list ? messages_list[messages_list.length - 1]?.content : null
|
||||
|
||||
@@ -121,8 +122,8 @@ export const ChatService = {
|
||||
}
|
||||
}
|
||||
|
||||
// Plots from executor
|
||||
if (name === "executor" && data?.encoded_plots) {
|
||||
// Plots from data analyst worker
|
||||
if (name === "data_analyst_worker" && data?.encoded_plots) {
|
||||
lastMsg.plots = [...(lastMsg.plots || []), ...data.encoded_plots]
|
||||
// Filter out the 'active' step and replace with 'complete'
|
||||
const filteredSteps = (lastMsg.steps || []).filter(s => s !== "Performing data analysis...");
|
||||
@@ -134,15 +135,25 @@ export const ChatService = {
|
||||
// Status for intermediate nodes (completion)
|
||||
const statusMap: Record<string, string> = {
|
||||
"query_analyzer": "Query analysis complete.",
|
||||
"planner": "Strategic plan generated.",
|
||||
"coder": "Analysis code generated."
|
||||
"planner": "Checklist generated.",
|
||||
"delegate": "Task assigned.",
|
||||
"reflector": "Result verified.",
|
||||
"coder": "Analysis code generated.",
|
||||
"executor": "Code execution complete.",
|
||||
"searcher": "Web search complete.",
|
||||
"summarizer": "Task summary generated."
|
||||
}
|
||||
|
||||
if (name && statusMap[name]) {
|
||||
// Find and replace the active status if it exists
|
||||
const activeStatus = name === "query_analyzer" ? "Analyzing query..." :
|
||||
name === "planner" ? "Generating strategic plan..." :
|
||||
name === "coder" ? "Writing analysis code..." : null;
|
||||
name === "planner" ? "Generating high-level plan..." :
|
||||
name === "delegate" ? "Routing task..." :
|
||||
name === "reflector" ? "Evaluating results..." :
|
||||
name === "coder" ? "Writing analysis code..." :
|
||||
name === "executor" ? "Executing code..." :
|
||||
name === "searcher" ? "Searching web..." :
|
||||
name === "summarizer" ? "Summarizing results..." : null;
|
||||
|
||||
let filteredSteps = lastMsg.steps || [];
|
||||
if (activeStatus) {
|
||||
@@ -159,9 +170,13 @@ export const ChatService = {
|
||||
if (type === "on_chain_start") {
|
||||
const startStatusMap: Record<string, string> = {
|
||||
"query_analyzer": "Analyzing query...",
|
||||
"planner": "Generating strategic plan...",
|
||||
"planner": "Generating high-level plan...",
|
||||
"delegate": "Routing task...",
|
||||
"reflector": "Evaluating results...",
|
||||
"coder": "Writing analysis code...",
|
||||
"executor": "Performing data analysis..."
|
||||
"executor": "Executing code...",
|
||||
"searcher": "Searching web...",
|
||||
"summarizer": "Summarizing results..."
|
||||
}
|
||||
|
||||
if (name && startStatusMap[name]) {
|
||||
|
||||
Reference in New Issue
Block a user