ai前端聊天窗口

This commit is contained in:
XaoLi717 2024-10-09 15:36:33 +08:00
parent 7aad323bed
commit a597eaefaa
2 changed files with 232 additions and 253 deletions

View File

@ -3,9 +3,6 @@
<div class="chat-list" v-for="(item, index) in list" :key="index">
<!-- 靠左 messagesystemassistant 类型 -->
<div class="left-message message-item" v-if="item.type !== 'user'">
<div class="avatar">
<el-avatar :src="roleAvatar" />
</div>
<div class="message">
<div>
<el-text class="time">{{ formatDate(item.createTime) }}</el-text>
@ -25,9 +22,6 @@
</div>
<!-- 靠右 messageuser 类型 -->
<div class="right-message message-item" v-if="item.type === 'user'">
<div class="avatar">
<el-avatar :src="userAvatar" />
</div>
<div class="message">
<div>
<el-text class="time">{{ formatDate(item.createTime) }}</el-text>
@ -39,7 +33,7 @@
<el-button class="btn-cus" link @click="copyContent(item.content)">
<img class="btn-image" src="@/assets/ai/copy.svg" />
</el-button>
<el-button class="btn-cus" link @click="onDelete(item.id)">
<el-button class="btn-cus" link @click="onDelete(index)">
<img class="btn-image h-17px mr-12px" src="@/assets/ai/delete.svg" />
</el-button>
<el-button class="btn-cus" link @click="onRefresh(item)">
@ -64,29 +58,18 @@ import { formatDate } from '@/utils/formatTime'
import MarkdownView from '@/components/MarkdownView/index.vue'
import { useClipboard } from '@vueuse/core'
import { ArrowDownBold, Edit, RefreshRight } from '@element-plus/icons-vue'
import { ChatMessageApi, ChatMessageVO } from '@/api/ai/chat/message'
import { ChatConversationVO } from '@/api/ai/chat/conversation'
import { useUserStore } from '@/store/modules/user'
import userAvatarDefaultImg from '@/assets/imgs/avatar.gif'
import roleAvatarDefaultImg from '@/assets/ai/gpt.svg'
import { ChatMessageVO } from '@/api/ai/chat/message'
const message = useMessage() //
const { copy } = useClipboard() // copy
const userStore = useUserStore()
// ()
const messageContainer: any = ref(null)
const isScrolling = ref(false) //
const userAvatar = computed(() => userStore.user.avatar ?? userAvatarDefaultImg)
const roleAvatar = computed(() => props.conversation.roleAvatar ?? roleAvatarDefaultImg)
// props
const props = defineProps({
conversation: {
type: Object as PropType<ChatConversationVO>,
required: true
},
list: {
type: Array as PropType<ChatMessageVO[]>,
required: true
@ -95,7 +78,7 @@ const props = defineProps({
const { list } = toRefs(props) //
const emits = defineEmits(['onDeleteSuccess', 'onRefresh', 'onEdit']) // emits
const emits = defineEmits(['onDeleteSuccess', 'onRefresh', 'onEdit','update:list']) // emits
// ============ ==============
@ -146,12 +129,14 @@ const copyContent = async (content) => {
}
/** 删除 */
const onDelete = async (id) => {
const onDelete = async (index) => {
const oldList = props.list
const newList = oldList?.splice(index,1)
// message
await ChatMessageApi.deleteChatMessage(id)
// console.log("item,index",newList)
message.success('删除成功!')
//
emits('onDeleteSuccess')
emits('update:list',newList)
}
/** 刷新 */

View File

@ -1,14 +1,5 @@
<template>
<el-container class="ai-layout">
<!-- 左侧对话列表 -->
<ConversationList
:active-id="activeConversationId"
ref="conversationListRef"
@on-conversation-create="handleConversationCreateSuccess"
@on-conversation-click="handleConversationClick"
@on-conversation-clear="handleConversationClear"
@on-conversation-delete="handlerConversationDelete"
/>
<!-- 右侧对话详情 -->
<el-container class="detail-container">
<el-header class="header">
@ -45,22 +36,16 @@
<div class="message-container">
<!-- 情况一消息加载中 -->
<MessageLoading v-if="activeMessageListLoading" />
<!-- 情况二无聊天对话时 -->
<MessageNewConversation
v-if="!activeConversation"
@on-new-conversation="handleConversationCreate"
/>
<!-- 情况三消息列表为空 -->
<MessageListEmpty
v-if="!activeMessageListLoading && messageList.length === 0 && activeConversation"
v-if="!activeMessageListLoading && messageList.length === 0"
@on-prompt="doSendMessage"
/>
<!-- 情况四消息列表不为空 -->
<MessageList
v-if="!activeMessageListLoading && messageList.length > 0"
ref="messageRef"
:conversation="activeConversation"
:list="messageList"
v-model:list="messageList"
@on-delete-success="handleMessageDelete"
@on-edit="handleMessageEdit"
@on-refresh="handleMessageRefresh"
@ -84,25 +69,25 @@
<div class="prompt-btns">
<div>
<el-switch v-model="enableContext" />
<span class="ml-5px text-14px text-#8f8f8f">上下文</span>
<span class="ml-5px text-14px text-#8f8f8f">记忆内容</span>
</div>
<!-- v-if="conversationInProgress == false"-->
<el-button
type="primary"
size="default"
@click="handleSendByButton"
:loading="conversationInProgress"
v-if="conversationInProgress == false"
>
{{ conversationInProgress ? '进行中' : '发送1' }}
</el-button>
<el-button
type="danger"
size="default"
@click="stopStream()"
v-if="conversationInProgress == true"
>
停止
{{ conversationInProgress ? '进行中' : '发送' }}
</el-button>
<!-- <el-button-->
<!-- type="danger"-->
<!-- size="default"-->
<!-- @click="stopStream()"-->
<!-- v-if="conversationInProgress == true"-->
<!-- >-->
<!-- 等待-->
<!-- </el-button>-->
</div>
</form>
</el-footer>
@ -119,29 +104,29 @@
<script setup lang="ts">
import { ChatMessageApi, ChatMessageVO } from '@/api/ai/chat/message'
import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
import ConversationList from './components/conversation/ConversationList.vue'
// import ConversationList from './components/conversation/ConversationList.vue'
import ConversationUpdateForm from './components/conversation/ConversationUpdateForm.vue'
import MessageList from './components/message/MessageList.vue'
import MessageListEmpty from './components/message/MessageListEmpty.vue'
import MessageLoading from './components/message/MessageLoading.vue'
import MessageNewConversation from './components/message/MessageNewConversation.vue'
import { Download, Top } from '@element-plus/icons-vue'
// import MessageNewConversation from './components/message/MessageNewConversation.vue'
// import { Download, Top } from '@element-plus/icons-vue'
import axios from "axios";
import {getUserProfile, ProfileVO} from "@/api/system/user/profile";
import {DeptVO, getDept, getDeptInfo} from "@/api/system/dept";
import { getAccessToken } from '@/utils/auth'
import {getUserProfile} from "@/api/system/user/profile";
import {getDeptInfo} from "@/api/system/dept";
// import { getAccessToken } from '@/utils/auth'
import { config } from '@/config/axios/config'
/** AI 聊天对话 列表 */
defineOptions({ name: 'AiChat' })
const route = useRoute() //
// const route = useRoute() //
const message = useMessage() //
const userid = ref('') //pch add
const userName = ref('') //pch add
const deptfull = ref('') //pch add
const postName = ref('') //pch add
//
const conversationListRef = ref()
// const conversationListRef = ref()
const activeConversationId = ref<number | null>(null) //
const activeConversation = ref<ChatConversationVO | null>(null) // Conversation
const conversationInProgress = ref(false) // true
@ -152,18 +137,19 @@ const activeMessageList = ref<ChatMessageVO[]>([]) // 选中对话的消息列
const activeMessageListLoading = ref<boolean>(false) // activeMessageList
const activeMessageListLoadingTimer = ref<any>() // activeMessageListLoading Timer
//
const textSpeed = ref<number>(50) // Typing speed in milliseconds
const textRoleRunning = ref<boolean>(false) // Typing speed in milliseconds
// const textSpeed = ref<number>(50) // Typing speed in milliseconds
// const textRoleRunning = ref<boolean>(false) // Typing speed in milliseconds
//
const isComposing = ref(false) //
const conversationInAbortController = ref<any>() // abort ( stream )
const inputTimeout = ref<any>() //
const prompt = ref<string>() // prompt
const enableContext = ref<boolean>(true) //
const enableContext = ref<boolean>(true) //
const flag = ref<number>(1);//
// Stream
const receiveMessageFullText = ref('')
const receiveMessageDisplayedText = ref('')
// const receiveMessageFullText = ref('')
// const receiveMessageDisplayedText = ref('')
// =========== ===========
@ -186,43 +172,43 @@ const getConversation = async (id: number | null) => {
* @param conversation 选中的对话
* @return 是否切换成功
*/
const handleConversationClick = async (conversation: ChatConversationVO) => {
//
if (conversationInProgress.value) {
message.alert('对话中,不允许切换!')
return false
}
// id
activeConversationId.value = conversation.id
activeConversation.value = conversation
// message
await getMessageList()
//
scrollToBottom(true)
//
prompt.value = ''
return true
}
// const handleConversationClick = async (conversation: ChatConversationVO) => {
// //
// if (conversationInProgress.value) {
// message.alert('!')
// return false
// }
//
// // id
// activeConversationId.value = conversation.id
// activeConversation.value = conversation
// // message
// await getMessageList()
// //
// scrollToBottom(true)
// //
// prompt.value = ''
// return true
// }
/** 删除某个对话*/
const handlerConversationDelete = async (delConversation: ChatConversationVO) => {
//
if (activeConversationId.value === delConversation.id) {
await handleConversationClear()
}
}
// const handlerConversationDelete = async (delConversation: ChatConversationVO) => {
// //
// if (activeConversationId.value === delConversation.id) {
// await handleConversationClear()
// }
// }
/** 清空选中的对话 */
const handleConversationClear = async () => {
//
if (conversationInProgress.value) {
message.alert('对话中,不允许切换!')
return false
}
activeConversationId.value = null
activeConversation.value = null
activeMessageList.value = []
}
// const handleConversationClear = async () => {
// //
// if (conversationInProgress.value) {
// message.alert('!')
// return false
// }
// activeConversationId.value = null
// activeConversation.value = null
// activeMessageList.value = []
// }
/** 修改聊天对话 */
const conversationUpdateFormRef = ref()
@ -235,15 +221,15 @@ const handleConversationUpdateSuccess = async () => {
}
/** 处理聊天对话的创建成功 */
const handleConversationCreate = async () => {
//
await conversationListRef.value.createConversation()
}
// const handleConversationCreate = async () => {
// //
// await conversationListRef.value.createConversation()
// }
/** 处理聊天对话的创建成功 */
const handleConversationCreateSuccess = async () => {
//
prompt.value = ''
}
// const handleConversationCreateSuccess = async () => {
// //
// prompt.value = ''
// }
// =========== ===========
@ -275,25 +261,63 @@ const getMessageList = async () => {
activeMessageListLoading.value = false
}
}
const messli = ref<{ type: string; content: string }[]>([]);
/** 真正执行【发送】消息操作 */
const doSendMessage = async (content: string) => {
if (conversationInProgress.value){
message.error('发送失败,原因:发送中!')
return
}
if (messli.value.length==0){
activeMessageListLoading.value = true
}
conversationInProgress.value=true
// console.log("content",content)
//
if (!content) {
activeMessageListLoading.value = false
conversationInProgress.value=false
message.error('发送失败,原因:内容为空!')
return
}
messli.value.push({
type: 'user',
content: content
})
//
prompt.value = ''
if (enableContext.value){
flag.value=1
}else if (!enableContext.value){
flag.value=0
}
// console.log("flag",flag.value)
let response
try {
response = await axios.post(`${config.ai_url}/chat?query=${content}&userid=${userid.value}&username=${userName.value}&deptfull=${deptfull.value}&postname=${postName.value}&memoryflag=${flag.value}`);
activeMessageListLoading.value = false
conversationInProgress.value=false
// console.log("response",response)
messli.value.push({
type: 'system',
content: response.data.msg.output
})
}catch (error){
activeMessageListLoading.value = false
conversationInProgress.value=false
console.error("错误: ",error)
}
// console.log("messli",messli.value)
return response.data.msg.output
}
/**
* 消息列表
*
* {@link #getMessageList()} 的差异是 systemMessage 考虑进去
*/
const messageList = computed(() => {
if (activeMessageList.value.length > 0) {
return activeMessageList.value
}
// systemMessage
if (activeConversation.value?.systemMessage) {
return [
{
id: 0,
type: 'system',
content: activeConversation.value.systemMessage
}
]
if (messli.value.length>0){
return messli.value
}
return []
})
@ -389,91 +413,61 @@ const onCompositionend = () => {
}, 200)
}
/** 真正执行【发送】消息操作 */
const doSendMessage = async (content: string) => {
//
if (content.length < 1) {
message.error('发送失败,原因:内容为空!')
return
}
// if (activeConversationId.value == null) {
// message.error('!')
// return
// }
//
prompt.value = ''
//
// const result = await doSendMessageStream({
// userId : userid ,
// userName :userName ,
// content: content,
// deptFull:deptfull,
// postName:postName
// } as ChatMessageVO)
const token = getAccessToken()
alert(userid.value+" "+userName.value+" "+deptfull.value+" "+postName.value)
const response = await axios.post(`${config.ai_url}/chat?query=${content}&token=${token}&userid=${userid.value}&username=${userName.value}&deptfull=${deptfull.value}&postname=${postName.value}&memoryflag=1`);
console.log(response.data.msg.output,"response")
return response.data.msg.output
}
/** 真正执行【发送】消息操作 */
const doSendMessageStream = async (userMessage: ChatMessageVO) => {
// AbortController 便
conversationInAbortController.value = new AbortController()
//
conversationInProgress.value = true
//
receiveMessageFullText.value = ''
try {
// 1.1 stream
activeMessageList.value.push({
id: -1,
conversationId: activeConversationId.value,
type: 'user',
content: userMessage.content,
createTime: new Date()
} as ChatMessageVO)
activeMessageList.value.push({
id: -2,
conversationId: activeConversationId.value,
type: 'assistant',
content: '思考中...',
createTime: new Date()
} as ChatMessageVO)
// 1.2
//await nextTick()
// await scrollToBottom() //
// 1.3
//textRoll()
// 2. event stream
let isFirstChunk = true // chunk
await ChatMessageApi.sendChatMessageStream(
userMessage.conversationId,
userMessage.content,
)
} catch {}
}
// const doSendMessageStream = async (userMessage: ChatMessageVO) => {
//
// // AbortController 便
// conversationInAbortController.value = new AbortController()
// //
// conversationInProgress.value = true
// //
// receiveMessageFullText.value = ''
//
// try {
// // 1.1 stream
// activeMessageList.value.push({
// id: -1,
// conversationId: activeConversationId.value,
// type: 'user',
// content: userMessage.content,
// createTime: new Date()
// } as ChatMessageVO)
// activeMessageList.value.push({
// id: -2,
// conversationId: activeConversationId.value,
// type: 'assistant',
// content: '...',
// createTime: new Date()
// } as ChatMessageVO)
//
// // 1.2
// //await nextTick()
//
// // await scrollToBottom() //
//
// // 1.3
// //textRoll()
//
// // 2. event stream
// // let isFirstChunk = true // chunk
// // await ChatMessageApi.sendChatMessageStream(
// // userMessage.conversationId,
// // userMessage.content,
// //
// // )
// } catch {}
// }
/** 停止 stream 流式调用 */
const stopStream = async () => {
// tip stream message controller
if (conversationInAbortController.value) {
conversationInAbortController.value.abort()
}
// false
conversationInProgress.value = false
}
// const stopStream = async () => {
// // tip stream message controller
// if (conversationInAbortController.value) {
// conversationInAbortController.value.abort()
// }
// // false
// conversationInProgress.value = false
// }
/** 编辑 message设置为 prompt可以再次编辑 */
const handleMessageEdit = (message: ChatMessageVO) => {
@ -496,59 +490,59 @@ const scrollToBottom = async (isIgnore?: boolean) => {
}
/** 自提滚动效果 */
const textRoll = async () => {
let index = 0
try {
//
if (textRoleRunning.value) {
return
}
//
textRoleRunning.value = true
receiveMessageDisplayedText.value = ''
const task = async () => {
//
const diff =
(receiveMessageFullText.value.length - receiveMessageDisplayedText.value.length) / 10
if (diff > 5) {
textSpeed.value = 10
} else if (diff > 2) {
textSpeed.value = 30
} else if (diff > 1.5) {
textSpeed.value = 50
} else {
textSpeed.value = 100
}
// 30
if (!conversationInProgress.value) {
textSpeed.value = 10
}
if (index < receiveMessageFullText.value.length) {
receiveMessageDisplayedText.value += receiveMessageFullText.value[index]
index++
// message
const lastMessage = activeMessageList.value[activeMessageList.value.length - 1]
lastMessage.content = receiveMessageDisplayedText.value
//
await scrollToBottom()
//
timer = setTimeout(task, textSpeed.value)
} else {
//
if (!conversationInProgress.value) {
textRoleRunning.value = false
clearTimeout(timer)
} else {
//
timer = setTimeout(task, textSpeed.value)
}
}
}
let timer = setTimeout(task, textSpeed.value)
} catch {}
}
// const textRoll = async () => {
// let index = 0
// try {
// //
// if (textRoleRunning.value) {
// return
// }
// //
// textRoleRunning.value = true
// receiveMessageDisplayedText.value = ''
// const task = async () => {
// //
// const diff =
// (receiveMessageFullText.value.length - receiveMessageDisplayedText.value.length) / 10
// if (diff > 5) {
// textSpeed.value = 10
// } else if (diff > 2) {
// textSpeed.value = 30
// } else if (diff > 1.5) {
// textSpeed.value = 50
// } else {
// textSpeed.value = 100
// }
// // 30
// if (!conversationInProgress.value) {
// textSpeed.value = 10
// }
//
// if (index < receiveMessageFullText.value.length) {
// receiveMessageDisplayedText.value += receiveMessageFullText.value[index]
// index++
//
// // message
// const lastMessage = activeMessageList.value[activeMessageList.value.length - 1]
// lastMessage.content = receiveMessageDisplayedText.value
// //
// await scrollToBottom()
// //
// timer = setTimeout(task, textSpeed.value)
// } else {
// //
// if (!conversationInProgress.value) {
// textRoleRunning.value = false
// clearTimeout(timer)
// } else {
// //
// timer = setTimeout(task, textSpeed.value)
// }
// }
// }
// let timer = setTimeout(task, textSpeed.value)
// } catch {}
// }
/*得到当前访问的用户信息pch add*/
const getUserInfo = async () => {