feat(ux): Implement advanced reasoning view with progress tracking and refined Markdown rendering.

This commit is contained in:
Yunxiao Xu
2026-02-13 03:13:58 -08:00
parent dc6e73ec79
commit e16b19ed66
6 changed files with 114 additions and 49 deletions

View File

@@ -22,14 +22,14 @@ describe("ChatService SSE Parsing", () => {
const events = ChatService.parseSSEChunk(rawChunk)
expect(events).toHaveLength(2)
expect(events[1].data.chunk).toBe(" World")
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`
const events = ChatService.parseSSEChunk(rawChunk)
expect(events[0].data.encoded_plots).toEqual(["base64data"])
expect(events[0].data!.encoded_plots).toEqual(["base64data"])
})
it("should identify the done event", () => {

View File

@@ -10,13 +10,26 @@ export interface MessageResponse {
steps?: string[] // reasoning steps
}
export interface ConversationResponse {
id: string
name: string
summary?: string
created_at: string
data_state: string
}
export interface ChatEvent {
type: string
name?: string
node?: string
data?: {
chunk?: { content?: string }
output?: { messages?: { content: string }[] }
output?: {
messages?: { content: string }[]
plots?: string[]
dfs?: Record<string, string>
summary?: string
}
encoded_plots?: string[]
message?: string
}
@@ -47,7 +60,7 @@ export const ChatService = {
if (!dataStr) continue
try {
const event = JSON.parse(dataStr)
const event = JSON.parse(dataStr) as ChatEvent
events.push(event)
} catch (err) {
console.error("Failed to parse SSE event JSON:", err, dataStr)
@@ -245,36 +258,37 @@ export const ChatService = {
}
activeMessages = this.updateMessagesWithEvent(activeMessages, event)
onMessageUpdate(activeMessages)
onMessageUpdate([...activeMessages])
}
}
} catch (err: any) {
} catch (err: unknown) {
console.error("Streaming error:", err)
if (onError) onError(err.message || "Connection failed")
const message = err instanceof Error ? err.message : "Connection failed"
if (onError) onError(message)
}
},
async listConversations() {
const response = await api.get("/conversations")
async listConversations(): Promise<ConversationResponse[]> {
const response = await api.get<ConversationResponse[]>("/conversations")
return response.data
},
async createConversation(name: string = "New Conversation") {
const response = await api.post("/conversations", { name })
async createConversation(name: string = "New Conversation"): Promise<ConversationResponse> {
const response = await api.post<ConversationResponse>("/conversations", { name })
return response.data
},
async renameConversation(conversationId: string, name: string) {
const response = await api.patch(`/conversations/${conversationId}`, { name })
async renameConversation(conversationId: string, name: string): Promise<ConversationResponse> {
const response = await api.patch<ConversationResponse>(`/conversations/${conversationId}`, { name })
return response.data
},
async deleteConversation(conversationId: string) {
async deleteConversation(conversationId: string): Promise<void> {
await api.delete(`/conversations/${conversationId}`)
},
async getMessages(conversationId: string) {
const response = await api.get(`/conversations/${conversationId}/messages`)
async getMessages(conversationId: string): Promise<MessageResponse[]> {
const response = await api.get<MessageResponse[]>(`/conversations/${conversationId}/messages`)
return response.data
}
}

View File

@@ -1,9 +1,10 @@
import { describe, it, expect, vi, beforeEach } from "vitest"
import api from "./api"
import { ChatService } from "./chat"
import { AxiosResponse } from "axios"
vi.mock("./api")
const mockedApi = api as any
const mockedApi = vi.mocked(api)
describe("ChatService History Management", () => {
beforeEach(() => {
@@ -12,7 +13,7 @@ describe("ChatService History Management", () => {
it("should list conversations", async () => {
const mockData = [{ id: "c1", name: "Conv 1" }]
mockedApi.get.mockResolvedValueOnce({ data: mockData })
mockedApi.get.mockResolvedValueOnce({ data: mockData } as AxiosResponse)
const result = await ChatService.listConversations()
expect(mockedApi.get).toHaveBeenCalledWith("/conversations")
@@ -21,7 +22,7 @@ describe("ChatService History Management", () => {
it("should create a conversation", async () => {
const mockData = { id: "c2", name: "New Conv" }
mockedApi.post.mockResolvedValueOnce({ data: mockData })
mockedApi.post.mockResolvedValueOnce({ data: mockData } as AxiosResponse)
const result = await ChatService.createConversation("New Conv")
expect(mockedApi.post).toHaveBeenCalledWith("/conversations", { name: "New Conv" })
@@ -30,7 +31,7 @@ describe("ChatService History Management", () => {
it("should rename a conversation", async () => {
const mockData = { id: "c1", name: "Renamed" }
mockedApi.patch.mockResolvedValueOnce({ data: mockData })
mockedApi.patch.mockResolvedValueOnce({ data: mockData } as AxiosResponse)
const result = await ChatService.renameConversation("c1", "Renamed")
expect(mockedApi.patch).toHaveBeenCalledWith("/conversations/c1", { name: "Renamed" })
@@ -38,7 +39,7 @@ describe("ChatService History Management", () => {
})
it("should delete a conversation", async () => {
mockedApi.delete.mockResolvedValueOnce({})
mockedApi.delete.mockResolvedValueOnce({} as AxiosResponse)
await ChatService.deleteConversation("c1")
expect(mockedApi.delete).toHaveBeenCalledWith("/conversations/c1")
@@ -46,7 +47,7 @@ describe("ChatService History Management", () => {
it("should fetch messages", async () => {
const mockData = [{ id: "m1", content: "Hi" }]
mockedApi.get.mockResolvedValueOnce({ data: mockData })
mockedApi.get.mockResolvedValueOnce({ data: mockData } as AxiosResponse)
const result = await ChatService.getMessages("c1")
expect(mockedApi.get).toHaveBeenCalledWith("/conversations/c1/messages")