Просмотр исходного кода

页签切换可以了-优化过一次

qdy 2 недель назад
Родитель
Сommit
93181ed760

+ 4
- 0
src/app/app.component.html Просмотреть файл

@@ -28,9 +28,13 @@
28 28
           @for (tab of (tabs$ | async); track tab.id) {
29 29
             <div class="tab-item" 
30 30
                  [class.active]="(activeTabId$ | async) === tab.id"
31
+                 [class.loading]="loadingTabId === tab.id"
31 32
                  (click)="openProjectTab(tab.id)">
32 33
               <mat-icon class="tab-icon">folder</mat-icon>
33 34
               <span class="tab-title">{{ tab.title || '未命名项目' }}</span>
35
+              @if (loadingTabId === tab.id) {
36
+                <mat-spinner diameter="16" class="tab-loading-spinner"></mat-spinner>
37
+              }
34 38
               <button mat-icon-button 
35 39
                       class="tab-close"
36 40
                       (click)="closeProjectTab(tab.id, $event)">

+ 23
- 0
src/app/app.component.scss Просмотреть файл

@@ -366,4 +366,27 @@
366 366
       color: white;
367 367
     }
368 368
   }
369
+  
370
+  &.loading {
371
+    cursor: wait;
372
+    opacity: 0.7;
373
+    background: #f0f9ff;
374
+    border-color: #93c5fd;
375
+    
376
+    .tab-title {
377
+      margin-right: 4px;
378
+    }
379
+    
380
+    .tab-loading-spinner {
381
+      margin-left: 4px;
382
+      
383
+      ::ng-deep .mat-progress-spinner circle, .mat-spinner circle {
384
+        stroke: #3b82f6;
385
+      }
386
+    }
387
+    
388
+    .tab-close {
389
+      display: none;
390
+    }
391
+  }
369 392
 }

+ 20
- 28
src/app/app.component.ts Просмотреть файл

