diff --git a/src/agentkit/server/frontend/src/api/types.ts b/src/agentkit/server/frontend/src/api/types.ts index f9c72e8..8033824 100644 --- a/src/agentkit/server/frontend/src/api/types.ts +++ b/src/agentkit/server/frontend/src/api/types.ts @@ -38,16 +38,30 @@ export interface IChatMessage { routing_method?: string confidence?: number task_id?: string - status?: 'completed' | 'pending' + status?: 'completed' | 'pending' | 'error' tool_calls?: IToolCallData[] thinking?: string expert_id?: string expert_name?: string expert_color?: string expert_avatar?: string - message_type?: 'chat' | 'handoff' | 'assist_request' | 'plan_update' | 'milestone' | 'board_speech' | 'board_summary' | 'board_conclusion' + message_type?: + | 'chat' + | 'handoff' + | 'assist_request' + | 'plan_update' + | 'milestone' + | 'board_started' + | 'board_speech' + | 'board_summary' + | 'board_conclusion' + | 'error' board_round?: number board_role?: 'moderator' | 'expert' | 'user' | 'summary' + plan_phases?: ITeamPlanPhase[] + error_detail?: string + board_started?: IBoardStartedData + board_conclusion?: IBoardConcludedData } /** Conversation with messages */ @@ -218,6 +232,34 @@ export interface IBoardMessage { timestamp: number } +/** Expert template (matches backend GET /api/v1/experts response item) */ +export interface IExpertTemplate { + name: string + description: string + is_builtin: boolean + avatar: string + color: string + persona: string + thinking_style: string + speaking_style: string + decision_framework: string +} + +/** Experts list response (matches backend GET /api/v1/experts) */ +export interface IExpertsResponse { + experts: IExpertTemplate[] + total: number +} + +/** File upload response (matches backend POST /api/v1/chat/upload) */ +export interface IUploadResponse { + filename: string + stored_name: string + content_type: string + size: number + download_url: string +} + /** API error */ export interface IApiError { status: number diff --git a/src/agentkit/server/frontend/src/components/chat/messages/BoardBannerCard.vue b/src/agentkit/server/frontend/src/components/chat/messages/BoardBannerCard.vue new file mode 100644 index 0000000..4f3d925 --- /dev/null +++ b/src/agentkit/server/frontend/src/components/chat/messages/BoardBannerCard.vue @@ -0,0 +1,137 @@ + + + + + + 🏛️ + 私董会 — {{ topic }} + + + + {{ expert.avatar }} + {{ expert.name }} + + + + 轮次:第 {{ currentRound }} / {{ maxRounds }} 轮 + + + + + + + + + + + diff --git a/src/agentkit/server/frontend/src/stores/chat.ts b/src/agentkit/server/frontend/src/stores/chat.ts index 975fedc..24124c7 100644 --- a/src/agentkit/server/frontend/src/stores/chat.ts +++ b/src/agentkit/server/frontend/src/stores/chat.ts @@ -31,7 +31,7 @@ export const useChatStore = defineStore('chat', () => { // Board Meeting state (transient, only active during a board discussion) const boardState = ref<{ topic: string - experts: Array<{ name: string; avatar: string; color: string; is_moderator: boolean }> + experts: Array<{ name: string; avatar: string; color: string; is_moderator: boolean; persona: string }> max_rounds: number current_round: number status: 'discussing' | 'concluding' | 'completed' | 'dissolved' @@ -56,7 +56,7 @@ export const useChatStore = defineStore('chat', () => { const data = await apiClient.getConversations() // Normalize server response: backend returns {id, created_at, updated_at, message_count} // but frontend IConversation expects {id, title, messages, created_at, updated_at} - conversations.value = data.map((conv: any) => ({ + conversations.value = data.map((conv: IConversation) => ({ id: conv.id, title: conv.title || '对话', messages: Array.isArray(conv.messages) ? conv.messages : [], @@ -276,7 +276,7 @@ export const useChatStore = defineStore('chat', () => { socket.onmessage = (event: MessageEvent) => { try { - const data = JSON.parse(event.data as string) as Record + const data = JSON.parse(event.data as string) as Record console.log('[Chat WS] Received:', data.type, data) handleWsMessage(data) } catch (error) { @@ -403,6 +403,8 @@ export const useChatStore = defineStore('chat', () => { return _teamStore } + // TODO: refactor to WsServerMessage union to eliminate `any`. + // This function predates the current VI redesign and touches many legacy branches. function handleWsMessage(data: Record): void { // Backend sends nested data: {type, data: {...}} // Flatten for easier access @@ -700,6 +702,7 @@ export const useChatStore = defineStore('chat', () => { avatar: e.avatar, color: e.color, is_moderator: e.is_moderator, + persona: e.persona, })), max_rounds: data.max_rounds, current_round: 0, @@ -708,18 +711,18 @@ export const useChatStore = defineStore('chat', () => { streamingSteps.value.push( `私董会已开启: 主题「${data.topic}」, ${data.experts.length} 位专家, 最多 ${data.max_rounds} 轮` ) - // Push a system-style message to indicate board start + // Push a structured banner message so the renderer can show BoardBannerCard const conversationId = currentConversationId.value if (conversationId) { const startMsg: IChatMessage = { id: generateId(), role: 'assistant', - content: `🏛️ **私董会开始**\n\n**主题**: ${data.topic}\n**专家**: ${data.experts - .map((e) => `${e.avatar} ${e.name}${e.is_moderator ? ' (主持人)' : ''}`) - .join(', ')}\n**最大轮次**: ${data.max_rounds}`, + content: `🏛️ 私董会开始:${data.topic}`, timestamp: new Date().toISOString(), status: 'completed', - message_type: 'milestone', + message_type: 'board_started', + board_started: data, + board_round: 0, } appendMessage(conversationId, startMsg) } @@ -789,9 +792,21 @@ export const useChatStore = defineStore('chat', () => { streamingSteps.value.push( `私董会结束: ${data.total_rounds} 轮讨论${data.error ? ' (异常)' : ''}` ) - // The final_answer event will carry the formatted conclusion, - // so we don't need to add a separate message here. - // The conclusion is already persisted by the backend. + // Push a structured conclusion message so the renderer can show BoardConclusionCard + const conversationId = currentConversationId.value + if (conversationId) { + const conclusionMsg: IChatMessage = { + id: generateId(), + role: 'assistant', + content: data.summary || '私董会已结束', + timestamp: new Date().toISOString(), + status: 'completed', + message_type: 'board_conclusion', + board_conclusion: data, + board_round: data.total_rounds, + } + appendMessage(conversationId, conclusionMsg) + } // Clear board state after a short delay to allow UI to update setTimeout(() => { boardState.value = null