diff --git a/vue-fastapi-backend/module_admin/controller/aichat_controller.py b/vue-fastapi-backend/module_admin/controller/aichat_controller.py index b70483c..d958554 100644 --- a/vue-fastapi-backend/module_admin/controller/aichat_controller.py +++ b/vue-fastapi-backend/module_admin/controller/aichat_controller.py @@ -28,10 +28,10 @@ async def get_chat_session_list(request: Request, return ResponseUtil.success(data=ai_session_list_result) -@aichatController.get("/chat/list/{sessionId}") -async def get_chat_list(request: Request, sessionId: str, query_db: AsyncSession = Depends(get_db), +@aichatController.get("/chat/list") +async def get_chat_list(request: Request, query: AiListQuery = Depends(AiListQuery.as_query), query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - ai_chat_list_result = await AiChatService.get_ai_chat_list_services(query_db, sessionId, current_user) + ai_chat_list_result = await AiChatService.get_ai_chat_list_services(query_db, query, current_user) logger.info('获取成功') return ResponseUtil.success(data=ai_chat_list_result) diff --git a/vue-fastapi-backend/module_admin/dao/aichat_dao.py b/vue-fastapi-backend/module_admin/dao/aichat_dao.py index 80e867f..57d3a3d 100644 --- a/vue-fastapi-backend/module_admin/dao/aichat_dao.py +++ b/vue-fastapi-backend/module_admin/dao/aichat_dao.py @@ -2,7 +2,8 @@ from sqlalchemy import desc, delete, func, select, update from sqlalchemy.ext.asyncio import AsyncSession from module_admin.entity.do.aichat_do import AiChatHistory from module_admin.entity.do.aichat_do import AiChatSession -from module_admin.entity.vo.aichat_vo import AiChatModel +from module_admin.entity.vo.aichat_vo import AiChatModel, AiListQuery +from utils.page_util import PageUtil class AiChatDao: @@ -21,14 +22,13 @@ class AiChatDao: return session_list @classmethod - async def get_ai_chat_list(cls, db: AsyncSession, sessionId: str, user_id: int): - chat_list = ( - await db.execute( - select(AiChatHistory).where(AiChatHistory.user == user_id, AiChatHistory.sessionId == sessionId) - .order_by(AiChatHistory.time) - ) - ).scalars().all() - return chat_list + async def get_ai_chat_list(cls, db: AsyncSession, query: AiListQuery, user_id: int): + querySql = ( + select(AiChatHistory).where(AiChatHistory.user == user_id, AiChatHistory.sessionId == query.session_id) + .order_by(desc(AiChatHistory.time)) + ) + result = await PageUtil.paginate(db, querySql, query.page_num, query.page_size, True) + return result @classmethod async def get_ai_chat_by_id(cls, sessionId: str, db: AsyncSession, user_id: int): diff --git a/vue-fastapi-backend/module_admin/entity/vo/aichat_vo.py b/vue-fastapi-backend/module_admin/entity/vo/aichat_vo.py index 634b3ab..b1c10ad 100644 --- a/vue-fastapi-backend/module_admin/entity/vo/aichat_vo.py +++ b/vue-fastapi-backend/module_admin/entity/vo/aichat_vo.py @@ -1,5 +1,8 @@ from pydantic import BaseModel from typing import Union, Optional, List +from pydantic import BaseModel, ConfigDict, Field, model_validator +from module_admin.annotation.pydantic_annotation import as_query +from pydantic.alias_generators import to_camel class CrudChatModel(BaseModel): @@ -7,6 +10,13 @@ class CrudChatModel(BaseModel): message: str +@as_query +class AiListQuery(BaseModel): + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + session_id: str + page_num: int + page_size: int + class AiChatModel(BaseModel): """ 菜单表对应pydantic模型 diff --git a/vue-fastapi-backend/module_admin/service/aichat_service.py b/vue-fastapi-backend/module_admin/service/aichat_service.py index 337e39e..0e124ef 100644 --- a/vue-fastapi-backend/module_admin/service/aichat_service.py +++ b/vue-fastapi-backend/module_admin/service/aichat_service.py @@ -22,9 +22,9 @@ class AiChatService: return ai_session_list @classmethod - async def get_ai_chat_list_services(cls, result_db: AsyncSession, sessionId: str, + async def get_ai_chat_list_services(cls, result_db: AsyncSession, query: AiListQuery, current_user: Optional[CurrentUserModel] = None): - ai_session_list = await AiChatDao.get_ai_chat_list(result_db, sessionId, current_user.user.user_id) # 查询最新的20条 + ai_session_list = await AiChatDao.get_ai_chat_list(result_db, query, current_user.user.user_id) # 查询最新的20条 return CamelCaseUtil.transform_result(ai_session_list) @classmethod diff --git a/vue-fastapi-frontend/src/api/aichat/aichat.js b/vue-fastapi-frontend/src/api/aichat/aichat.js index a0b353d..e32ea4e 100644 --- a/vue-fastapi-frontend/src/api/aichat/aichat.js +++ b/vue-fastapi-frontend/src/api/aichat/aichat.js @@ -14,11 +14,11 @@ export function listChatHistory(sessionId) { }) } -export function getChatList(sessionId) { +export function getChatList(data) { return request({ - url: '/default-api/aichat/chat/list/'+sessionId, + url: '/default-api/aichat/chat/list', method: 'get', - params: {} + params: data }) } diff --git a/vue-fastapi-frontend/src/layout/components/AppMain.vue b/vue-fastapi-frontend/src/layout/components/AppMain.vue index 664240d..a5e6b8b 100644 --- a/vue-fastapi-frontend/src/layout/components/AppMain.vue +++ b/vue-fastapi-frontend/src/layout/components/AppMain.vue @@ -27,9 +27,6 @@ import iframeToggle from "./IframeToggle/index" import useTagsViewStore from '@/store/modules/tagsView' import { ref, onMounted, reactive, nextTick, computed } from 'vue' import AichatIndex from "../../views/aichat/index.vue"; -import Cookies from "js-cookie"; -import {v4 as uuidv4} from "uuid"; -import {getChatList} from "@/api/aichat/aichat.js"; const route = useRoute() const tagsViewStore = useTagsViewStore() diff --git a/vue-fastapi-frontend/src/views/aichat/aichat.vue b/vue-fastapi-frontend/src/views/aichat/aichat.vue index 976a817..36fb305 100644 --- a/vue-fastapi-frontend/src/views/aichat/aichat.vue +++ b/vue-fastapi-frontend/src/views/aichat/aichat.vue @@ -258,16 +258,15 @@ function setScrollBottom() { scrollDiv.value.setScrollTop(getMaxHeight()) } - /** * 滚动条距离最上面的高度 */ const scrollTop = ref(0) - const getMaxHeight = () => { - return dialogScrollbar.value.scrollHeight + return dialogScrollbar.value?.scrollHeight ?? 0; } + const handleScrollTop = ($event) => { scrollTop.value = $event.scrollTop emit('scroll', { ...$event, dialogScrollbar: dialogScrollbar.value, scrollDiv: scrollDiv.value }) @@ -278,13 +277,13 @@ const handleScroll = () => { // 内部高度小于外部高度 就需要出滚动条 if (scrollDiv.value.wrapRef.offsetHeight < dialogScrollbar.value.scrollHeight) { // 如果当前滚动条距离最下面的距离在 规定距离 滚动条就跟随 - scrollDiv.value.setScrollTop(getMaxHeight()) - + // if (scorll.value) { + scrollDiv.value.setScrollTop(getMaxHeight()) + // } } } } - /**文件上传中处理 */ const handleFileUploadProgress = (event, file, fileList) => { upload.isUploading = true; @@ -354,6 +353,7 @@ function changeThumb(index,chat){ function openUpload(){ upload.open = true } + function chooseMachine(val){ inputValue.value = inputValue.value.slice(0,-1) currentMachine.value = [val] @@ -472,17 +472,20 @@ function sendChatMessage(data){ chatList.value.push({"type":"answer","content":[{"type":"text","content":"服务异常"}],"isEnd":true,"isStop":false,"sessionId":chatList.value[0].sessionId,"sessionName":chatList.value[0].sessionName,"operate":"","thumbDownReason":""}) }) } + watch( chatList, () => { nextTick(() => { // 将滚动条滚动到最下面 - scrollDiv.value.setScrollTop(getMaxHeight()) + // scrollDiv.value.setScrollTop(getMaxHeight()) + handleScroll() }) },{ deep:true, immediate:true } ) + const getWrite = (reader) => { // 修复点1:将tempResult改为局部变量,避免递归状态污染 let tempResult = ''; @@ -572,6 +575,7 @@ const stopChat = (index) => { answer.content = JSON.stringify(answer.content) addChat(answer) } + const startChat = (index) => { regenerationChart(index) } diff --git a/vue-fastapi-frontend/src/views/aichat/index.vue b/vue-fastapi-frontend/src/views/aichat/index.vue index fe0e73d..b6021ec 100644 --- a/vue-fastapi-frontend/src/views/aichat/index.vue +++ b/vue-fastapi-frontend/src/views/aichat/index.vue @@ -100,6 +100,12 @@ const currentRecordList = ref([]) const sessionId = ref(Cookies.get("chatSessionId")) // 当前历史记录Id 默认为'new' const mouseId = ref('') +const paginationConfig = reactive({ + current_page: 1, + page_size: 20, + total: 0 +}) + function mouseenter(row) { mouseId.value = row.sessionId } @@ -124,7 +130,6 @@ function showChatHistory(){ }) } - function newChat() { currentRecordList.value = [] sessionId.value = uuidv4() @@ -133,16 +138,31 @@ function newChat() { } function handleScroll(event) { - if (event.scrollTop === 0 && currentRecordList.value.length>0) { + if ( + event.scrollTop === 0 && + paginationConfig.total > currentRecordList.value.length + ) { const history_height = event.dialogScrollbar.offsetHeight - event.scrollDiv.setScrollTop(event.dialogScrollbar.offsetHeight - history_height) + paginationConfig.current_page += 1 + getChatRecord({sessionId: sessionId.value, pageNum: paginationConfig.current_page, pageSize: paginationConfig.page_size}).then(() => { + event.scrollDiv.setScrollTop(event.dialogScrollbar.offsetHeight - history_height) + }) } } function clickListHandle(item){ - getChatList(item.sessionId).then(res=>{ - currentRecordList.value = [] - let array = res.data + paginationConfig.current_page = 1 + currentRecordList.value = [] + sessionId.value = item.sessionId + Cookies.set("chatSessionId",sessionId.value) + getChatRecord({sessionId: item.sessionId, pageNum: paginationConfig.current_page, pageSize: paginationConfig.page_size}) + show.value = false +} + +function getChatRecord(data){ + return getChatList(data).then(res=>{ + let array = res.data.rows + paginationConfig.total = res.data.total for (let i = 0; i < array.length; i++) { if (array[i].type === 'answer'){ array[i].content = JSON.parse(array[i].content) @@ -150,12 +170,16 @@ function clickListHandle(item){ if (array[i].type === 'question'){ array[i].file = JSON.parse(array[i].file) } - currentRecordList.value.push(array[i]) - AiChatRef.value.setScrollBottom() } - show.value = false - sessionId.value = item.sessionId - Cookies.set("chatSessionId",sessionId.value) + currentRecordList.value = [...array, ...currentRecordList.value].sort((a, b) => + a.time.localeCompare(b.time) + ) + if (paginationConfig.current_page === 1) { + nextTick(() => { + // 将滚动条滚动到最下面 + AiChatRef.value.setScrollBottom() + }) + } }) } watch(() => props.chatDataList, value => currentRecordList.value = JSON.parse(JSON.stringify(value))) @@ -163,22 +187,7 @@ onMounted( ()=>{ if (Cookies.get("chatSessionId")){ //调用子页面的赋值方法,给子页面赋值 - getChatList(Cookies.get("chatSessionId")).then(res=>{ - let array = res.data - if (array && array.length >0){ - for (let i = 0; i < array.length; i++) { - if (array[i].type === 'answer'){ - array[i].content = JSON.parse(array[i].content) - } - if (array[i].type === 'question'){ - array[i].file = JSON.parse(array[i].file) - } - } - } - currentRecordList.value = array - }).then(()=>{ - AiChatRef.value.setScrollBottom() - }) + getChatRecord({sessionId: Cookies.get("chatSessionId"), pageNum: paginationConfig.current_page, pageSize: paginationConfig.page_size}) }else { Cookies.set("chatSessionId",uuidv4()) }