feat(frontend): U8 适配前端类型支持流水线阶段事件
- types.ts: WsServerMessage 新增 phase_started/phase_completed/phase_failed 三个事件类型 - types.ts: ITeamPlanPhase 新增 task_description/depends_on/result 字段,parallel_type 和 milestone 改为可选 - chat.ts: handleWsMessage 新增 3 个 phase 事件 case 分支,调用 teamStore.updatePhaseStatus 更新阶段状态 - team.ts: 新增 updatePhaseStatus(phaseId, status, result?) 方法并导出 - ExpertTeamView.vue: 增强 phase 渲染展示 task_description 和 result,补充 --pending/--failed CSS 样式 - PlanVisualization.vue: 修复 parallel_type 可选后的类型检查错误
This commit is contained in:
parent
1e818b507d
commit
a72bc012d5
|
|
@ -104,6 +104,9 @@ export type WsServerMessage =
|
|||
| { type: 'expert_step'; data: { expert_id: string; expert_name: string; expert_color: string; content: string; step: number } }
|
||||
| { type: 'expert_result'; data: { expert_id: string; expert_name: string; expert_color: string; content: string } }
|
||||
| { type: 'plan_update'; data: { plan_phases: ITeamPlanPhase[] } }
|
||||
| { type: 'phase_started'; data: { phase_id: string; phase_name: string; assigned_expert: string; depends_on: string[] } }
|
||||
| { type: 'phase_completed'; data: { phase_id: string; phase_name: string; result_summary: string } }
|
||||
| { type: 'phase_failed'; data: { phase_id: string; phase_name: string; error: string } }
|
||||
| { type: 'team_synthesis'; data: { content: string } }
|
||||
| { type: 'team_dissolved'; data: { team_id: string } }
|
||||
// Board Meeting 模式事件
|
||||
|
|
@ -130,9 +133,12 @@ export interface ITeamPlanPhase {
|
|||
id: string
|
||||
name: string
|
||||
assigned_expert: string
|
||||
task_description?: string
|
||||
depends_on: string[]
|
||||
status: 'pending' | 'in_progress' | 'completed' | 'failed'
|
||||
parallel_type: 'serial' | 'subtask_parallel' | 'competitive_parallel'
|
||||
milestone: string
|
||||
result?: string
|
||||
parallel_type?: 'serial' | 'subtask_parallel' | 'competitive_parallel'
|
||||
milestone?: string
|
||||
}
|
||||
|
||||
/** Expert team state */
|
||||
|
|
|
|||
|
|
@ -35,9 +35,17 @@
|
|||
class="expert-team-view__phase"
|
||||
:class="`expert-team-view__phase--${phase.status}`"
|
||||
>
|
||||
<span class="expert-team-view__phase-name">{{ phase.name }}</span>
|
||||
<span class="expert-team-view__phase-expert">{{ phase.assigned_expert }}</span>
|
||||
<a-tag :color="statusColor(phase.status)" size="small">{{ statusLabel(phase.status) }}</a-tag>
|
||||
<div class="expert-team-view__phase-header">
|
||||
<span class="expert-team-view__phase-name">{{ phase.name }}</span>
|
||||
<span class="expert-team-view__phase-expert">{{ phase.assigned_expert }}</span>
|
||||
<a-tag :color="statusColor(phase.status)" size="small">{{ statusLabel(phase.status) }}</a-tag>
|
||||
</div>
|
||||
<div v-if="phase.task_description" class="expert-team-view__phase-desc">
|
||||
{{ phase.task_description }}
|
||||
</div>
|
||||
<div v-if="phase.result" class="expert-team-view__phase-result">
|
||||
{{ phase.result }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -152,11 +160,22 @@ function statusLabel(status: string): string {
|
|||
}
|
||||
|
||||
.expert-team-view__phase {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
padding: var(--space-2) 0;
|
||||
font-size: var(--font-xs);
|
||||
border-bottom: 1px solid var(--border-primary);
|
||||
}
|
||||
|
||||
.expert-team-view__phase:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.expert-team-view__phase-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-1) 0;
|
||||
font-size: var(--font-xs);
|
||||
}
|
||||
|
||||
.expert-team-view__phase-name {
|
||||
|
|
@ -167,11 +186,52 @@ function statusLabel(status: string): string {
|
|||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.expert-team-view__phase--completed {
|
||||
opacity: 0.7;
|
||||
.expert-team-view__phase-desc {
|
||||
color: var(--text-secondary);
|
||||
font-size: var(--font-xs);
|
||||
padding-left: var(--space-2);
|
||||
border-left: 2px solid var(--border-primary);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.expert-team-view__phase-result {
|
||||
color: var(--text-tertiary);
|
||||
font-size: var(--font-xs);
|
||||
padding-left: var(--space-2);
|
||||
border-left: 2px solid var(--color-success);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
max-height: 80px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.expert-team-view__phase--pending {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.expert-team-view__phase--in_progress {
|
||||
font-weight: var(--font-weight-medium);
|
||||
background: var(--bg-tertiary);
|
||||
margin: 0 calc(-1 * var(--space-3));
|
||||
padding-left: var(--space-3);
|
||||
padding-right: var(--space-3);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.expert-team-view__phase--completed {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.expert-team-view__phase--failed {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.expert-team-view__phase--failed .expert-team-view__phase-result {
|
||||
border-left-color: var(--color-error);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
<span>执行者: {{ phase.assigned_expert }}</span>
|
||||
<span v-if="phase.milestone"> | 里程碑: {{ phase.milestone }}</span>
|
||||
</div>
|
||||
<div v-if="phase.parallel_type !== 'serial'" class="plan-visualization__phase-type">
|
||||
<div v-if="phase.parallel_type && phase.parallel_type !== 'serial'" class="plan-visualization__phase-type">
|
||||
<a-tag size="small">{{ parallelLabel(phase.parallel_type) }}</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -661,6 +661,33 @@ export const useChatStore = defineStore('chat', () => {
|
|||
break
|
||||
}
|
||||
|
||||
case 'phase_started': {
|
||||
const teamStore = _getTeamStore()
|
||||
if (teamStore?.teamState) {
|
||||
teamStore.updatePhaseStatus(payload.phase_id, 'in_progress')
|
||||
streamingSteps.value.push(`阶段开始: ${payload.phase_name} (${payload.assigned_expert})`)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'phase_completed': {
|
||||
const teamStore = _getTeamStore()
|
||||
if (teamStore?.teamState) {
|
||||
teamStore.updatePhaseStatus(payload.phase_id, 'completed', payload.result_summary)
|
||||
streamingSteps.value.push(`阶段完成: ${payload.phase_name}`)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'phase_failed': {
|
||||
const teamStore = _getTeamStore()
|
||||
if (teamStore?.teamState) {
|
||||
teamStore.updatePhaseStatus(payload.phase_id, 'failed', payload.error)
|
||||
streamingSteps.value.push(`阶段失败: ${payload.phase_name} - ${payload.error}`)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// ── Board Meeting 模式事件 ────────────────────────────────────────
|
||||
|
||||
case 'board_started': {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,21 @@ export const useTeamStore = defineStore('team', () => {
|
|||
}
|
||||
}
|
||||
|
||||
function updatePhaseStatus(
|
||||
phaseId: string,
|
||||
status: ITeamPlanPhase['status'],
|
||||
result?: string,
|
||||
) {
|
||||
if (!teamState.value) return
|
||||
const phases = teamState.value.plan_phases.map((p) => {
|
||||
if (p.id !== phaseId) return p
|
||||
return result !== undefined
|
||||
? { ...p, status, result }
|
||||
: { ...p, status }
|
||||
})
|
||||
teamState.value = { ...teamState.value, plan_phases: phases }
|
||||
}
|
||||
|
||||
function selectExpert(expertId: string | null) {
|
||||
selectedExpertId.value = expertId
|
||||
}
|
||||
|
|
@ -50,6 +65,6 @@ export const useTeamStore = defineStore('team', () => {
|
|||
return {
|
||||
teamState, selectedExpertId, activeExperts, leadExpert,
|
||||
isTeamMode, currentPhase, completedPhases,
|
||||
setTeamState, updatePhases, selectExpert, clearTeam
|
||||
setTeamState, updatePhases, updatePhaseStatus, selectExpert, clearTeam
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue