import { Component, OnInit, OnDestroy, HostListener, Input, OnChanges, SimpleChanges } 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 { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; 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 { 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 { IndependentEventService } from '../services/independent-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, MatProgressSpinnerModule, MatTooltipModule, ConversationComponent, DocumentListComponent, AIResponseComponent ], templateUrl: './project-tab.component.html', styleUrl: './project-tab.component.scss', }) export class ProjectTabComponent implements OnInit, OnDestroy, OnChanges { @Input() tabId?: string; @Input() standalone = false; @Input() instanceId?: string; @Input() projectId?: string; @Input() independentEventService?: IndependentEventService; project: Session | null = null; effectiveInstanceId: string = ''; // 文档数据 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 sessionService: SessionService, private agentService: AgentService, private mockDataService: MockDataService, private eventService: EventService, private route: ActivatedRoute ) {} ngOnInit() { // 生成或使用传入的实例ID this.updateEffectiveInstanceId(); // 从本地存储恢复宽度 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); } // 监听路由参数变化或使用输入参数 if (this.projectId) { // 使用输入的项目ID this.loadProjectDirectly(this.projectId); } else { // 监听路由参数 this.subscriptions.add( this.route.paramMap.subscribe(params => { const projectId = params.get('projectId'); if (projectId) { this.loadProjectDirectly(projectId); } }) ); } } ngOnChanges(changes: SimpleChanges) { console.log('🔍 [ProjectTabComponent] 输入属性变化:', changes); // 处理instanceId变化 if (changes['instanceId']) { console.log('🔍 [ProjectTabComponent] instanceId变化,更新有效实例ID'); this.updateEffectiveInstanceId(); } if (changes['projectId'] && !changes['projectId'].firstChange) { console.log('🔍 [ProjectTabComponent] 项目ID变化,重新加载项目:', this.projectId); // 清理旧的订阅 this.subscriptions.unsubscribe(); this.subscriptions = new Subscription(); // 取消文档事件订阅 if (this.eventSubscription) { this.eventSubscription.unsubscribe(); this.eventSubscription = undefined; } // 重置项目数据 this.project = null; this.documentSessions = []; // 项目变化时更新实例ID,确保SSE事件路由正确 this.updateEffectiveInstanceId(); // 重新加载项目 if (this.projectId) { this.loadProjectDirectly(this.projectId); } } } private loadProjectDirectly(projectId: string) { console.log('🔍 [ProjectTabComponent] 独立模式加载项目:', projectId); // 直接从会话服务加载项目,不依赖标签页 this.subscriptions.add( this.sessionService.sessions$.subscribe(sessions => { console.log('🔍 [ProjectTabComponent] 收到会话列表,数量:', sessions.length, '查找项目ID:', projectId); // 首先尝试用project_id匹配(项目ID) let session = sessions.find(s => s.project_id === projectId); if (session) { console.log('🔍 [ProjectTabComponent] 通过project_id找到会话:', session.id, '标题:', session.title); } else { // 如果没找到,尝试用id匹配(可能是会话ID) session = sessions.find(s => s.id === projectId); if (session) { console.log('🔍 [ProjectTabComponent] 通过id找到会话:', session.id, '标题:', session.title, 'project_id:', session.project_id); } } if (session && session !== this.project) { console.log('🔍 [ProjectTabComponent] 设置项目会话:', session.id); this.project = session; this.updateEffectiveInstanceId(); // 独立模式下不设置全局活动会话,避免状态污染 // this.sessionService.setActiveSession(session); this.loadDocumentSessions(); } else if (!session) { console.log('🔍 [ProjectTabComponent] 未找到对应会话,等待会话加载'); } }) ); // 初始加载会话列表 this.sessionService.loadSessions(); } 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() !== ''); } /** * 更新有效的实例ID * 优先使用传入的instanceId,其次使用项目会话ID,最后生成一个唯一ID */ private updateEffectiveInstanceId(): void { if (this.instanceId && this.instanceId.trim() !== '') { // 使用传入的instanceId this.effectiveInstanceId = this.instanceId; console.log('🔍 [ProjectTabComponent] 使用传入的instanceId:', this.effectiveInstanceId); } else { // 生成唯一的实例ID作为回退,确保每个标签页有唯一ID this.effectiveInstanceId = `tab-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; console.log('🔍 [ProjectTabComponent] 生成唯一instanceId:', this.effectiveInstanceId); } } ngOnDestroy() { this.subscriptions.unsubscribe(); // 取消文档事件订阅 if (this.eventSubscription) { this.eventSubscription.unsubscribe(); this.eventSubscription = undefined; } } }