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: `
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);
}
}