diff --git a/backend/ai/migrations/0004_alter_chatconversation_model_id_and_more.py b/backend/ai/migrations/0004_alter_chatconversation_model_id_and_more.py new file mode 100644 index 0000000..5ec6358 --- /dev/null +++ b/backend/ai/migrations/0004_alter_chatconversation_model_id_and_more.py @@ -0,0 +1,40 @@ +# Generated by Django 5.2.1 on 2025-07-17 07:07 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("ai", "0003_aimodel_model_type"), + ] + + operations = [ + migrations.AlterField( + model_name="chatconversation", + name="model_id", + field=models.ForeignKey( + blank=True, + db_column="model_id", + db_comment="向量模型编号", + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="ai.aimodel", + verbose_name="向量模型编号", + ), + ), + migrations.AlterField( + model_name="chatmessage", + name="model_id", + field=models.ForeignKey( + blank=True, + db_column="model_id", + db_comment="向量模型编号", + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="ai.aimodel", + verbose_name="向量模型编号", + ), + ), + ] diff --git a/backend/ai/models.py b/backend/ai/models.py index 10f94b6..3f4266a 100644 --- a/backend/ai/models.py +++ b/backend/ai/models.py @@ -258,6 +258,7 @@ class ChatConversation(CoreModel): model_id = models.ForeignKey( 'AIModel', on_delete=models.CASCADE, + null=True, blank=True, db_column='model_id', verbose_name="向量模型编号", db_comment='向量模型编号' @@ -302,6 +303,7 @@ class ChatMessage(CoreModel): model_id = models.ForeignKey( 'AIModel', on_delete=models.CASCADE, + null=True, blank=True, db_column='model_id', verbose_name="向量模型编号", db_comment='向量模型编号' diff --git a/chat/api/v1/ai_chat.py b/chat/api/v1/ai_chat.py index 8113dda..45a6c0c 100644 --- a/chat/api/v1/ai_chat.py +++ b/chat/api/v1/ai_chat.py @@ -3,16 +3,15 @@ import asyncio from fastapi import APIRouter, Depends, Request from fastapi.responses import StreamingResponse +from sqlalchemy.orm import Session from pydantic import BaseModel -from langchain.memory import ConversationBufferMemory from langchain.chains import ConversationChain -# from langchain.chat_models import ChatOpenAI from langchain_community.chat_models import ChatOpenAI from deps.auth import get_current_user -from services.chat_service import chat_service -from utils.resp import resp_success +from services.chat_service import ChatDBService +from db.session import get_db router = APIRouter() @@ -30,21 +29,37 @@ def get_deepseek_llm(api_key: str, model: str, openai_api_base: str): ) @router.post('/stream') -async def chat_stream(request: Request): +async def chat_stream(request: Request, db: Session = Depends(get_db), user=Depends(get_current_user)): body = await request.json() content = body.get('content') - print(content, 'content') + conversation_id = body.get('conversation_id') model = 'deepseek-chat' api_key = os.getenv("DEEPSEEK_API_KEY") - openai_api_base="https://api.deepseek.com/v1" + openai_api_base = "https://api.deepseek.com/v1" llm = get_deepseek_llm(api_key, model, openai_api_base) if not content or not isinstance(content, str): from fastapi.responses import JSONResponse return JSONResponse({"error": "content不能为空"}, status_code=400) + user_id = user["user_id"] + + # 1. 获取或新建对话 + try: + conversation = ChatDBService.get_or_create_conversation(db, conversation_id, user_id, model) + except ValueError as e: + from fastapi.responses import JSONResponse + return JSONResponse({"error": str(e)}, status_code=400) + # 2. 插入当前消息 + ChatDBService.add_message(db, conversation, user_id, content) + + # 3. 查询历史消息,组装上下文 + history = ChatDBService.get_history(db, conversation.id) + history_contents = [msg.content for msg in history] + context = '\n'.join(history_contents) + async def event_generator(): - async for chunk in llm.astream(content): + async for chunk in llm.astream(context): # 只返回 chunk.content 内容 if hasattr(chunk, 'content'): yield f"data: {chunk.content}\n\n" diff --git a/chat/db/session.py b/chat/db/session.py index 242ba08..264bcee 100644 --- a/chat/db/session.py +++ b/chat/db/session.py @@ -1,5 +1,5 @@ from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import sessionmaker, declarative_base from config import SQLALCHEMY_DATABASE_URL engine = create_engine(SQLALCHEMY_DATABASE_URL, pool_pre_ping=True) @@ -10,4 +10,6 @@ def get_db(): try: yield db finally: - db.close() \ No newline at end of file + db.close() + +Base = declarative_base() diff --git a/chat/main.py b/chat/main.py index d1dce16..425a90d 100644 --- a/chat/main.py +++ b/chat/main.py @@ -1,8 +1,13 @@ +import os from fastapi import FastAPI +from dotenv import load_dotenv from api.v1 import ai_chat from fastapi.middleware.cors import CORSMiddleware from routers.ai_api_key import router as ai_api_key_router +# 加载.env环境变量,优先项目根目录 +load_dotenv() + app = FastAPI() origins = [ diff --git a/chat/models/ai.py b/chat/models/ai.py index 88b1f3b..c014a4d 100644 --- a/chat/models/ai.py +++ b/chat/models/ai.py @@ -2,11 +2,11 @@ from sqlalchemy import ( Column, Integer, String, Text, DateTime, Boolean, Float, ForeignKey ) from sqlalchemy.orm import relationship, declarative_base + +from db.session import Base +from models.base import CoreModel from models.user import DjangoUser # 确保导入 DjangoUser -Base = declarative_base() - - # 状态选择类(示例) class CommonStatus: DISABLED = 0 @@ -37,16 +37,6 @@ class MessageType: return [('text', '文本'), ('image', '图片')] -# 基础模型类 -class CoreModel(Base): - __abstract__ = True - - id = Column(Integer, primary_key=True, autoincrement=True) - create_time = Column(DateTime) - update_time = Column(DateTime) - is_deleted = Column(Boolean, default=False) - - # AI API 密钥表 class AIApiKey(CoreModel): __tablename__ = 'ai_api_key' @@ -83,161 +73,160 @@ class AIModel(CoreModel): # AI 工具表 -# class Tool(CoreModel): -# __tablename__ = 'ai_tool' +class Tool(CoreModel): + __tablename__ = 'ai_tool' -# name = Column(String(128), nullable=False) -# description = Column(String(256), nullable=True) -# status = Column(Integer, default=0) + name = Column(String(128), nullable=False) + description = Column(String(256), nullable=True) + status = Column(Integer, default=0) -# def __str__(self): -# return self.name + def __str__(self): + return self.name # AI 知识库表 -# class Knowledge(CoreModel): -# __tablename__ = 'ai_knowledge' +class Knowledge(CoreModel): + __tablename__ = 'ai_knowledge' -# name = Column(String(255), nullable=False) -# description = Column(Text, nullable=True) -# embedding_model_id = Column(Integer, ForeignKey('ai_model.id'), nullable=False) -# embedding_model = Column(String(32), nullable=False) -# top_k = Column(Integer, default=0) -# similarity_threshold = Column(Float, nullable=False) -# status = Column(Integer, default=CommonStatus.DISABLED) + name = Column(String(255), nullable=False) + description = Column(Text, nullable=True) + embedding_model_id = Column(Integer, ForeignKey('ai_model.id'), nullable=False) + embedding_model = Column(String(32), nullable=False) + top_k = Column(Integer, default=0) + similarity_threshold = Column(Float, nullable=False) + status = Column(Integer, default=CommonStatus.DISABLED) -# embedding_model_rel = relationship('AIModel', backref='knowledges') -# documents = relationship('KnowledgeDocument', backref='knowledge', cascade='all, delete-orphan') -# segments = relationship('KnowledgeSegment', backref='knowledge', cascade='all, delete-orphan') -# roles = relationship('ChatRole', secondary='ai_chat_role_knowledge', backref='knowledges') + embedding_model_rel = relationship('AIModel', backref='knowledges') + documents = relationship('KnowledgeDocument', backref='knowledge', cascade='all, delete-orphan') + segments = relationship('KnowledgeSegment', backref='knowledge', cascade='all, delete-orphan') + roles = relationship('ChatRole', secondary='ai_chat_role_knowledge', backref='knowledges') -# def __str__(self): -# return self.name + def __str__(self): + return self.name # AI 知识库文档表 -# class KnowledgeDocument(CoreModel): -# __tablename__ = 'ai_knowledge_document' +class KnowledgeDocument(CoreModel): + __tablename__ = 'ai_knowledge_document' -# knowledge_id = Column(Integer, ForeignKey('ai_knowledge.id'), nullable=False) -# name = Column(String(255), nullable=False) -# url = Column(String(1024), nullable=False) -# content = Column(Text, nullable=False) -# content_length = Column(Integer, nullable=False) -# tokens = Column(Integer, nullable=False) -# segment_max_tokens = Column(Integer, nullable=False) -# retrieval_count = Column(Integer, default=0) -# status = Column(Integer, default=CommonStatus.DISABLED) + knowledge_id = Column(Integer, ForeignKey('ai_knowledge.id'), nullable=False) + name = Column(String(255), nullable=False) + url = Column(String(1024), nullable=False) + content = Column(Text, nullable=False) + content_length = Column(Integer, nullable=False) + tokens = Column(Integer, nullable=False) + segment_max_tokens = Column(Integer, nullable=False) + retrieval_count = Column(Integer, default=0) + status = Column(Integer, default=CommonStatus.DISABLED) -# segments = relationship('KnowledgeSegment', backref='document', cascade='all, delete-orphan') + segments = relationship('KnowledgeSegment', backref='document', cascade='all, delete-orphan') -# def __str__(self): -# return self.name + def __str__(self): + return self.name # AI 知识库分段表 -# class KnowledgeSegment(CoreModel): -# __tablename__ = 'ai_knowledge_segment' +class KnowledgeSegment(CoreModel): + __tablename__ = 'ai_knowledge_segment' -# knowledge_id = Column(Integer, ForeignKey('ai_knowledge.id'), nullable=False) -# document_id = Column(Integer, ForeignKey('ai_knowledge_document.id'), nullable=False) -# content = Column(Text, nullable=False) -# content_length = Column(Integer, nullable=False) -# tokens = Column(Integer, nullable=False) -# vector_id = Column(String(100), nullable=True) -# retrieval_count = Column(Integer, default=0) -# status = Column(Integer, default=CommonStatus.DISABLED) + knowledge_id = Column(Integer, ForeignKey('ai_knowledge.id'), nullable=False) + document_id = Column(Integer, ForeignKey('ai_knowledge_document.id'), nullable=False) + content = Column(Text, nullable=False) + content_length = Column(Integer, nullable=False) + tokens = Column(Integer, nullable=False) + vector_id = Column(String(100), nullable=True) + retrieval_count = Column(Integer, default=0) + status = Column(Integer, default=CommonStatus.DISABLED) -# def __str__(self): -# return f"Segment {self.id}" + def __str__(self): + return f"Segment {self.id}" # AI 聊天角色表 -# class ChatRole(CoreModel): -# __tablename__ = 'ai_chat_role' +class ChatRole(CoreModel): + __tablename__ = 'ai_chat_role' -# name = Column(String(128), nullable=False) -# avatar = Column(String(256), nullable=False) -# description = Column(String(256), nullable=True) -# status = Column(Integer, default=CommonStatus.DISABLED) -# sort = Column(Integer, default=0) -# public_status = Column(Boolean, default=False) -# category = Column(String(32), nullable=True) -# model_id = Column(Integer, ForeignKey('ai_model.id'), nullable=False) -# system_message = Column(String(1024), nullable=True) -# user_id = Column( -# Integer, -# ForeignKey('system_users.id'), # 假设DjangoUser表名是system_users -# nullable=True # 允许为空(如匿名角色) -# ) -# user = relationship(DjangoUser, backref='chat_roles') # 正确:DjangoUser 已定义并导入 + name = Column(String(128), nullable=False) + avatar = Column(String(256), nullable=False) + description = Column(String(256), nullable=True) + status = Column(Integer, default=CommonStatus.DISABLED) + sort = Column(Integer, default=0) + public_status = Column(Boolean, default=False) + category = Column(String(32), nullable=True) + model_id = Column(Integer, ForeignKey('ai_model.id'), nullable=False) + system_message = Column(String(1024), nullable=True) + user_id = Column( + Integer, + ForeignKey('system_users.id'), # 假设DjangoUser表名是system_users + nullable=True # 允许为空(如匿名角色) + ) + user = relationship(DjangoUser, backref='chat_roles') # 正确:DjangoUser 已定义并导入 -# model = relationship('AIModel', backref='chat_roles') -# tools = relationship('Tool', secondary='ai_chat_role_tool', backref='roles') -# # conversations = relationship('ChatConversation', backref='role', cascade='all, delete-orphan') -# # messages = relationship('ChatMessage', backref='role', cascade='all, delete-orphan') - -# def __str__(self): -# return self.name + model = relationship('AIModel', backref='chat_roles') + tools = relationship('Tool', secondary='ai_chat_role_tool', backref='roles') + # conversations = relationship('ChatConversation', backref='role', cascade='all, delete-orphan') + # messages = relationship('ChatMessage', backref='role', cascade='all, delete-orphan') + def __str__(self): + return self.name # AI 聊天对话表 -# class ChatConversation(CoreModel): -# __tablename__ = 'ai_chat_conversation' +class ChatConversation(CoreModel): + __tablename__ = 'ai_chat_conversation' -# title = Column(String(256), nullable=False) -# pinned = Column(Boolean, default=False) -# pinned_time = Column(DateTime, nullable=True) -# # user_id = Column(Integer, ForeignKey('system_users.id'), nullable=True) -# role_id = Column(Integer, ForeignKey('ai_chat_role.id'), nullable=True) -# model_id = Column(Integer, ForeignKey('ai_model.id'), nullable=False) -# model = Column(String(32), nullable=False) -# system_message = Column(String(1024), nullable=True) -# temperature = Column(Float, nullable=False) -# max_tokens = Column(Integer, nullable=False) -# max_contexts = Column(Integer, nullable=False) -# # user = relationship(DjangoUser, backref='conversations') # 正确:DjangoUser 已定义并导入 + title = Column(String(256), nullable=False) + pinned = Column(Boolean, default=False) + pinned_time = Column(DateTime, nullable=True) + user_id = Column(Integer, ForeignKey('system_users.id'), nullable=True) + role_id = Column(Integer, ForeignKey('ai_chat_role.id'), nullable=True) + model_id = Column(Integer, ForeignKey('ai_model.id'), nullable=False) + model = Column(String(32), nullable=False) + system_message = Column(String(1024), nullable=True) + temperature = Column(Float, nullable=False) + max_tokens = Column(Integer, nullable=False) + max_contexts = Column(Integer, nullable=False) + user = relationship(DjangoUser, backref='conversations') # 正确:DjangoUser 已定义并导入 -# model_rel = relationship('AIModel', backref='conversations') -# messages = relationship('ChatMessage', backref='conversation', cascade='all, delete-orphan') + model_rel = relationship('AIModel', backref='conversations') + messages = relationship('ChatMessage', backref='conversation', cascade='all, delete-orphan') -# def __str__(self): -# return self.title + def __str__(self): + return self.title # AI 聊天消息表 -# class ChatMessage(CoreModel): -# __tablename__ = 'ai_chat_message' +class ChatMessage(CoreModel): + __tablename__ = 'ai_chat_message' -# conversation_id = Column(Integer, nullable=False) -# # user_id = Column(Integer, ForeignKey('system_users.id'), nullable=True) -# role_id = Column(Integer, ForeignKey('ai_chat_role.id'), nullable=True) -# model = Column(String(32), nullable=False) -# model_id = Column(Integer, ForeignKey('ai_model.id'), nullable=False) -# type = Column(String(16), nullable=False) -# reply_id = Column(Integer, nullable=True) -# content = Column(String(2048), nullable=False) -# use_context = Column(Boolean, default=False) -# segment_ids = Column(String(2048), nullable=True) + conversation_id = Column(Integer, ForeignKey('ai_chat_conversation.id'), nullable=False) + user_id = Column(Integer, ForeignKey('system_users.id'), nullable=True) + role_id = Column(Integer, ForeignKey('ai_chat_role.id'), nullable=True) + model = Column(String(32), nullable=False) + model_id = Column(Integer, ForeignKey('ai_model.id'), nullable=False) + type = Column(String(16), nullable=False) + reply_id = Column(Integer, nullable=True) + content = Column(String(2048), nullable=False) + use_context = Column(Boolean, default=False) + segment_ids = Column(String(2048), nullable=True) -# # user = relationship(DjangoUser, backref='messages') # 正确:DjangoUser 已定义并导入 -# model_rel = relationship('AIModel', backref='messages') + user = relationship(DjangoUser, backref='messages') # 正确:DjangoUser 已定义并导入 + model_rel = relationship('AIModel', backref='messages') -# def __str__(self): -# return self.content[:30] + def __str__(self): + return self.content[:30] -# # 聊天角色与知识库的关联表 -# class ChatRoleKnowledge(Base): -# __tablename__ = 'ai_chat_role_knowledge' +# 聊天角色与知识库的关联表 +class ChatRoleKnowledge(Base): + __tablename__ = 'ai_chat_role_knowledge' -# chat_role_id = Column(Integer, ForeignKey('ai_chat_role.id'), primary_key=True) -# knowledge_id = Column(Integer, ForeignKey('ai_knowledge.id'), primary_key=True) + chat_role_id = Column(Integer, ForeignKey('ai_chat_role.id'), primary_key=True) + knowledge_id = Column(Integer, ForeignKey('ai_knowledge.id'), primary_key=True) -# # 聊天角色与工具的关联表 -# class ChatRoleTool(Base): -# __tablename__ = 'ai_chat_role_tool' +# 聊天角色与工具的关联表 +class ChatRoleTool(Base): + __tablename__ = 'ai_chat_role_tool' -# chat_role_id = Column(Integer, ForeignKey('ai_chat_role.id'), primary_key=True) -# tool_id = Column(Integer, ForeignKey('ai_tool.id'), primary_key=True) \ No newline at end of file + chat_role_id = Column(Integer, ForeignKey('ai_chat_role.id'), primary_key=True) + tool_id = Column(Integer, ForeignKey('ai_tool.id'), primary_key=True) \ No newline at end of file diff --git a/chat/models/base.py b/chat/models/base.py new file mode 100644 index 0000000..8cfe077 --- /dev/null +++ b/chat/models/base.py @@ -0,0 +1,13 @@ +from db.session import Base +from sqlalchemy import ( + Column, Integer, String, Text, DateTime, Boolean, Float, ForeignKey +) + +# 基础模型类 +class CoreModel(Base): + __abstract__ = True + + id = Column(Integer, primary_key=True, autoincrement=True) + create_time = Column(DateTime) + update_time = Column(DateTime) + is_deleted = Column(Boolean, default=False) diff --git a/chat/models/user.py b/chat/models/user.py index cc089cc..b9c573f 100644 --- a/chat/models/user.py +++ b/chat/models/user.py @@ -1,7 +1,7 @@ from sqlalchemy import Column, Integer, String, DateTime, Boolean -from sqlalchemy.ext.declarative import declarative_base -Base = declarative_base() +from db.session import Base + class AuthToken(Base): __tablename__ = 'authtoken_token' @@ -12,6 +12,7 @@ class AuthToken(Base): class DjangoUser(Base): __tablename__ = 'system_users' + id = Column(Integer, primary_key=True) username = Column(String(150), nullable=False) email = Column(String(254)) diff --git a/chat/services/chat_service.py b/chat/services/chat_service.py index 5031c0d..bf7a074 100644 --- a/chat/services/chat_service.py +++ b/chat/services/chat_service.py @@ -1,15 +1,58 @@ # LangChain集成示例 -from langchain_openai import OpenAI -from dotenv import load_dotenv -load_dotenv() +from sqlalchemy.orm import Session +from datetime import datetime +from models.ai import ChatConversation, ChatMessage, MessageType -class ChatService: - def __init__(self): - # 这里以OpenAI为例,实际可根据需要配置 - self.llm = OpenAI(temperature=0.7, api_key='sssss') +class ChatDBService: + @staticmethod + def get_or_create_conversation(db: Session, conversation_id: int | None, user_id: int, model: str) -> ChatConversation: + if not conversation_id: + conversation = ChatConversation( + title="新对话", + user_id=user_id, + role_id=None, + model_id=1, # 需根据实际模型id调整 + model=model, + system_message=None, + temperature=0.7, + max_tokens=2048, + max_contexts=10, + create_time=datetime.now(), + update_time=datetime.now(), + is_deleted=False + ) + db.add(conversation) + db.commit() + db.refresh(conversation) + return conversation + else: + conversation = db.query(ChatConversation).get(conversation_id) + if not conversation: + raise ValueError("无效的conversation_id") + return conversation - def chat(self, prompt: str) -> str: - # 简单调用LLM - return self.llm(prompt) + @staticmethod + def add_message(db: Session, conversation: ChatConversation, user_id: int, content: str) -> ChatMessage: + message = ChatMessage( + conversation_id=conversation.id, + user_id=user_id, + role_id=None, + model=conversation.model, + model_id=conversation.model_id, + type=MessageType.TEXT, + reply_id=None, + content=content, + use_context=True, + segment_ids=None, + create_time=datetime.now(), + update_time=datetime.now(), + is_deleted=False + ) + db.add(message) + db.commit() + return message + + @staticmethod + def get_history(db: Session, conversation_id: int) -> list[ChatMessage]: + return db.query(ChatMessage).filter_by(conversation_id=conversation_id).order_by(ChatMessage.id).all() -chat_service = ChatService() \ No newline at end of file diff --git a/web/apps/web-antd/src/api/ai/chat.ts b/web/apps/web-antd/src/api/ai/chat.ts index 2c79471..c3375bc 100644 --- a/web/apps/web-antd/src/api/ai/chat.ts +++ b/web/apps/web-antd/src/api/ai/chat.ts @@ -4,9 +4,13 @@ import { formatToken } from '#/utils/auth'; export interface FetchAIStreamParams { content: string; + conversation_id?: null | number; } -export async function fetchAIStream({ content }: FetchAIStreamParams) { +export async function fetchAIStream({ + content, + conversation_id, +}: FetchAIStreamParams) { const accessStore = useAccessStore(); const token = accessStore.accessToken; const headers = new Headers(); @@ -19,6 +23,7 @@ export async function fetchAIStream({ content }: FetchAIStreamParams) { headers, body: JSON.stringify({ content, + conversation_id, }), }); diff --git a/web/apps/web-antd/src/views/ai/chat/index.vue b/web/apps/web-antd/src/views/ai/chat/index.vue index 4cb5cf9..4c59b39 100644 --- a/web/apps/web-antd/src/views/ai/chat/index.vue +++ b/web/apps/web-antd/src/views/ai/chat/index.vue @@ -15,7 +15,6 @@ import { } from 'ant-design-vue'; import { fetchAIStream } from '#/api/ai/chat'; -// 移除 import typingSound from '@/assets/typing.mp3'; interface Message { id: number; @@ -23,27 +22,17 @@ interface Message { content: string; } +interface ChatItem { + id: number; + title: string; + lastMessage: string; +} + // mock 历史对话 -const chatList = ref([ - { - id: 1, - title: '和deepseek的对话', - lastMessage: 'AI: 你好,有什么可以帮您?', - }, - { id: 2, title: '工作助理', lastMessage: 'AI: 今天的日程已为您安排。' }, -]); +const chatList = ref([]); // mock 聊天消息 -const messages = ref>({ - 1: [ - { id: 1, role: 'user', content: '你好' }, - { id: 2, role: 'ai', content: '你好,有什么可以帮您?' }, - ], - 2: [ - { id: 1, role: 'user', content: '帮我安排下今天的日程' }, - { id: 2, role: 'ai', content: '今天的日程已为您安排。' }, - ], -}); +const messages = ref([]); // mock 模型列表 const modelOptions = [ @@ -51,8 +40,8 @@ const modelOptions = [ { label: 'GPT-4', value: 'gpt-4' }, ]; -const selectedChatId = ref(chatList.value[0]?.id || 1); -const selectedModel = ref(modelOptions[0].value); +const selectedChatId = ref(chatList.value[0]?.id ?? null); +const selectedModel = ref(modelOptions[0]?.value); const search = ref(''); const input = ref(''); const messagesRef = ref(null); @@ -64,9 +53,14 @@ const filteredChats = computed(() => { return chatList.value.filter((chat) => chat.title.includes(search.value)); }); -const currentMessages = computed( - () => messages.value?.[selectedChatId.value] || [], -); +// 直接用conversationId过滤 +const currentMessages = computed(() => { + if (!selectedChatId.value) return []; + return []; + // return messages.value.filter( + // (msg) => msg.conversationId === selectedChatId.value, + // ); +}); function selectChat(id: number) { selectedChatId.value = id; @@ -80,38 +74,46 @@ function handleNewChat() { title: `新对话${chatList.value.length + 1}`, lastMessage: '', }); - messages.value[newId] = []; selectedChatId.value = newId; nextTick(scrollToBottom); } async function handleSend() { - if (!input.value.trim()) return; - const msg: Message = { id: Date.now(), role: 'user', content: input.value }; - if (!messages.value[selectedChatId.value]) { - messages.value[selectedChatId.value] = []; - } - messages.value[selectedChatId.value].push(msg); + console.log(111); + const msg: Message = { + id: Date.now(), + role: 'user', + content: input.value, + }; + messages.value.push(msg); // 预留AI消息 - const aiMsgObj: Message = { id: Date.now() + 1, role: 'ai', content: '' }; - messages.value[selectedChatId.value].push(aiMsgObj); + const aiMsgObj: Message = { + id: Date.now() + 1, + role: 'ai', + content: '', + }; + messages.value.push(aiMsgObj); currentAiMessage.value = aiMsgObj; isAiTyping.value = true; const stream = await fetchAIStream({ content: input.value, + conversation_id: selectedChatId.value, // 新增 }); - // 移除打字音效播放 - for await (const chunk of stream) { for (const char of chunk) { aiMsgObj.content += char; + // 保证messages数组响应式更新 + const idx = messages.value.indexOf(aiMsgObj); + if (idx !== -1) { + messages.value.splice(idx, 1, { ...aiMsgObj }); + } currentAiMessage.value = { ...aiMsgObj }; - // 移除打字音效播放 - await new Promise(resolve => setTimeout(resolve, 15)); - nextTick(scrollToBottom); + await nextTick(); + scrollToBottom(); + await new Promise((resolve) => setTimeout(resolve, 15)); } } isAiTyping.value = false; @@ -365,7 +367,13 @@ function scrollToBottom() { border-radius: 2px; } @keyframes blink-cursor { - 0%, 50% { opacity: 1; } - 51%, 100% { opacity: 0; } + 0%, + 50% { + opacity: 1; + } + 51%, + 100% { + opacity: 0; + } }