优化历史对话

This commit is contained in:
XIE7654
2025-07-17 16:45:18 +08:00
parent 6ed606f7a4
commit ff47665dcb
2 changed files with 188 additions and 28 deletions

View File

@@ -1,7 +1,7 @@
import os import os
import asyncio import asyncio
from fastapi import APIRouter, Depends, Request from fastapi import APIRouter, Depends, Request, Query
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -12,6 +12,7 @@ from langchain_community.chat_models import ChatOpenAI
from deps.auth import get_current_user from deps.auth import get_current_user
from services.chat_service import ChatDBService from services.chat_service import ChatDBService
from db.session import get_db from db.session import get_db
from models.ai import ChatConversation, ChatMessage
router = APIRouter() router = APIRouter()
@@ -29,7 +30,7 @@ def get_deepseek_llm(api_key: str, model: str, openai_api_base: str):
) )
@router.post('/stream') @router.post('/stream')
async def chat_stream(request: Request, db: Session = Depends(get_db), user=Depends(get_current_user)): async def chat_stream(request: Request, user=Depends(get_current_user), db: Session = Depends(get_db)):
body = await request.json() body = await request.json()
content = body.get('content') content = body.get('content')
conversation_id = body.get('conversation_id') conversation_id = body.get('conversation_id')
@@ -68,3 +69,44 @@ async def chat_stream(request: Request, db: Session = Depends(get_db), user=Depe
await asyncio.sleep(0.01) await asyncio.sleep(0.01)
return StreamingResponse(event_generator(), media_type='text/event-stream') return StreamingResponse(event_generator(), media_type='text/event-stream')
@router.get('/conversations')
def get_conversations(
user_id: int = Query(None),
db: Session = Depends(get_db)
):
"""获取指定用户的聊天对话列表"""
conversations = db.query(ChatConversation).filter(ChatConversation.user_id == user_id).order_by(ChatConversation.update_time.desc()).all()
# 可根据需要序列化
return [
{
'id': c.id,
'title': c.title,
'update_time': c.update_time,
'last_message': c.messages[-1].content if c.messages else '',
}
for c in conversations
]
@router.get('/messages')
def get_messages(
conversation_id: int = Query(None),
user_id: int = Query(None),
db: Session = Depends(get_db)
):
"""获取指定会话的消息列表可选user_id过滤"""
query = db.query(ChatMessage).filter(ChatMessage.conversation_id == conversation_id)
if user_id is not None:
query = query.filter(ChatMessage.user_id == user_id)
messages = query.order_by(ChatMessage.id).all()
return [
{
'id': m.id,
'role': m.role_id, # 如需role名可再查
'content': m.content,
'user_id': m.user_id,
'conversation_id': m.conversation_id,
'create_time': m.create_time,
}
for m in messages
]

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, nextTick, ref } from 'vue'; import { computed, nextTick, onMounted, ref } from 'vue';
import { Page } from '@vben/common-ui'; import { Page } from '@vben/common-ui';
@@ -9,7 +9,7 @@ import {
Input, Input,
InputSearch, InputSearch,
List, List,
ListItemMeta, ListItem,
Row, Row,
Select, Select,
} from 'ant-design-vue'; } from 'ant-design-vue';
@@ -52,7 +52,6 @@ const filteredChats = computed(() => {
if (!search.value) return chatList.value; if (!search.value) return chatList.value;
return chatList.value.filter((chat) => chat.title.includes(search.value)); return chatList.value.filter((chat) => chat.title.includes(search.value));
}); });
// 直接用conversationId过滤 // 直接用conversationId过滤
const currentMessages = computed(() => { const currentMessages = computed(() => {
if (!selectedChatId.value) return []; if (!selectedChatId.value) return [];
@@ -126,6 +125,28 @@ function scrollToBottom() {
messagesRef.value.scrollTop = messagesRef.value.scrollHeight; messagesRef.value.scrollTop = messagesRef.value.scrollHeight;
} }
} }
// 获取历史对话
async function fetchConversations() {
// 这里假设user_id为1实际应从登录信息获取
const params = new URLSearchParams({ user_id: '1' });
const res = await fetch(`/chat/api/v1/conversations?${params.toString()}`);
const data = await res.json();
chatList.value = data.map((item: any) => ({
id: item.id,
title: item.title,
lastMessage: item.last_message || '',
}));
console.log(chatList.value, 'chatList');
// 默认选中第一个对话
if (chatList.value.length > 0) {
selectedChatId.value = chatList.value[0].id;
}
}
onMounted(() => {
fetchConversations();
});
</script> </script>
<template> <template>
@@ -142,28 +163,34 @@ function scrollToBottom() {
style="margin: 12px 0 8px 0" style="margin: 12px 0 8px 0"
/> />
</div> </div>
<List style="flex: 1; overflow-y: auto; padding-bottom: 12px"> <div class="chat-list">
<template #default> <List style="flex: 1; overflow-y: auto; padding-bottom: 12px">
<ListItemMeta <template #default>
v-for="item in filteredChats" <ListItem
:key="item.id" v-for="item in filteredChats"
class="chat-list-item" :key="item.id"
:class="[{ selected: item.id === selectedChatId }]" class="chat-list-item"
@click="selectChat(item.id)" :class="[{ selected: item.id === selectedChatId }]"
> @click="selectChat(item.id)"
<ListItemMeta> >
<template #title> <div class="chat-item-avatar">
<span class="chat-title" :title="item.title">{{ <!-- 可用头像或首字母 -->
item.title <span class="avatar-text">{{ item.title.slice(0, 1) }}</span>
}}</span> </div>
</template> <div class="chat-item-content">
<template #description> <div class="chat-item-title-row">
<span class="chat-desc">{{ item.lastMessage }}</span> <span class="chat-title" :title="item.title">{{
</template> item.title
</ListItemMeta> }}</span>
</ListItemMeta> <!-- 未读角标如有未读可加 -->
</template> <!-- <span class="unread-dot"></span> -->
</List> </div>
<div class="chat-desc">{{ item.lastMessage }}</div>
</div>
</ListItem>
</template>
</List>
</div>
</Col> </Col>
<!-- 右侧聊天区 --> <!-- 右侧聊天区 -->
<Col :span="18" class="chat-content"> <Col :span="18" class="chat-content">
@@ -376,4 +403,95 @@ function scrollToBottom() {
opacity: 0; opacity: 0;
} }
} }
.chat-list-item {
display: flex;
align-items: center;
border-radius: 8px;
margin-bottom: 6px;
padding: 8px 12px;
cursor: pointer;
transition:
background 0.2s,
box-shadow 0.2s;
}
.chat-list-item.selected {
background: #e6f7ff;
box-shadow: 0 2px 8px #1677ff22;
border: 1.5px solid #1677ff;
}
.chat-list-item:hover {
background: #f0f5ff;
box-shadow: 0 2px 8px #1677ff11;
}
.chat-item-avatar {
width: 36px;
height: 36px;
background: #1677ff22;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
font-size: 18px;
color: #1677ff;
font-weight: bold;
}
.avatar-text {
user-select: none;
}
.chat-item-content {
flex: 1;
min-width: 0;
}
.chat-item-title-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.chat-title {
font-weight: 500;
font-size: 15px;
max-width: 140px;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.unread-dot {
display: inline-block;
width: 8px;
height: 8px;
background: #ff4d4f;
border-radius: 50%;
margin-left: 8px;
}
.chat-desc {
color: #888;
font-size: 12px;
max-width: 140px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-top: 2px;
}
.chat-sider {
background: #fafbfc;
display: flex;
flex-direction: column;
border-right: 1px solid #eee;
padding: 16px 8px 8px 8px;
height: 100%; /* 关键让侧边栏高度100% */
min-width: 220px;
}
.sider-header {
margin-bottom: 8px;
}
.chat-list {
flex: 1;
overflow-y: auto; /* 只在对话列表区滚动 */
min-height: 0; /* 关键flex子项内滚动时必须加 */
max-height: calc(100vh - 120px); /* 可根据实际header/footer高度调整 */
}
</style> </style>