Sin descripción
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

home.component.ts 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. import { Component, OnInit, OnDestroy } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. import { Router } from '@angular/router';
  4. import { MatCardModule } from '@angular/material/card';
  5. import { MatButtonModule } from '@angular/material/button';
  6. import { MatIconModule } from '@angular/material/icon';
  7. import { MatChipsModule } from '@angular/material/chips';
  8. import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
  9. import { MatTooltipModule } from '@angular/material/tooltip';
  10. import { Subscription, forkJoin } from 'rxjs';
  11. import { SessionService } from '../services/session.service';
  12. import { WindowService } from '../services/window.service';
  13. import { AgentService } from '../services/agent.service';
  14. import { EventService } from '../services/event.service';
  15. import { MenuService } from '../services/menu.service';
  16. import { Session } from '../models/session.model';
  17. @Component({
  18. selector: 'app-home',
  19. standalone: true,
  20. imports: [
  21. CommonModule,
  22. MatCardModule,
  23. MatButtonModule,
  24. MatIconModule,
  25. MatChipsModule,
  26. MatProgressSpinnerModule,
  27. MatTooltipModule
  28. ],
  29. templateUrl: './home.component.html',
  30. styleUrl: './home.component.scss',
  31. })
  32. export class HomeComponent implements OnInit, OnDestroy {
  33. projects: Session[] = [];
  34. isLoading = false;
  35. showNewProjectModal = false;
  36. // 跟踪正在加载的项目(project_id集合)
  37. loadingProjects = new Set<string>();
  38. // 存储项目最新消息的映射(project_id -> 最新消息内容)
  39. private projectMessages = new Map<string, string>();
  40. private subscriptions: Subscription = new Subscription();
  41. constructor(
  42. private sessionService: SessionService,
  43. private windowService: WindowService,
  44. private agentService: AgentService,
  45. private eventService: EventService,
  46. private menuService: MenuService,
  47. private router: Router
  48. ) {}
  49. ngOnInit() {
  50. this.loadProjects();
  51. // 订阅会话更新事件,更新项目列表
  52. this.subscriptions.add(
  53. this.sessionService.sessions$.subscribe(sessions => {
  54. this.updateProjects(sessions);
  55. })
  56. );
  57. // 订阅事件流,接收实时更新
  58. this.subscriptions.add(
  59. this.eventService.allEvents$.subscribe(event => {
  60. const payload = event.payload;
  61. // 处理会话更新事件,更新对应项目卡片
  62. if (payload.type === 'session.updated') {
  63. this.refreshProjects();
  64. }
  65. // 处理消息更新事件,更新项目卡片中的最新消息预览
  66. if (payload.type === 'message.updated') {
  67. this.handleMessageUpdated(event);
  68. }
  69. // 处理消息部分更新事件(流式输出)
  70. if (payload.type === 'message.part.updated') {
  71. this.handleMessagePartUpdated(event);
  72. }
  73. })
  74. );
  75. }
  76. loadProjects() {
  77. this.isLoading = true;
  78. this.sessionService.loadSessions();
  79. }
  80. updateProjects(sessions: Session[]) {
  81. // 按project_id去重,获取唯一项目
  82. const projectMap = new Map<string, Session>();
  83. sessions.forEach(session => {
  84. const projectId = session.project_id || session.id;
  85. // 保留最新或第一个会话(简单起见,保留第一个遇到的)
  86. if (!projectMap.has(projectId)) {
  87. projectMap.set(projectId, session);
  88. }
  89. });
  90. const newProjects = Array.from(projectMap.values());
  91. this.projects = newProjects;
  92. this.isLoading = false;
  93. // 清理不再存在的项目的消息缓存
  94. this.cleanupProjectMessages(newProjects);
  95. }
  96. // 清理无效项目的消息缓存
  97. private cleanupProjectMessages(currentProjects: Session[]) {
  98. const currentProjectIds = new Set(currentProjects.map(p => p.project_id || p.id));
  99. // 删除不再存在的项目的消息
  100. for (const projectId of this.projectMessages.keys()) {
  101. if (!currentProjectIds.has(projectId)) {
  102. this.projectMessages.delete(projectId);
  103. }
  104. }
  105. }
  106. refreshProjects() {
  107. this.sessionService.loadSessions();
  108. }
  109. getAgentDisplayName(agentId: string): string {
  110. return this.agentService.getAgentDisplayName(agentId);
  111. }
  112. getStatusDisplayName(status: string): string {
  113. const statusMap: Record<string, string> = {
  114. 'requirement_document': '需求文档',
  115. 'technical_document': '技术文档',
  116. 'code': '代码开发',
  117. 'test': '测试',
  118. 'release': '发布'
  119. };
  120. return statusMap[status] || status;
  121. }
  122. getStatusClass(status: string): string {
  123. return `status-${status}`;
  124. }
  125. formatDate(dateString?: string): string {
  126. if (!dateString) return '未知';
  127. const date = new Date(dateString);
  128. return date.toLocaleDateString('zh-CN');
  129. }
  130. runProject(project: Session) {
  131. console.log('运行项目:', project.title);
  132. // TODO: 发送需求文档开始工作
  133. // 1. 打开项目页签
  134. this.editProject(project);
  135. // 2. 发送需求文档到AI(如果存在需求文档)
  136. alert('运行功能待实现');
  137. }
  138. // 检查项目是否正在加载
  139. isProjectLoading(projectId: string): boolean {
  140. return this.loadingProjects.has(projectId);
  141. }
  142. editProject(project: Session) {
  143. const projectId = project.project_id || project.id;
  144. console.log('🔍 [HomeComponent] 编辑项目:', project.title, '项目ID:', projectId);
  145. // 设置加载状态
  146. this.loadingProjects.add(projectId);
  147. // 使用 WindowService 打开项目标签页
  148. const success = this.windowService.openProjectTab(projectId);
  149. if (!success) {
  150. // WindowService 已处理错误提示,这里只需清理加载状态
  151. console.error('🔍 [HomeComponent] 打开项目标签页失败');
  152. }
  153. this.loadingProjects.delete(projectId);
  154. }
  155. deleteProject(project: Session) {
  156. if (confirm(`确定删除项目 "${project.title}" 吗?`)) {
  157. console.log('删除项目:', project.title);
  158. // TODO: 调用删除API
  159. alert('删除功能待实现');
  160. }
  161. }
  162. onProjectCreated(session: Session) {
  163. console.log('新项目创建:', session.title);
  164. // 刷新项目列表
  165. this.refreshProjects();
  166. }
  167. // 处理消息更新事件
  168. private handleMessageUpdated(event: any) {
  169. const payload = event.payload;
  170. const messageInfo = payload.properties?.info;
  171. if (!messageInfo) return;
  172. // 提取会话ID(可能来自不同字段)
  173. const sessionId = messageInfo.sessionID || messageInfo.sessionId || messageInfo.session_id;
  174. if (!sessionId) return;
  175. // 查找对应的项目ID(会话ID可能等于项目ID,或需要映射)
  176. // 这里简单假设会话ID就是项目ID,或者通过projects数组查找
  177. const project = this.projects.find(p => p.id === sessionId || p.project_id === sessionId);
  178. if (!project) return;
  179. const projectId = project.project_id || project.id;
  180. const content = messageInfo.content || '';
  181. // 只存储AI消息(assistant角色)
  182. if (messageInfo.role === 'assistant' && content.trim()) {
  183. // 截断过长的消息
  184. const truncatedContent = content.length > 100 ? content.substring(0, 100) + '...' : content;
  185. this.projectMessages.set(projectId, truncatedContent);
  186. console.log(`🔍 [HomeComponent] 更新项目 ${projectId} 的最新消息:`, truncatedContent);
  187. }
  188. }
  189. // 处理消息部分更新事件(流式输出)
  190. private handleMessagePartUpdated(event: any) {
  191. const payload = event.payload;
  192. const part = payload.properties?.part;
  193. const delta = payload.properties?.delta;
  194. if (!part || !delta) return;
  195. // 提取会话ID
  196. const sessionId = part.sessionID || part.sessionId || part.session_id;
  197. if (!sessionId) return;
  198. // 查找对应的项目
  199. const project = this.projects.find(p => p.id === sessionId || p.project_id === sessionId);
  200. if (!project) return;
  201. const projectId = project.project_id || project.id;
  202. // 只处理文本类型的部分更新
  203. if (part.type === 'text' || part.type === 'reasoning') {
  204. // 获取当前消息或初始化
  205. const currentMessage = this.projectMessages.get(projectId) || '';
  206. const newMessage = currentMessage + delta;
  207. // 截断过长的消息
  208. const truncatedMessage = newMessage.length > 100 ? newMessage.substring(0, 100) + '...' : newMessage;
  209. this.projectMessages.set(projectId, truncatedMessage);
  210. console.log(`🔍 [HomeComponent] 流式更新项目 ${projectId} 的消息:`, truncatedMessage.substring(0, 50));
  211. }
  212. }
  213. // 获取项目的最新消息预览
  214. getProjectLatestMessage(projectId: string): string {
  215. const message = this.projectMessages.get(projectId);
  216. if (message) {
  217. return message;
  218. }
  219. // 默认消息
  220. return '项目已创建,等待需求文档';
  221. }
  222. ngOnDestroy() {
  223. this.subscriptions.unsubscribe();
  224. }
  225. }