| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- import { Component, OnInit, OnDestroy } from '@angular/core';
- import { CommonModule } from '@angular/common';
- import { Router } from '@angular/router';
- import { MatCardModule } from '@angular/material/card';
- import { MatButtonModule } from '@angular/material/button';
- import { MatIconModule } from '@angular/material/icon';
- import { MatChipsModule } from '@angular/material/chips';
- import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
- import { MatTooltipModule } from '@angular/material/tooltip';
- import { Subscription, forkJoin } from 'rxjs';
- import { SessionService } from '../services/session.service';
- import { WindowService } from '../services/window.service';
-
- import { AgentService } from '../services/agent.service';
- import { EventService } from '../services/event.service';
-
- import { MenuService } from '../services/menu.service';
- import { Session } from '../models/session.model';
-
- @Component({
- selector: 'app-home',
- standalone: true,
- imports: [
- CommonModule,
- MatCardModule,
- MatButtonModule,
- MatIconModule,
- MatChipsModule,
- MatProgressSpinnerModule,
- MatTooltipModule
- ],
- templateUrl: './home.component.html',
- styleUrl: './home.component.scss',
- })
- export class HomeComponent implements OnInit, OnDestroy {
- projects: Session[] = [];
- isLoading = false;
- showNewProjectModal = false;
-
- // 跟踪正在加载的项目(project_id集合)
- loadingProjects = new Set<string>();
-
- // 存储项目最新消息的映射(project_id -> 最新消息内容)
- private projectMessages = new Map<string, string>();
-
- private subscriptions: Subscription = new Subscription();
-
- constructor(
- private sessionService: SessionService,
- private windowService: WindowService,
-
- private agentService: AgentService,
- private eventService: EventService,
- private menuService: MenuService,
- private router: Router
- ) {}
-
- ngOnInit() {
- this.loadProjects();
-
- // 订阅会话更新事件,更新项目列表
- this.subscriptions.add(
- this.sessionService.sessions$.subscribe(sessions => {
- this.updateProjects(sessions);
- })
- );
-
- // 订阅事件流,接收实时更新
- this.subscriptions.add(
- this.eventService.allEvents$.subscribe(event => {
- const payload = event.payload;
-
- // 处理会话更新事件,更新对应项目卡片
- if (payload.type === 'session.updated') {
- this.refreshProjects();
- }
-
- // 处理消息更新事件,更新项目卡片中的最新消息预览
- if (payload.type === 'message.updated') {
- this.handleMessageUpdated(event);
- }
-
- // 处理消息部分更新事件(流式输出)
- if (payload.type === 'message.part.updated') {
- this.handleMessagePartUpdated(event);
- }
- })
- );
- }
-
- loadProjects() {
- this.isLoading = true;
- this.sessionService.loadSessions();
- }
-
- updateProjects(sessions: Session[]) {
- // 按project_id去重,获取唯一项目
- const projectMap = new Map<string, Session>();
-
- sessions.forEach(session => {
- const projectId = session.project_id || session.id;
- // 保留最新或第一个会话(简单起见,保留第一个遇到的)
- if (!projectMap.has(projectId)) {
- projectMap.set(projectId, session);
- }
- });
-
- const newProjects = Array.from(projectMap.values());
- this.projects = newProjects;
- this.isLoading = false;
-
- // 清理不再存在的项目的消息缓存
- this.cleanupProjectMessages(newProjects);
- }
-
- // 清理无效项目的消息缓存
- private cleanupProjectMessages(currentProjects: Session[]) {
- const currentProjectIds = new Set(currentProjects.map(p => p.project_id || p.id));
-
- // 删除不再存在的项目的消息
- for (const projectId of this.projectMessages.keys()) {
- if (!currentProjectIds.has(projectId)) {
- this.projectMessages.delete(projectId);
- }
- }
- }
-
- refreshProjects() {
- this.sessionService.loadSessions();
- }
-
- getAgentDisplayName(agentId: string): string {
- return this.agentService.getAgentDisplayName(agentId);
- }
-
- getStatusDisplayName(status: string): string {
- const statusMap: Record<string, string> = {
- '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');
- }
-
- runProject(project: Session) {
- console.log('运行项目:', project.title);
- // TODO: 发送需求文档开始工作
- // 1. 打开项目页签
- this.editProject(project);
- // 2. 发送需求文档到AI(如果存在需求文档)
- alert('运行功能待实现');
- }
-
- // 检查项目是否正在加载
- isProjectLoading(projectId: string): boolean {
- return this.loadingProjects.has(projectId);
- }
-
- editProject(project: Session) {
- const projectId = project.project_id || project.id;
- console.log('🔍 [HomeComponent] 编辑项目:', project.title, '项目ID:', projectId);
-
- // 设置加载状态
- this.loadingProjects.add(projectId);
-
- // 使用 WindowService 打开项目标签页
- const success = this.windowService.openProjectTab(projectId);
-
- if (!success) {
- // WindowService 已处理错误提示,这里只需清理加载状态
- console.error('🔍 [HomeComponent] 打开项目标签页失败');
- }
-
- this.loadingProjects.delete(projectId);
- }
-
-
-
- deleteProject(project: Session) {
- if (confirm(`确定删除项目 "${project.title}" 吗?`)) {
- console.log('删除项目:', project.title);
- // TODO: 调用删除API
- alert('删除功能待实现');
- }
- }
-
- onProjectCreated(session: Session) {
- console.log('新项目创建:', session.title);
- // 刷新项目列表
- this.refreshProjects();
- }
-
- // 处理消息更新事件
- private handleMessageUpdated(event: any) {
- const payload = event.payload;
- const messageInfo = payload.properties?.info;
-
- if (!messageInfo) return;
-
- // 提取会话ID(可能来自不同字段)
- const sessionId = messageInfo.sessionID || messageInfo.sessionId || messageInfo.session_id;
- if (!sessionId) return;
-
- // 查找对应的项目ID(会话ID可能等于项目ID,或需要映射)
- // 这里简单假设会话ID就是项目ID,或者通过projects数组查找
- const project = this.projects.find(p => p.id === sessionId || p.project_id === sessionId);
- if (!project) return;
-
- const projectId = project.project_id || project.id;
- const content = messageInfo.content || '';
-
- // 只存储AI消息(assistant角色)
- if (messageInfo.role === 'assistant' && content.trim()) {
- // 截断过长的消息
- const truncatedContent = content.length > 100 ? content.substring(0, 100) + '...' : content;
- this.projectMessages.set(projectId, truncatedContent);
- console.log(`🔍 [HomeComponent] 更新项目 ${projectId} 的最新消息:`, truncatedContent);
- }
- }
-
- // 处理消息部分更新事件(流式输出)
- private handleMessagePartUpdated(event: any) {
- const payload = event.payload;
- const part = payload.properties?.part;
- const delta = payload.properties?.delta;
-
- if (!part || !delta) return;
-
- // 提取会话ID
- const sessionId = part.sessionID || part.sessionId || part.session_id;
- if (!sessionId) return;
-
- // 查找对应的项目
- const project = this.projects.find(p => p.id === sessionId || p.project_id === sessionId);
- if (!project) return;
-
- const projectId = project.project_id || project.id;
-
- // 只处理文本类型的部分更新
- if (part.type === 'text' || part.type === 'reasoning') {
- // 获取当前消息或初始化
- const currentMessage = this.projectMessages.get(projectId) || '';
- const newMessage = currentMessage + delta;
-
- // 截断过长的消息
- const truncatedMessage = newMessage.length > 100 ? newMessage.substring(0, 100) + '...' : newMessage;
- this.projectMessages.set(projectId, truncatedMessage);
-
- console.log(`🔍 [HomeComponent] 流式更新项目 ${projectId} 的消息:`, truncatedMessage.substring(0, 50));
- }
- }
-
- // 获取项目的最新消息预览
- getProjectLatestMessage(projectId: string): string {
- const message = this.projectMessages.get(projectId);
- if (message) {
- return message;
- }
- // 默认消息
- return '项目已创建,等待需求文档';
- }
-
- ngOnDestroy() {
- this.subscriptions.unsubscribe();
- }
- }
|