Refine agents command center topology visuals

Strengthen the Ultron command center with clearer blueprint-style hierarchy, embedded route telemetry, and test coverage for active path visualization.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-24 21:42:01 +08:00
parent b8d135a7e2
commit aafa05dc1c
3 changed files with 1875 additions and 564 deletions

View File

@@ -0,0 +1,121 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { mount } from '@vue/test-utils'
const mocks = vi.hoisted(() => ({
getHierarchyStats: vi.fn(),
updateConfig: vi.fn(),
}))
vi.mock('@/api/agent', () => ({
agentApi: {
getHierarchyStats: mocks.getHierarchyStats,
updateConfig: mocks.updateConfig,
},
}))
import AgentsPage from './index.vue'
const hierarchyStats = {
main_agents: [
{
agent_id: 'planner',
call_count: 12,
current_task: null,
status: 'idle',
sub_commanders: [
{ agent_id: 'planner_scope', call_count: 4, current_task: null, status: 'idle' },
{ agent_id: 'planner_steps', call_count: 9, current_task: '拆解执行步骤', status: 'active' },
],
},
{
agent_id: 'executor',
call_count: 8,
current_task: null,
status: 'idle',
sub_commanders: [
{ agent_id: 'executor_tasks', call_count: 8, current_task: null, status: 'idle' },
{ agent_id: 'executor_forum', call_count: 4, current_task: null, status: 'idle' },
],
},
{
agent_id: 'librarian',
call_count: 5,
current_task: null,
status: 'idle',
sub_commanders: [
{ agent_id: 'librarian_retrieval', call_count: 5, current_task: null, status: 'idle' },
{ agent_id: 'librarian_graph', call_count: 2, current_task: null, status: 'idle' },
],
},
{
agent_id: 'analyst',
call_count: 3,
current_task: null,
status: 'idle',
sub_commanders: [
{ agent_id: 'analyst_progress', call_count: 2, current_task: null, status: 'idle' },
{ agent_id: 'analyst_insights', call_count: 3, current_task: null, status: 'idle' },
],
},
],
}
describe('agents page pcb command center', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.useFakeTimers()
mocks.getHierarchyStats.mockResolvedValue(hierarchyStats)
mocks.updateConfig.mockResolvedValue({})
})
it('shows commander skills and active route telemetry for an active sub commander path', async () => {
const wrapper = mount(AgentsPage)
await Promise.resolve()
await Promise.resolve()
const skillsPanel = wrapper.get('[data-testid="commander-skills"]')
expect(skillsPanel.text()).toContain('指挥官技能')
expect(skillsPanel.text()).toContain('路径拆解')
const activeSkill = wrapper.get('[data-testid="commander-skill-skill_planner"]')
expect(activeSkill.classes()).toContain('active')
const plannerBus = wrapper.get('[data-testid="bus-link-planner"]')
expect(plannerBus.classes()).toContain('energized')
const plannerStepsBranch = wrapper.get('[data-testid="sub-link-planner_steps"]')
expect(plannerStepsBranch.classes()).toContain('energized')
const routeTelemetry = wrapper.get('[data-testid="route-telemetry"]')
expect(routeTelemetry.text()).toContain('ACTIVE ROUTE')
expect(routeTelemetry.text()).toContain('PLANNER')
expect(routeTelemetry.text()).toContain('STEPS')
})
it('keeps the configuration drawer available when selecting a main agent chip', async () => {
const wrapper = mount(AgentsPage)
await Promise.resolve()
await Promise.resolve()
await wrapper.get('[data-testid="agent-chip-planner"]').trigger('click')
expect(wrapper.text()).toContain('AGENT CONFIGURATION')
expect(wrapper.find('input').exists()).toBe(true)
})
it('cycles the energized demo path when hierarchy stats are offline', async () => {
mocks.getHierarchyStats.mockRejectedValue(new Error('offline'))
const wrapper = mount(AgentsPage)
await Promise.resolve()
await Promise.resolve()
expect(wrapper.get('[data-testid="bus-link-planner"]').classes()).toContain('energized')
expect(wrapper.get('[data-testid="sub-link-planner_scope"]').classes()).toContain('energized')
await vi.advanceTimersByTimeAsync(1700)
expect(wrapper.get('[data-testid="sub-link-planner_scope"]').classes()).not.toContain('energized')
expect(wrapper.get('[data-testid="sub-link-planner_steps"]').classes()).toContain('energized')
})
})

File diff suppressed because it is too large Load Diff