import { Component, OnInit, OnDestroy, ViewChild, ElementRef, AfterViewChecked } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatCardModule } from '@angular/material/card'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { FormsModule } from '@angular/forms'; import { Subscription } from 'rxjs'; import { ConversationService } from '../services/conversation.service'; import { SessionService } from '../services/session.service'; import { ChatMessage } from '../models/conversation.model'; import { Session } from '../models/session.model'; @Component({ selector: 'app-conversation', standalone: true, imports: [ CommonModule, MatCardModule, MatButtonModule, MatIconModule, MatInputModule, MatFormFieldModule, MatProgressSpinnerModule, FormsModule ], template: `
{{ message.role === 'user' ? 'person' : 'smart_toy' }} {{ message.role === 'user' ? '用户' : 'AI助手' }} {{ message.timestamp | date:'HH:mm' }}
forum

开始对话

选择或创建一个会话来开始对话

输入消息...

请先选择或创建一个会话

`, styles: [` .conversation-container { height: 100%; display: flex; flex-direction: column; min-height: 0; } .messages-container { flex: 1; overflow-y: auto; padding: 20px; background: #fafafa; min-height: 0; } .message-wrapper { margin-bottom: 20px; } .message { max-width: 80%; padding: 12px 16px; border-radius: 18px; position: relative; } .message.user { background: #007bff; color: white; margin-left: auto; border-bottom-right-radius: 4px; } .message.assistant { background: white; color: #333; border: 1px solid #e0e0e0; margin-right: auto; border-bottom-left-radius: 4px; } .message-header { display: flex; align-items: center; margin-bottom: 8px; font-size: 12px; opacity: 0.8; } .message-icon { font-size: 16px; height: 16px; width: 16px; margin-right: 6px; } .message-role { font-weight: 500; margin-right: 8px; } .message-time { margin-left: auto; } .message-content { line-height: 1.5; white-space: pre-wrap; } .user .message-content { color: white; } .loading-spinner { display: inline-block; margin-left: 8px; } .input-container { display: flex; gap: 12px; padding: 16px; border-top: 1px solid #e0e0e0; background: white; flex-shrink: 0; } .input-field { flex: 1; } .send-button { align-self: flex-end; height: 56px; min-width: 56px; } .empty-state { text-align: center; padding: 60px 20px; color: #666; } .empty-state mat-icon { font-size: 64px; height: 64px; width: 64px; margin-bottom: 16px; color: #ccc; } .no-session { padding: 20px; text-align: center; color: #666; background: #f5f5f5; } `] }) export class ConversationComponent implements OnInit, OnDestroy, AfterViewChecked { @ViewChild('messagesContainer') private messagesContainer!: ElementRef; @ViewChild('messageInput') private messageInput!: ElementRef; messages: ChatMessage[] = []; userInput = ''; isLoading = false; activeSession: Session | null = null; private subscriptions: Subscription = new Subscription(); private currentStreamSubscription: Subscription | null = null; private shouldScroll = false; constructor( private conversationService: ConversationService, private sessionService: SessionService ) {} ngOnInit() { // 订阅活动会话变化 this.subscriptions.add( this.sessionService.activeSession$.subscribe(session => { console.log('🔍 [ConversationComponent] 活动会话变化:', session?.id); // 会话切换时取消当前正在进行的流式请求 if (this.activeSession && this.activeSession.id !== session?.id) { console.log('🔍 [ConversationComponent] 会话切换,取消当前流式请求'); this.cancelCurrentStream(); } this.activeSession = session; if (session) { this.loadMessages(session.id); } else { this.messages = []; } }) ); // 订阅新消息 this.subscriptions.add( this.conversationService.newMessage$.subscribe(message => { if (message) { this.messages.push(message); this.shouldScroll = true; } }) ); // 订阅流式更新 this.subscriptions.add( this.conversationService.streamUpdate$.subscribe(update => { if (update.type === 'text') { // 更新最后一条消息的内容 const lastMessage = this.messages[this.messages.length - 1]; if (lastMessage && lastMessage.role === 'assistant') { lastMessage.content += update.data; this.shouldScroll = true; } } else if (update.type === 'done') { // 标记加载完成 const lastMessage = this.messages[this.messages.length - 1]; if (lastMessage) { lastMessage.loading = false; } // 重置加载状态并聚焦输入框 this.isLoading = false; this.focusInput(); } else if (update.type === 'error') { // 处理错误情况 const lastMessage = this.messages[this.messages.length - 1]; if (lastMessage && lastMessage.role === 'assistant') { lastMessage.content += '\n\n[错误: ' + update.data + ']'; lastMessage.loading = false; } // 重置加载状态并聚焦输入框 this.isLoading = false; this.focusInput(); } }) ); } ngAfterViewChecked() { if (this.shouldScroll) { this.scrollToBottom(); this.shouldScroll = false; } } ngOnDestroy() { this.subscriptions.unsubscribe(); this.cancelCurrentStream(); } // 取消当前的流式请求 private cancelCurrentStream() { if (this.currentStreamSubscription) { console.log('🔍 [ConversationComponent] 取消当前的流式请求'); this.currentStreamSubscription.unsubscribe(); this.currentStreamSubscription = null; } // 重置加载状态 if (this.isLoading) { this.isLoading = false; } } loadMessages(sessionId: string) { this.messages = []; // 实际应该从服务加载历史消息 // 暂时为空 } onSendMessage(event: any) { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); this.sendMessage(); } } sendMessage() { if (!this.userInput.trim() || !this.activeSession) return; // 取消之前可能仍在进行的流式请求(允许打断之前的回答) this.cancelCurrentStream(); const userMessage: ChatMessage = { id: Date.now().toString(), role: 'user', content: this.userInput, timestamp: new Date(), sessionID: this.activeSession.id }; // 添加用户消息 this.messages.push(userMessage); this.shouldScroll = true; // 添加AI响应占位 const aiMessage: ChatMessage = { id: (Date.now() + 1).toString(), role: 'assistant', content: '', timestamp: new Date(), sessionID: this.activeSession.id, loading: true }; this.messages.push(aiMessage); this.shouldScroll = true; // 发送到服务 this.isLoading = true; this.currentStreamSubscription = this.conversationService.sendMessage( this.activeSession.id, this.userInput ).subscribe({ next: () => { // 流式连接已建立,但传输仍在继续 // isLoading状态将在流式完成时在streamUpdate$中重置 }, error: (error) => { console.error('发送消息失败:', error); aiMessage.content = '抱歉,发送消息时出现错误: ' + error.message; aiMessage.loading = false; this.isLoading = false; this.currentStreamSubscription = null; this.focusInput(); }, complete: () => { // 流式完成已在streamUpdate$中处理 this.currentStreamSubscription = null; } }); // 清空输入框 this.userInput = ''; // 聚焦输入框 this.focusInput(); } formatContent(content: string): string { // 简单的格式化,可扩展为Markdown渲染 return content.replace(/\n/g, '
'); } private scrollToBottom() { try { this.messagesContainer.nativeElement.scrollTop = this.messagesContainer.nativeElement.scrollHeight; } catch (err) { console.error('滚动失败:', err); } } private focusInput() { // 延迟聚焦以确保DOM已更新 setTimeout(() => { if (this.messageInput?.nativeElement) { this.messageInput.nativeElement.focus(); } }, 100); } }