From 897e55e6729a3ada67ae636f95a15ac2dac85a90 Mon Sep 17 00:00:00 2001 From: ViperEkura <3081035982@qq.com> Date: Tue, 28 Apr 2026 15:20:08 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E9=83=A8=E5=88=86=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src/components/ProcessBlock.vue | 11 +- dashboard/src/utils/parallelStreamManager.js | 5 +- dashboard/src/views/ChatRoomView.vue | 224 +++++++++++++++++-- luxx/services/agentic_loop.py | 35 ++- luxx/services/chat_room.py | 6 +- luxx/services/stream_context.py | 64 +++++- luxx/tools/executor.py | 37 +-- 7 files changed, 331 insertions(+), 51 deletions(-) diff --git a/dashboard/src/components/ProcessBlock.vue b/dashboard/src/components/ProcessBlock.vue index c79dbbd..ab86c82 100644 --- a/dashboard/src/components/ProcessBlock.vue +++ b/dashboard/src/components/ProcessBlock.vue @@ -112,7 +112,16 @@ const allItems = computed(() => { } else if (step.type === 'tool_result') { // 合并 tool_result 到对应的 tool_call const toolId = step.id_ref || step.id - const match = items.findLast(it => it.type === 'tool_call' && it.id === toolId) + // Use find() instead of findLast() for better compatibility + // Search in reverse order to find the most recent matching tool_call + let match = null + for (let j = items.length - 1; j >= 0; j--) { + if (items[j].type === 'tool_call' && items[j].id === toolId) { + match = items[j] + break + } + } + if (match) { let resultContent = step.content || '' let displayContent = resultContent diff --git a/dashboard/src/utils/parallelStreamManager.js b/dashboard/src/utils/parallelStreamManager.js index 2be8278..638d48b 100644 --- a/dashboard/src/utils/parallelStreamManager.js +++ b/dashboard/src/utils/parallelStreamManager.js @@ -123,7 +123,10 @@ class ParallelStreamManager { break case 'agent_error': - store.errorAgentStream(roomId, data.agent_id || data.agentId, data.error) + store.errorAgentStream(roomId, data.agent_id || data.agentId, { + message: data.error, + agentName: data.agent_name || data.agentName + }) break case 'parallel_end': diff --git a/dashboard/src/views/ChatRoomView.vue b/dashboard/src/views/ChatRoomView.vue index cc021d4..d100a0e 100644 --- a/dashboard/src/views/ChatRoomView.vue +++ b/dashboard/src/views/ChatRoomView.vue @@ -49,7 +49,7 @@ const roomsLoading = ref(false) const providers = ref([]) const showCreate = ref(false) const creating = ref(false) -const newRoom = ref({ title: '', task: '', max_rounds: 5, agent_ids: [] }) +const newRoom = ref({ title: '', task: '', max_rounds: 5, agent_ids: [], execution_mode: 'sequential' }) const showAddToRoom = ref(false) // ============ Selected room state ============ @@ -85,6 +85,25 @@ const canEditRoom = computed(() => room.value?.status !== 'running' && !streamin const executionMode = computed(() => room.value?.execution_mode || 'sequential') const isParallelMode = computed(() => executionMode.value === 'parallel') +// Parallel mode agent states +const parallelAgents = computed(() => { + const roomData = store.rooms[selectedId.value] + if (!roomData || !roomData.agents) return {} + return roomData.agents +}) + +const parallelAgentList = computed(() => Object.values(parallelAgents.value)) + +const parallelStats = computed(() => { + const agents = parallelAgentList.value + return { + total: agents.length, + completed: agents.filter(a => a.status === 'completed').length, + streaming: agents.filter(a => a.status === 'streaming').length, + error: agents.filter(a => a.status === 'error').length + } +}) + // Group messages by round number for better visual organization const groupedMessages = computed(() => { const groups = [] @@ -228,6 +247,7 @@ async function createRoom() { title: newRoom.value.title, task: newRoom.value.task, max_rounds: newRoom.value.max_rounds, + execution_mode: newRoom.value.execution_mode, agents }) showCreate.value = false @@ -243,7 +263,7 @@ async function createRoom() { } function resetNewRoom() { - newRoom.value = { title: '', task: '', max_rounds: 5, agent_ids: [] } + newRoom.value = { title: '', task: '', max_rounds: 5, agent_ids: [], execution_mode: 'sequential' } } function toggleAgentInNewRoom(agentId) { @@ -275,9 +295,15 @@ async function deleteRoom(id) { async function selectRoom(id) { if (streaming.value) { if (abortController) abortController.abort() + if (isParallelMode.value) { + parallelStreamManager.cancelRoom(selectedId.value) + } streaming.value = false } + // Clean up both mode states streamingMessages.value = {} + store.cleanupRoom(selectedId.value) + selectedId.value = id error.value = '' editingRoomAgent.value = null @@ -347,9 +373,15 @@ async function startRoom() { if (e.name !== 'AbortError') error.value = e.message } finally { streaming.value = false + // Reload messages after parallel execution completes if (selectedId.value) { - const res = await chatRoomsAPI.get(selectedId.value) - room.value = res.data + const [roomRes, msgRes] = await Promise.all([ + chatRoomsAPI.get(selectedId.value), + chatRoomsAPI.getMessages(selectedId.value) + ]) + room.value = roomRes.data + messages.value = msgRes.data?.messages || [] + store.cleanupRoom(selectedId.value) await loadRooms() } } @@ -457,11 +489,21 @@ function handleSSEEvent(event, data) { async function stopRoom() { try { - if (abortController) { abortController.abort(); abortController = null } + // Cancel sequential mode + if (abortController) { + abortController.abort(); + abortController = null + } + // Cancel parallel mode + if (isParallelMode.value) { + parallelStreamManager.cancelRoom(selectedId.value) + } await chatRoomsAPI.stop(selectedId.value) room.value = { ...room.value, status: 'paused' } streaming.value = false streamingMessages.value = {} + // Clean up parallel store + store.cleanupRoom(selectedId.value) } catch (e) { console.error('Failed to stop room:', e) } } @@ -704,12 +746,31 @@ onUnmounted(() => { 📋 Sequential -
-
+ +
+
+ +
+ {{ parallelStats.completed }}/{{ parallelStats.total }} +
- - + + +