diff --git a/src/agentkit/server/frontend/components.d.ts b/src/agentkit/server/frontend/components.d.ts index 7f9ead0..135177e 100644 --- a/src/agentkit/server/frontend/components.d.ts +++ b/src/agentkit/server/frontend/components.d.ts @@ -16,6 +16,7 @@ declare module 'vue' { AButton: typeof import('ant-design-vue/es')['Button'] ACard: typeof import('ant-design-vue/es')['Card'] ACheckbox: typeof import('ant-design-vue/es')['Checkbox'] + ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup'] ACol: typeof import('ant-design-vue/es')['Col'] ACollapse: typeof import('ant-design-vue/es')['Collapse'] ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel'] @@ -64,12 +65,18 @@ declare module 'vue' { ATabs: typeof import('ant-design-vue/es')['Tabs'] ATag: typeof import('ant-design-vue/es')['Tag'] ATextarea: typeof import('ant-design-vue/es')['Textarea'] + ATimePicker: typeof import('ant-design-vue/es/time-picker/dayjs')['TimePicker'] AUploadDragger: typeof import('ant-design-vue/es')['UploadDragger'] BoardBannerCard: typeof import('./src/components/chat/messages/BoardBannerCard.vue')['default'] BoardConclusionCard: typeof import('./src/components/chat/messages/BoardConclusionCard.vue')['default'] BoardMeetingModal: typeof import('./src/components/chat/BoardMeetingModal.vue')['default'] BoardRoundCard: typeof import('./src/components/chat/messages/BoardRoundCard.vue')['default'] BoardStatusView: typeof import('./src/components/chat/BoardStatusView.vue')['default'] + CalendarDrawer: typeof import('./src/components/calendar/CalendarDrawer.vue')['default'] + CalendarGrid: typeof import('./src/components/calendar/CalendarGrid.vue')['default'] + CalendarPanel: typeof import('./src/components/calendar/CalendarPanel.vue')['default'] + CalendarTab: typeof import('./src/components/layout/tabs/CalendarTab.vue')['default'] + CardView: typeof import('./src/components/calendar/CardView.vue')['default'] ChangePasswordPanel: typeof import('./src/components/settings/ChangePasswordPanel.vue')['default'] ChatInput: typeof import('./src/components/chat/ChatInput.vue')['default'] ChatMessage: typeof import('./src/components/chat/ChatMessage.vue')['default'] @@ -80,8 +87,16 @@ declare module 'vue' { ConditionNode: typeof import('./src/components/workflow/ConditionNode.vue')['default'] ContextPill: typeof import('./src/components/chat/ContextPill.vue')['default'] DashboardOverview: typeof import('./src/components/evolution/DashboardOverview.vue')['default'] + DebateArgumentCard: typeof import('./src/components/chat/messages/DebateArgumentCard.vue')['default'] + DebateBannerCard: typeof import('./src/components/chat/messages/DebateBannerCard.vue')['default'] + DebateConclusionCard: typeof import('./src/components/chat/messages/DebateConclusionCard.vue')['default'] + DebateSummaryCard: typeof import('./src/components/chat/messages/DebateSummaryCard.vue')['default'] + DocumentCard: typeof import('./src/components/chat/messages/DocumentCard.vue')['default'] + DocumentPanel: typeof import('./src/components/chat/DocumentPanel.vue')['default'] DocumentUpload: typeof import('./src/components/kb/DocumentUpload.vue')['default'] ErrorCard: typeof import('./src/components/chat/messages/ErrorCard.vue')['default'] + EventBadge: typeof import('./src/components/calendar/EventBadge.vue')['default'] + EventEditor: typeof import('./src/components/calendar/EventEditor.vue')['default'] ExperiencePanel: typeof import('./src/components/evolution/ExperiencePanel.vue')['default'] ExperienceTimeline: typeof import('./src/components/evolution/ExperienceTimeline.vue')['default'] ExpertMessage: typeof import('./src/components/chat/ExpertMessage.vue')['default'] @@ -91,7 +106,9 @@ declare module 'vue' { FileTree: typeof import('./src/components/code/FileTree.vue')['default'] FlowCanvas: typeof import('./src/components/workflow/FlowCanvas.vue')['default'] IconNav: typeof import('./src/components/layout/IconNav.vue')['default'] + InvitationManager: typeof import('./src/components/calendar/InvitationManager.vue')['default'] KnowledgeTab: typeof import('./src/components/layout/tabs/KnowledgeTab.vue')['default'] + ListView: typeof import('./src/components/calendar/ListView.vue')['default'] MentionDropdown: typeof import('./src/components/chat/MentionDropdown.vue')['default'] MessageShell: typeof import('./src/components/chat/messages/MessageShell.vue')['default'] MetricsChart: typeof import('./src/components/evolution/MetricsChart.vue')['default'] @@ -105,6 +122,7 @@ declare module 'vue' { PlanVisualization: typeof import('./src/components/chat/PlanVisualization.vue')['default'] PropertyPanel: typeof import('./src/components/workflow/PropertyPanel.vue')['default'] QuadrantPanel: typeof import('./src/components/layout/QuadrantPanel.vue')['default'] + ReminderConfig: typeof import('./src/components/calendar/ReminderConfig.vue')['default'] RightPanel: typeof import('./src/components/layout/RightPanel.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] @@ -123,6 +141,7 @@ declare module 'vue' { SourceConfig: typeof import('./src/components/kb/SourceConfig.vue')['default'] SplashScreen: typeof import('./src/components/layout/SplashScreen.vue')['default'] SplitPane: typeof import('./src/components/layout/SplitPane.vue')['default'] + SyncSettings: typeof import('./src/components/calendar/SyncSettings.vue')['default'] SystemMonitorPanel: typeof import('./src/components/layout/SystemMonitorPanel.vue')['default'] SystemTab: typeof import('./src/components/layout/tabs/SystemTab.vue')['default'] TeamModal: typeof import('./src/components/chat/TeamModal.vue')['default'] diff --git a/src/agentkit/server/frontend/src/api/types.ts b/src/agentkit/server/frontend/src/api/types.ts index 4330b27..0678afb 100644 --- a/src/agentkit/server/frontend/src/api/types.ts +++ b/src/agentkit/server/frontend/src/api/types.ts @@ -58,6 +58,10 @@ export interface IChatMessage { | 'board_speech' | 'board_summary' | 'board_conclusion' + | 'debate_started' + | 'debate_argument' + | 'debate_summary' + | 'debate_resolved' | 'error' board_round?: number board_role?: 'moderator' | 'expert' | 'user' | 'summary' @@ -65,6 +69,13 @@ export interface IChatMessage { error_detail?: string board_started?: IBoardStartedData board_conclusion?: IBoardConcludedData + debate_topic?: string + debate_round?: number + debate_decision?: string + debate_rationale?: string + debate_participants?: string[] + debate_opening?: string + debate_moderator?: string } /** Conversation with messages */ @@ -132,6 +143,12 @@ export type WsServerMessage = | { type: 'round_summary'; data: IRoundSummaryData } | { type: 'user_intervention'; data: IUserInterventionData } | { type: 'board_concluded'; data: IBoardConcludedData } + // Debate (U5) 事件 + | { type: 'debate_started'; data: IDebateStartedData } + | { type: 'expert_argument'; data: IDebateArgumentData } + | { type: 'debate_round_summary'; data: IDebateRoundSummaryData } + | { type: 'debate_resolved'; data: IDebateResolvedData } + | { type: 'team_intervention_ack'; data: { content: string } } // Calendar 事件 (KTD-10 — piggyback on chat WS) | { type: 'calendar_event_created'; data: ICalendarEventCreatedData } | { type: 'calendar_reminder'; data: ICalendarReminderData } @@ -225,6 +242,47 @@ export interface IBoardConcludedData { error?: string } +// ── Debate (U5) 模式类型 ────────────────────────────────────────────── + +/** debate_started event payload */ +export interface IDebateStartedData { + phase_id: string + phase_name: string + topic: string + participants: string[] + max_rounds: number + opening: string +} + +/** expert_argument event payload */ +export interface IDebateArgumentData { + phase_id: string + expert_id: string + expert_name: string + expert_color: string + content: string + round: number + topic: string +} + +/** debate_round_summary event payload */ +export interface IDebateRoundSummaryData { + phase_id: string + moderator_name: string + content: string + round: number + continue: boolean +} + +/** debate_resolved event payload */ +export interface IDebateResolvedData { + phase_id: string + phase_name: string + decision: 'adopt' | 'compromise' | 'shelve' | 'inconclusive' + conclusion: string + rationale: string +} + /** Board meeting status (matches backend BoardStatus enum) */ export type BoardStatus = 'forming' | 'discussing' | 'concluding' | 'completed' | 'dissolved' diff --git a/src/agentkit/server/frontend/src/components/chat/ChatInput.vue b/src/agentkit/server/frontend/src/components/chat/ChatInput.vue index 482c6f6..7e57a93 100644 --- a/src/agentkit/server/frontend/src/components/chat/ChatInput.vue +++ b/src/agentkit/server/frontend/src/components/chat/ChatInput.vue @@ -78,6 +78,16 @@ 私董会 + + 辩论 + import { ref, computed, onMounted, onUnmounted, type Component } from 'vue' import { Input as AInput, Button as AButton, Select as ASelect } from 'ant-design-vue' -import { SendOutlined, TeamOutlined, UsergroupAddOutlined, PaperClipOutlined, PoweroffOutlined } from '@ant-design/icons-vue' +import { SendOutlined, TeamOutlined, UsergroupAddOutlined, PaperClipOutlined, PoweroffOutlined, CommentOutlined } from '@ant-design/icons-vue' import ContextPill from './ContextPill.vue' import MentionDropdown from './MentionDropdown.vue' import BoardMeetingModal from './BoardMeetingModal.vue' import TeamModal from './TeamModal.vue' import { useSkillsStore } from '@/stores/skills' +import { useTeamStore } from '@/stores/team' import type { ISkillInfo } from '@/api/skills' import { getDynamicBaseURL } from '@/api/base' import { apiClient } from '@/api/client' @@ -230,6 +241,7 @@ const mentionQuery = ref('') const mentionStartIndex = ref(-1) const mentionPosition = ref({ left: 0 }) const skillsStore = useSkillsStore() +const teamStore = useTeamStore() const skillSuggestions = computed(() => { return (skillsStore.skills || []).map((s: ISkillInfo) => ({ @@ -318,6 +330,14 @@ function handleTeamSubmit(command: string): void { emit('send', command, selectedModel.value) } +function handleDebateClick(): void { + // Prompt user for debate topic, then send as intervention + const topic = window.prompt('请输入辩论主题') + if (topic && topic.trim()) { + emit('send', `/debate ${topic.trim()}`, selectedModel.value) + } +} + function openFilePicker(): void { fileInputRef.value?.click() } @@ -563,6 +583,17 @@ function removePill(idx: number): void { background: var(--accent-board-soft); } +.chat-input__action-btn--debate { + color: #722ed1; + border-color: #d3adf7; +} + +.chat-input__action-btn--debate:not(:disabled):hover { + color: #531dab; + border-color: #722ed1; + background: #f9f0ff; +} + .chat-input__action-btn--clear { color: var(--text-tertiary); } diff --git a/src/agentkit/server/frontend/src/components/chat/helpers/useMessageRenderer.ts b/src/agentkit/server/frontend/src/components/chat/helpers/useMessageRenderer.ts index 23018ee..48846ce 100644 --- a/src/agentkit/server/frontend/src/components/chat/helpers/useMessageRenderer.ts +++ b/src/agentkit/server/frontend/src/components/chat/helpers/useMessageRenderer.ts @@ -6,6 +6,10 @@ import TeamPlanCard from '@/components/chat/messages/TeamPlanCard.vue' import BoardBannerCard from '@/components/chat/messages/BoardBannerCard.vue' import BoardRoundCard from '@/components/chat/messages/BoardRoundCard.vue' import BoardConclusionCard from '@/components/chat/messages/BoardConclusionCard.vue' +import DebateBannerCard from '@/components/chat/messages/DebateBannerCard.vue' +import DebateArgumentCard from '@/components/chat/messages/DebateArgumentCard.vue' +import DebateSummaryCard from '@/components/chat/messages/DebateSummaryCard.vue' +import DebateConclusionCard from '@/components/chat/messages/DebateConclusionCard.vue' import ErrorCard from '@/components/chat/messages/ErrorCard.vue' export type MessageViewType = @@ -16,6 +20,10 @@ export type MessageViewType = | 'board_speech' | 'board_summary' | 'board_conclusion' + | 'debate_started' + | 'debate_argument' + | 'debate_summary' + | 'debate_resolved' | 'milestone' | 'error' @@ -48,6 +56,14 @@ export function resolveMessageType(message: IChatMessage): MessageViewType { return 'board_summary' case 'board_conclusion': return 'board_conclusion' + case 'debate_started': + return 'debate_started' + case 'debate_argument': + return 'debate_argument' + case 'debate_summary': + return 'debate_summary' + case 'debate_resolved': + return 'debate_resolved' case 'milestone': return 'milestone' default: @@ -168,6 +184,83 @@ export function useMessageRenderer(message: IChatMessage) { }, } + case 'debate_started': + return { + type, + shell: { + name: '辩论', + avatar: '⚖', + color: '#722ed1', + meta: message.debate_topic || '', + }, + component: DebateBannerCard, + props: { + topic: message.debate_topic || '', + participants: message.debate_participants || [], + opening: message.debate_opening || message.content, + }, + } + + case 'debate_argument': + return { + type, + shell: { + name: message.expert_name || '专家', + avatar: (message.expert_name || '?')[0], + color: message.expert_color || '#722ed1', + meta: `辩论第${message.debate_round || 1}轮`, + }, + component: DebateArgumentCard, + props: { + content: message.content, + round: message.debate_round || 1, + expertName: message.expert_name || '', + expertColor: message.expert_color || '#722ed1', + }, + } + + case 'debate_summary': + return { + type, + shell: { + name: message.expert_name || 'Lead', + avatar: (message.expert_name || 'L')[0], + color: '#722ed1', + meta: `第${message.debate_round || 1}轮小结`, + }, + component: DebateSummaryCard, + props: { + content: message.content, + round: message.debate_round || 1, + moderatorName: message.debate_moderator || message.expert_name || '', + }, + } + + case 'debate_resolved': { + const decisionLabels: Record = { + adopt: '采纳', + compromise: '折中', + shelve: '搁置', + inconclusive: '未决', + } + const decision = message.debate_decision || 'inconclusive' + return { + type, + shell: { + name: '辩论裁决', + avatar: '⚖', + color: '#fa8c16', + meta: decisionLabels[decision] || decision, + }, + component: DebateConclusionCard, + props: { + conclusion: message.content, + decision, + rationale: message.debate_rationale || '', + }, + } + } + case 'error': return { type, diff --git a/src/agentkit/server/frontend/src/components/chat/messages/DebateArgumentCard.vue b/src/agentkit/server/frontend/src/components/chat/messages/DebateArgumentCard.vue new file mode 100644 index 0000000..b14c316 --- /dev/null +++ b/src/agentkit/server/frontend/src/components/chat/messages/DebateArgumentCard.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/src/agentkit/server/frontend/src/components/chat/messages/DebateBannerCard.vue b/src/agentkit/server/frontend/src/components/chat/messages/DebateBannerCard.vue new file mode 100644 index 0000000..260a173 --- /dev/null +++ b/src/agentkit/server/frontend/src/components/chat/messages/DebateBannerCard.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/src/agentkit/server/frontend/src/components/chat/messages/DebateConclusionCard.vue b/src/agentkit/server/frontend/src/components/chat/messages/DebateConclusionCard.vue new file mode 100644 index 0000000..8f2b835 --- /dev/null +++ b/src/agentkit/server/frontend/src/components/chat/messages/DebateConclusionCard.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/src/agentkit/server/frontend/src/components/chat/messages/DebateSummaryCard.vue b/src/agentkit/server/frontend/src/components/chat/messages/DebateSummaryCard.vue new file mode 100644 index 0000000..e1c52ff --- /dev/null +++ b/src/agentkit/server/frontend/src/components/chat/messages/DebateSummaryCard.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/src/agentkit/server/frontend/src/components/chat/messages/index.ts b/src/agentkit/server/frontend/src/components/chat/messages/index.ts index 45b8142..b67d448 100644 --- a/src/agentkit/server/frontend/src/components/chat/messages/index.ts +++ b/src/agentkit/server/frontend/src/components/chat/messages/index.ts @@ -5,5 +5,9 @@ export { default as TeamPlanCard } from './TeamPlanCard.vue' export { default as BoardBannerCard } from './BoardBannerCard.vue' export { default as BoardRoundCard } from './BoardRoundCard.vue' export { default as BoardConclusionCard } from './BoardConclusionCard.vue' +export { default as DebateBannerCard } from './DebateBannerCard.vue' +export { default as DebateArgumentCard } from './DebateArgumentCard.vue' +export { default as DebateSummaryCard } from './DebateSummaryCard.vue' +export { default as DebateConclusionCard } from './DebateConclusionCard.vue' export { default as ErrorCard } from './ErrorCard.vue' export { default as FileAttachment } from './FileAttachment.vue' diff --git a/src/agentkit/server/frontend/src/stores/chat.ts b/src/agentkit/server/frontend/src/stores/chat.ts index 48db053..5b5b142 100644 --- a/src/agentkit/server/frontend/src/stores/chat.ts +++ b/src/agentkit/server/frontend/src/stores/chat.ts @@ -10,6 +10,10 @@ import type { IChatRequest, WsClientMessage, WsServerMessage, + IDebateStartedData, + IDebateArgumentData, + IDebateRoundSummaryData, + IDebateResolvedData, } from '@/api/types' function generateId(): string { @@ -148,6 +152,15 @@ export const useChatStore = defineStore('chat', () => { const isBoardMode = computed(() => boardState.value !== null && boardState.value.status === 'discussing') + // Debate state (transient, only active during a debate collaboration) + const debateState = ref<{ + topic: string + participants: string[] + current_round: number + max_rounds: number + status: 'debating' | 'resolved' | 'cancelled' + } | null>(null) + // --- Getters --- const currentConversation = computed(() => { return conversations.value.find((c) => c.id === currentConversationId.value) @@ -1199,6 +1212,130 @@ export const useChatStore = defineStore('chat', () => { }, 1000) break } + + // ── Debate events (U5) ────────────────────────────────────────── + + case 'debate_started': { + const d = data.data as IDebateStartedData + debateState.value = { + topic: d.topic, + participants: d.participants, + current_round: 0, + max_rounds: d.max_rounds, + status: 'debating', + } + const sessionId = resolveIncomingConvId() + if (!sessionId) break + appendMessage(sessionId, { + id: generateId(), + role: 'assistant', + content: d.opening, + timestamp: new Date().toISOString(), + status: 'completed', + message_type: 'debate_started', + debate_topic: d.topic, + debate_participants: d.participants, + debate_opening: d.opening, + }) + appendStep({ type: 'team_event', label: '辩论开始', detail: d.topic.slice(0, 40), status: 'success' }, sessionId) + break + } + + case 'expert_argument': { + const d = data.data as IDebateArgumentData + debateState.value = { + ...(debateState.value as NonNullable), + current_round: d.round, + } + const sessionId = resolveIncomingConvId() + if (!sessionId) break + appendMessage(sessionId, { + id: generateId(), + role: 'assistant', + content: d.content, + timestamp: new Date().toISOString(), + status: 'completed', + message_type: 'debate_argument', + expert_name: d.expert_name, + expert_color: d.expert_color, + debate_topic: d.topic, + debate_round: d.round, + }) + appendStep({ + type: 'team_event', + label: d.expert_name, + detail: `辩论第${d.round}轮`, + status: 'success', + }, sessionId) + break + } + + case 'debate_round_summary': { + const d = data.data as IDebateRoundSummaryData + const sessionId = resolveIncomingConvId() + if (!sessionId) break + appendMessage(sessionId, { + id: generateId(), + role: 'assistant', + content: d.content, + timestamp: new Date().toISOString(), + status: 'completed', + message_type: 'debate_summary', + expert_name: d.moderator_name, + debate_round: d.round, + debate_moderator: d.moderator_name, + }) + appendStep({ + type: 'team_event', + label: d.moderator_name, + detail: `第${d.round}轮小结`, + status: 'success', + }, sessionId) + break + } + + case 'debate_resolved': { + const d = data.data as IDebateResolvedData + if (debateState.value) { + debateState.value = { ...debateState.value, status: 'resolved' } + } + const sessionId = resolveIncomingConvId() + if (!sessionId) break + appendMessage(sessionId, { + id: generateId(), + role: 'assistant', + content: d.conclusion, + timestamp: new Date().toISOString(), + status: 'completed', + message_type: 'debate_resolved', + debate_decision: d.decision, + debate_rationale: d.rationale, + }) + appendStep({ + type: 'team_event', + label: '辩论裁决', + detail: d.decision, + status: 'success', + }, sessionId) + // Clear debate state after 1 second (same pattern as board_concluded) + setTimeout(() => { debateState.value = null }, 1000) + break + } + + case 'team_intervention_ack': { + // User intervention was accepted by the server — no message needed, + // just a step entry for visibility. + const d = data.data as { content: string } + const sessionId = resolveIncomingConvId() + if (!sessionId) break + appendStep({ + type: 'team_event', + label: '用户干预', + detail: d.content.slice(0, 40), + status: 'success', + }, sessionId) + break + } } } @@ -1251,6 +1388,7 @@ export const useChatStore = defineStore('chat', () => { pendingConversations, streamingStepsByConv, boardState, + debateState, // Legacy aliases (derive from current conversation for backward compat). // New code should use `isCurrentLoading` / `currentStreamingSteps` instead. isLoading: isCurrentLoading,