import { Component, OnInit, OnDestroy, HostListener, Input } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ActivatedRoute } from '@angular/router'; import { MatIcon } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatListModule } from '@angular/material/list'; import { MatExpansionModule } from '@angular/material/expansion'; import { MatChipsModule } from '@angular/material/chips'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatTooltipModule } from '@angular/material/tooltip'; import { Subscription } from 'rxjs'; import { ConversationComponent } from './conversation.component'; import { DocumentListComponent } from './document-list.component'; import { AIResponseComponent } from './ai-response.component'; import { TabService, Tab } from '../services/tab.service'; import { SessionService } from '../services/session.service'; import { AgentService } from '../services/agent.service'; import { MockDataService } from '../services/mock-data.service'; import { EventService } from '../services/event.service'; import { Session, SessionStatus } from '../models/session.model'; import { DocumentType, DocumentSession } from '../models/document.model'; @Component({ selector: 'app-project-tab', standalone: true, imports: [ CommonModule, MatIcon, MatButtonModule, MatCardModule, MatListModule, MatExpansionModule, MatChipsModule, MatProgressBarModule, MatTooltipModule, ConversationComponent, DocumentListComponent, AIResponseComponent ], templateUrl: './project-tab.component.html', styleUrl: './project-tab.component.scss', }) export class ProjectTabComponent implements OnInit, OnDestroy { @Input() tabId?: string; project: Session | null = null; activeTab: Tab | null = null; // 文档数据 documentSessions: DocumentSession[] = []; leftWidth = 320; middleWidth = 500; isDragging = false; isDraggingMiddle = false; minLeftWidth = 280; maxLeftWidth = 500; minMiddleWidth = 400; maxMiddleWidth = 800; private subscriptions: Subscription = new Subscription(); private eventSubscription?: Subscription; constructor( private tabService: TabService, private sessionService: SessionService, private agentService: AgentService, private mockDataService: MockDataService, private eventService: EventService, private route: ActivatedRoute ) {} ngOnInit() { // 从本地存储恢复宽度 const savedLeftWidth = localStorage.getItem('project_tab_leftWidth'); if (savedLeftWidth) { this.leftWidth = parseInt(savedLeftWidth, 10); } const savedMiddleWidth = localStorage.getItem('project_tab_middleWidth'); if (savedMiddleWidth) { this.middleWidth = parseInt(savedMiddleWidth, 10); } // 监听路由参数变化 this.subscriptions.add( this.route.paramMap.subscribe(params => { const projectId = params.get('projectId'); if (projectId) { this.loadTabByProjectId(projectId); } }) ); // 获取活动页签 this.subscriptions.add( this.tabService.activeTab$.subscribe(tab => { this.activeTab = tab || null; if (tab) { this.project = tab.session; // 设置活动会话 this.sessionService.setActiveSession(this.project); } else { this.project = null; } }) ); // 如果指定了tabId,直接获取 if (this.tabId) { const tab = this.tabService.getTabById(this.tabId); if (tab) { this.activeTab = tab; this.project = tab.session; this.sessionService.setActiveSession(this.project); this.loadDocumentSessions(); } } // 加载文档数据 this.loadDocumentSessions(); } private loadTabByProjectId(projectId: string) { const tab = this.tabService.getTabByProjectId(projectId); if (tab) { this.activeTab = tab; this.project = tab.session; this.sessionService.setActiveSession(this.project); } else { console.warn('找不到项目ID对应的标签页:', projectId); // 可能标签页已被关闭,尝试从会话服务加载会话 this.subscriptions.add( this.sessionService.sessions$.subscribe(sessions => { const session = sessions.find(s => s.project_id === projectId || s.id === projectId); if (session) { // 创建临时标签页 this.project = session; this.sessionService.setActiveSession(session); } }) ); } } getAgentDisplayName(agentId: string): string { return this.agentService.getAgentDisplayName(agentId); } getStatusDisplayName(status: string): string { const statusMap: Record = { 'requirement_document': '需求文档', 'technical_document': '技术文档', 'code': '代码开发', 'test': '测试', 'release': '发布' }; return statusMap[status] || status; } getStatusClass(status: string): string { return `status-${status}`; } formatDate(dateString?: string): string { if (!dateString) return '未知'; const date = new Date(dateString); return date.toLocaleDateString('zh-CN'); } getProgressValue(status: string): number { const progressMap: Record = { 'requirement_document': 20, 'technical_document': 40, 'code': 60, 'test': 80, 'release': 100 }; return progressMap[status] || 0; } getProgressText(status: string): string { const progressMap: Record = { 'requirement_document': '20% - 需求分析', 'technical_document': '40% - 技术设计', 'code': '60% - 代码开发', 'test': '80% - 测试验证', 'release': '100% - 发布完成' }; return progressMap[status] || '0% - 未开始'; } focusRequirementInput() { // TODO: 聚焦到对话输入框,并提示输入需求 console.log('聚焦需求输入'); // 可以通过服务或事件通知ConversationComponent } startDrag(event: MouseEvent) { event.preventDefault(); this.isDragging = true; document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none'; } startMiddleDrag(event: MouseEvent) { event.preventDefault(); this.isDraggingMiddle = true; document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none'; } @HostListener('document:mousemove', ['$event']) onDrag(event: MouseEvent) { if (this.isDragging) { const newWidth = event.clientX; if (newWidth >= this.minLeftWidth && newWidth <= this.maxLeftWidth) { this.leftWidth = newWidth; } } if (this.isDraggingMiddle) { const newWidth = event.clientX - this.leftWidth - 8; if (newWidth >= this.minMiddleWidth && newWidth <= this.maxMiddleWidth) { this.middleWidth = newWidth; } } } @HostListener('document:mouseup') stopDrag() { if (this.isDragging) { this.isDragging = false; localStorage.setItem('project_tab_leftWidth', this.leftWidth.toString()); } if (this.isDraggingMiddle) { this.isDraggingMiddle = false; localStorage.setItem('project_tab_middleWidth', this.middleWidth.toString()); } document.body.style.cursor = ''; document.body.style.userSelect = ''; } /** * 加载文档会话数据 */ private loadDocumentSessions() { if (!this.project) return; const projectId = this.project.project_id || this.project.id; this.mockDataService.getProjectDocuments(projectId).subscribe({ next: (documents) => { this.documentSessions = documents; // 设置文档会话的事件订阅 this.setupDocumentEventSubscriptions(); }, error: (error) => { console.error('加载文档数据失败:', error); // 使用空文档作为回退 import('../models/document.model').then(module => { this.documentSessions = module.createEmptyDocumentSessions(projectId); // 即使使用空文档,也尝试设置事件订阅 this.setupDocumentEventSubscriptions(); }); } }); } /** * 设置文档会话的事件订阅 */ private setupDocumentEventSubscriptions() { // 取消现有的事件订阅 if (this.eventSubscription) { this.eventSubscription.unsubscribe(); this.eventSubscription = undefined; } // 获取有效的文档会话ID const sessionIds = this.getDocumentSessionIds(); if (sessionIds.length === 0) { console.log('没有有效的文档会话ID可订阅'); return; } console.log(`设置文档事件订阅,会话ID:`, sessionIds); // 连接到多会话事件流 this.eventService.connectToMultipleSessions(sessionIds); // 订阅多会话事件 this.eventSubscription = this.eventService.multiSessionEvents$.subscribe({ next: ({ sessionId, event }) => { this.handleDocumentEvent(sessionId, event); }, error: (error) => { console.error('文档事件订阅错误:', error); } }); } /** * 处理文档事件,更新文档内容 */ private handleDocumentEvent(sessionId: string, event: any) { // 找到对应的文档 const docIndex = this.documentSessions.findIndex(doc => doc.sessionId === sessionId); if (docIndex === -1) { // 未找到对应文档,可能是其他事件 return; } const payload = event.payload; console.log(`处理文档事件,会话ID: ${sessionId}, 类型: ${payload.type}`); // 提取内容 let content = ''; const properties = payload.properties || {}; if (payload.type === 'message.updated') { // 处理消息更新事件 const messageInfo = properties.info || {}; if (messageInfo.content) { content = messageInfo.content; } } else if (properties.content) { content = properties.content; } else if (properties.message) { content = properties.message; } else if (properties.info && properties.info.content) { content = properties.info.content; } if (content) { // 更新文档内容 this.documentSessions[docIndex] = { ...this.documentSessions[docIndex], content: content, lastUpdated: new Date(), hasContent: true, isLoading: false }; // 触发变更检测(使用展开操作符创建新数组) this.documentSessions = [...this.documentSessions]; console.log(`文档 ${this.documentSessions[docIndex].title} 内容已更新,长度: ${content.length} 字符`); } } /** * 获取所有文档会话ID(过滤空值) */ getDocumentSessionIds(): string[] { return this.documentSessions .map(doc => doc.sessionId) .filter(sessionId => sessionId && sessionId.trim() !== ''); } ngOnDestroy() { this.subscriptions.unsubscribe(); // 取消文档事件订阅 if (this.eventSubscription) { this.eventSubscription.unsubscribe(); this.eventSubscription = undefined; } } }