@@ -29,6 +29,7 @@ export class AppComponent implements OnInit, OnDestroy {
29 29
   tabs$: Observable<any>;
30 30
   activeTabId$: Observable<string | null>;
31 31
   showNewProjectModal = false;
32
+  loadingTabId: string | null = null; // 正在加载的页签ID
32 33
   
33 34
   private subscriptions: Subscription = new Subscription();
34 35
   isEventStreamConnected = false;
@@ -185,43 +186,34 @@ export class AppComponent implements OnInit, OnDestroy {
185 186
 
186 187
   // 打开项目标签页 - 导航到实例页面
187 188
   openProjectTab(projectId: string) {
189
+    console.time(`[页签切换-总耗时] ${projectId}`);
190
+    console.log('📊 [页签切换] 开始切换页签:', projectId);
191
+    
192
+    // 设置加载状态
193
+    this.loadingTabId = projectId;
194
+    
188 195
     const tab = this.tabService.getTabByProjectId(projectId);
189 196
     if (!tab) {
190 197
       console.error('找不到对应的标签页:', projectId);
191 198
       // 回退到项目页面
192 199
       this.router.navigate(['/project', projectId]);
193 200
       this.tabService.setActiveTab(projectId);
201
+      this.loadingTabId = null;
202
+      console.timeEnd(`[页签切换-总耗时] ${projectId}`);
194 203
       return;
195 204
     }
196 205
 
197
-    // 获取会话的菜单项ID
198
-    this.menuService.getMenuItemBySessionId(tab.session.id).subscribe({
199
-      next: (menuItemId) => {
200
-        console.log('找到菜单项ID:', menuItemId, '为会话:', tab.session.id);
201
-        // 创建或获取实例
202
-        this.instanceService.createInstance({ menuItemId, title: tab.session.title }).subscribe({
203
-          next: (instance) => {
204
-            console.log('实例创建/获取成功:', instance.id);
205
-            // 确保当前会话ID添加到实例中(用于标签页同步)
206
-            this.instanceService.addSessionToInstance(instance.id, tab.session.id);
207
-            // 导航到实例页面
208
-            this.router.navigate(['/instance', instance.id]);
209
-            this.tabService.setActiveTab(projectId);
210
-          },
211
-          error: (error) => {
212
-            console.error('创建实例失败:', error);
213
-            // 回退到项目页面
214
-            this.router.navigate(['/project', projectId]);
215
-            this.tabService.setActiveTab(projectId);
216
-          }
217
-        });
218
-      },
219
-      error: (error) => {
220
-        console.error('获取菜单项失败:', error);
221
-        // 回退到项目页面
222
-        this.router.navigate(['/project', projectId]);
223
-        this.tabService.setActiveTab(projectId);
224
-      }
206
+    // 注释:跳过菜单项查询,直接导航到项目页面
207
+    console.log('📊 [页签切换] 跳过菜单项查询,直接导航到项目页面');
208
+    
209
+    // 直接导航到项目页面
210
+    console.time(`[页签切换-导航] ${projectId}`);
211
+    this.router.navigate(['/project', projectId]).then(() => {
212
+      console.timeEnd(`[页签切换-导航] ${projectId}`);
213
+      this.tabService.setActiveTab(projectId);
214
+      this.loadingTabId = null;
215
+      console.timeEnd(`[页签切换-总耗时] ${projectId}`);
216
+      console.log('📊 [页签切换] 切换完成,页面已导航');
225 217
     });
226 218
   }
227 219
 

+ 110
- 22
src/app/components/conversation.component.ts Просмотреть файл

@@ -7,7 +7,8 @@ import { MatInputModule } from '@angular/material/input';
7 7
 import { MatFormFieldModule } from '@angular/material/form-field';
8 8
 import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
9 9
 import { FormsModule } from '@angular/forms';
10
-import { Subscription } from 'rxjs';
10
+import { Subject, Subscription, of } from 'rxjs';
11
+import { distinctUntilChanged, switchMap, takeUntil, tap, catchError } from 'rxjs/operators';
11 12
 import { ConversationService } from '../services/conversation.service';
12 13
 import { SessionService } from '../services/session.service';
13 14
 import { EventService } from '../services/event.service';
@@ -48,6 +49,12 @@ export class ConversationComponent implements OnInit, OnDestroy, AfterViewChecke
48 49
   private shouldScroll = false;
49 50
   private isInThinking = false; // 是否正在思考过程中
50 51
   private thinkingStartIndex = 0; // 思考内容的起始位置(在消息内容中的索引)
52
+  
53
+  // RxJS Subjects for reactive session management
54
+  private destroy$ = new Subject<void>();
55
+  private sessionChanged$ = new Subject<string>();
56
+  private currentLoadingSession: string | null = null;
57
+  private isLoadingMessages = false;
51 58
 
52 59
   constructor(
53 60
     private conversationService: ConversationService,
@@ -56,9 +63,31 @@ export class ConversationComponent implements OnInit, OnDestroy, AfterViewChecke
56 63
   ) {}
57 64
 
58 65
   ngOnInit() {
59
-    // 订阅活动会话变化
66
+    console.log('🔍 [ConversationComponent] 初始化,instanceId:', this.instanceId);
67
+    
68
+    // 立即检查当前是否有活动会话(处理组件在会话设置后初始化的情况)
69
+    const currentSession = this.sessionService.getActiveSession();
70
+    if (currentSession) {
71
+      console.log('🔍 [ConversationComponent] 初始化时已有活动会话:', currentSession.id);
72
+      this.activeSession = currentSession;
73
+      this.loadMessages(currentSession.id);
74
+      if (this.instanceId) {
75
+        this.eventService.registerSessionInstanceMapping(currentSession.id, this.instanceId);
76
+      }
77
+      this.subscribeToSessionEvents(currentSession.id);
78
+    }
79
+    
80
+    // 订阅活动会话变化 - 使用响应式管道避免重复加载
60 81
     this.subscriptions.add(
61
-      this.sessionService.activeSession$.subscribe(session => {
82
+      this.sessionService.activeSession$.pipe(
83
+        takeUntil(this.destroy$),
84
+        // 避免相同会话重复触发
85
+        distinctUntilChanged((prev, curr) => {
86
+          const prevId = prev?.id;
87
+          const currId = curr?.id;
88
+          return prevId === currId;
89
+        })
90
+      ).subscribe(session => {
62 91
         console.log('🔍 [ConversationComponent] 活动会话变化:', session?.id);
63 92
         
64 93
         // 会话切换时取消当前正在进行的流式请求
@@ -69,6 +98,7 @@ export class ConversationComponent implements OnInit, OnDestroy, AfterViewChecke
69 98
         
70 99
         this.activeSession = session;
71 100
         if (session) {
101
+          console.log('🔍 [ConversationComponent] 加载会话消息:', session.id);
72 102
           this.loadMessages(session.id);
73 103
           // 如果实例ID存在,注册会话-实例映射
74 104
           if (this.instanceId) {
@@ -77,6 +107,7 @@ export class ConversationComponent implements OnInit, OnDestroy, AfterViewChecke
77 107
           // 订阅该会话的事件
78 108
           this.subscribeToSessionEvents(session.id);
79 109
         } else {
110
+          console.log('🔍 [ConversationComponent] 无活动会话,清空消息');
80 111
           this.messages = [];
81 112
           // 取消会话事件订阅
82 113
           this.unsubscribeFromSessionEvents();
@@ -260,6 +291,17 @@ export class ConversationComponent implements OnInit, OnDestroy, AfterViewChecke
260 291
   ngOnDestroy() {
261 292
     this.subscriptions.unsubscribe();
262 293
     this.cancelCurrentStream();
294
+    
295
+    // 清理消息加载订阅
296
+    if (this.loadMessagesSubscription) {
297
+      this.loadMessagesSubscription.unsubscribe();
298
+      this.loadMessagesSubscription = null;
299
+    }
300
+    
301
+    // 清理RxJS Subjects
302
+    this.destroy$.next();
303
+    this.destroy$.complete();
304
+    this.sessionChanged$.complete();
263 305
   }
264 306
 
265 307
   // 取消当前的流式请求
@@ -289,30 +331,76 @@ export class ConversationComponent implements OnInit, OnDestroy, AfterViewChecke
289 331
     this.isInThinking = false;
290 332
     this.thinkingStartIndex = 0;
291 333
   }
334
+  
335
+  // 安全的计时器函数,避免重复计时器错误
336
+  private safeTimeStart(label: string) {
337
+    try { 
338
+      console.timeEnd(label); 
339
+    } catch(e) {
340
+      // 忽略计时器不存在的错误
341
+    }
342
+    console.time(label);
343
+  }
344
+  
345
+  private safeTimeEnd(label: string) {
346
+    try { 
347
+      console.timeEnd(label); 
348
+    } catch(e) {
349
+      // 忽略计时器不存在的错误
350
+    }
351
+  }
292 352
 
353
+  private loadMessagesSubscription: Subscription | null = null;
354
+  
293 355
   loadMessages(sessionId: string) {
294
-    console.log('🔍 [ConversationComponent] 加载会话消息,sessionId:', sessionId);
356
+    console.log('🔍 [ConversationComponent] loadMessages 被调用,sessionId:', sessionId);
357
+    
358
+    // 取消之前的加载请求
359
+    if (this.loadMessagesSubscription) {
360
+      console.log('🔍 [ConversationComponent] 取消之前的消息加载请求');
361
+      this.loadMessagesSubscription.unsubscribe();
362
+      this.loadMessagesSubscription = null;
363
+    }
364
+    
365
+    // 安全地开始计时器
366
+    this.safeTimeStart(`[对话消息-加载] ${sessionId}`);
367
+    console.log('📊 [对话消息] 开始加载会话消息,sessionId:', sessionId);
368
+    
295 369
     this.messages = [];
296 370
     
297 371
     // 从服务加载历史消息
298
-    this.subscriptions.add(
299
-      this.conversationService.getHistory(sessionId, 50).subscribe({
300
-        next: (historyMessages) => {
301
-          console.log('🔍 [ConversationComponent] 收到历史消息数量:', historyMessages.length);
302
-          this.messages = historyMessages;
303
-          
304
-          // 按时间排序(最早的在前)
305
-          this.messages.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
306
-          
307
-          this.shouldScroll = true;
308
-        },
309
-        error: (error) => {
310
-          console.error('🔍 [ConversationComponent] 加载历史消息失败:', error);
311
-          // 历史消息加载失败,但页面仍然可以正常工作
312
-          // 用户仍然可以发送新消息
313
-        }
314
-      })
315
-    );
372
+    this.loadMessagesSubscription = this.conversationService.getHistory(sessionId, 50).subscribe({
373
+      next: (historyMessages) => {
374
+        this.safeTimeEnd(`[对话消息-加载] ${sessionId}`);
375
+        console.log('📊 [对话消息] 收到历史消息数量:', historyMessages.length);
376
+        
377
+        // 开始消息处理和渲染计时
378
+        this.safeTimeStart(`[对话消息-处理] ${sessionId}`);
379
+        
380
+        this.messages = historyMessages;
381
+        
382
+        // 按时间排序(最早的在前)
383
+        this.messages.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
384
+        
385
+        this.shouldScroll = true;
386
+        
387
+        // 使用setTimeout估算渲染完成时间
388
+        setTimeout(() => {
389
+          this.safeTimeEnd(`[对话消息-处理] ${sessionId}`);
390
+          console.log('📊 [对话消息] 消息处理和渲染完成,会话:', sessionId);
391
+        }, 0);
392
+        
393
+        // 清理订阅引用
394
+        this.loadMessagesSubscription = null;
395
+      },
396
+      error: (error) => {
397
+        this.safeTimeEnd(`[对话消息-加载] ${sessionId}`);
398
+        console.error('📊 [对话消息] 加载历史消息失败:', error);
399
+        // 历史消息加载失败,但页面仍然可以正常工作
400
+        // 用户仍然可以发送新消息
401
+        this.loadMessagesSubscription = null;
402
+      }
403
+    });
316 404
   }
317 405
 
318 406
   // 订阅会话事件

Загрузка…
Отмена
Сохранить