| | |
| | | <!-- AI 对话 --> |
| | | <template> |
| | | <el-aside width="260px" class="conversation-container h-100%"> |
| | | <el-aside width="280px" class="conversation-container h-100%"> |
| | | <!-- 左顶部:对话 --> |
| | | <div class="h-100%"> |
| | | <div class="h-80%"> |
| | | <div class="conversation-title"> |
| | | <img |
| | | src="@/assets/ai/zhuanlu/conversation_big.png" |
| | |
| | | 对话列表 |
| | | </div> |
| | | <!-- <hr class="line"/>--> |
| | | <el-button class="w-1/1 btn-new-conversation" type="primary" @click="createConversation"> |
| | | <el-button class="w-1/1 btn-new-conversation" type="primary" @click="createNewConversation"> |
| | | <img |
| | | src="@/assets/ai/zhuanlu/conversation_big.png" |
| | | class="mr-8px w-[1.5em] h-[1.5em]" |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- 底部占位 --> |
| | | <div class="h-160px w-100%"></div> |
| | | </div> |
| | | </div> |
| | | |
| | |
| | | import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation' |
| | | import { Bottom, Top } from '@element-plus/icons-vue' |
| | | import roleAvatarDefaultImg from '@/assets/ai/zhuanlu/assistant.png' |
| | | import {ChatMessageVO} from "@/api/ai/chat/message"; |
| | | import {formatToDate, formatToDateTime} from "@/utils/dateUtil"; |
| | | |
| | | const message = useMessage() // 消息弹窗 |
| | | |
| | | // 定义属性 |
| | | const searchName = ref<string>('') // 对话搜索 |
| | | const modelName = ref<string>('common') // 对话搜索 |
| | | const activeConversationId = ref<number | null>(null) // 选中的对话,默认为 null |
| | | const hoverConversationId = ref<number | null>(null) // 悬浮上去的对话 |
| | | const conversationList = ref([] as ChatConversationVO[]) // 对话列表 |
| | |
| | | activeId: { |
| | | type: String || null, |
| | | required: true |
| | | } |
| | | }, |
| | | modelName: { |
| | | type: String || null, |
| | | required: true |
| | | }, |
| | | quickAccess: { |
| | | type: Boolean || null, |
| | | required: true |
| | | }, |
| | | defaultMessage: {} |
| | | }) |
| | | |
| | | // 定义钩子 |
| | |
| | | }, 50) |
| | | |
| | | // 1.1 获取 对话数据 |
| | | conversationList.value = await ChatConversationApi.getChatConversationEnergyList(modelName.value) |
| | | if(conversationList.value.length == 0) { |
| | | await createConversation() |
| | | } |
| | | conversationList.value = await ChatConversationApi.getChatConversationEnergyList(props.modelName) |
| | | // 1.2 排序 |
| | | conversationList.value.sort((a, b) => { |
| | | return b.createTime - a.createTime |
| | |
| | | return |
| | | } |
| | | |
| | | // 2. 对话根据时间分组(置顶、今天、一天前、三天前、七天前、30 天前) |
| | | // 2. 对话根据时间分组(置顶、今天、昨天、三天前、七天前、30 天前) |
| | | conversationMap.value = await getConversationGroupByCreateTime(conversationList.value) |
| | | } finally { |
| | | // 清理定时器 |
| | |
| | | const groupMap = { |
| | | 置顶: [], |
| | | 今天: [], |
| | | 一天前: [], |
| | | 昨天: [], |
| | | 三天前: [], |
| | | 七天前: [], |
| | | 三十天前: [] |
| | |
| | | const now = Date.now() |
| | | // 定义时间间隔常量(单位:毫秒) |
| | | const oneDay = 24 * 60 * 60 * 1000 |
| | | const threeDays = 3 * oneDay |
| | | const sevenDays = 7 * oneDay |
| | | const thirtyDays = 30 * oneDay |
| | | //今天 |
| | | const today = formatToDate(new Date()) |
| | | const yesterday = formatToDate(new Date().setDate(new Date().getDate() - 1)) |
| | | for (const conversation of list) { |
| | | // 置顶 |
| | | if (conversation.pinned) { |
| | |
| | | } |
| | | // 计算时间差(单位:毫秒) |
| | | const diff = now - conversation.createTime |
| | | let conversationDate = formatToDate(conversation.createTime) |
| | | let titleDate = conversation.title.split(' ')[0] |
| | | // 根据时间间隔判断 |
| | | if (diff < oneDay) { |
| | | if (titleDate == today) { |
| | | groupMap['今天'].push(conversation) |
| | | } else if (diff < threeDays) { |
| | | groupMap['一天前'].push(conversation) |
| | | } else if (titleDate == yesterday) { |
| | | groupMap['昨天'].push(conversation) |
| | | } else if (diff < sevenDays) { |
| | | groupMap['三天前'].push(conversation) |
| | | } else if (diff < thirtyDays) { |
| | |
| | | } |
| | | |
| | | /** 新建对话 */ |
| | | const createConversation = async () => { |
| | | const createNewConversation = async () => { |
| | | await createConversation(null) |
| | | } |
| | | |
| | | /** 新建对话 */ |
| | | const createConversation = async (title: String) => { |
| | | // 1. 新建对话 |
| | | const conversationId = await ChatConversationApi.createChatConversationEnergy( |
| | | {modelName: modelName.value} as unknown as ChatConversationVO |
| | | {modelName: props.modelName, title: title} as unknown as ChatConversationVO |
| | | ) |
| | | // 2. 获取对话内容 |
| | | await getChatConversationList() |
| | | // 3. 选中对话 |
| | | await handleConversationClick(conversationId) |
| | | // 4. 回调 |
| | | emits('onConversationCreate') |
| | | // // 4. 回调 |
| | | // emits('onConversationCreate') |
| | | } |
| | | |
| | | /** 修改对话的标题 */ |
| | |
| | | onMounted(async () => { |
| | | // 获取 对话列表 |
| | | await getChatConversationList() |
| | | // 默认选中 |
| | | if (props.activeId) { |
| | | activeConversationId.value = props.activeId |
| | | } else { |
| | | // 首次默认选中第一个 |
| | | if(!props.quickAccess) { |
| | | // 默认选中 |
| | | if (props.activeId) { |
| | | activeConversationId.value = props.activeId |
| | | } else { |
| | | // 首次默认选中第一个 |
| | | if (conversationList.value.length) { |
| | | activeConversationId.value = conversationList.value[0].id |
| | | // 回调 onConversationClick |
| | | await emits('onConversationClick', conversationList.value[0]) |
| | | } |
| | | } |
| | | } else if(props.defaultMessage) { |
| | | let tempTitle = formatToDateTime(new Date(props.defaultMessage.createTime)) |
| | | if (conversationList.value.length) { |
| | | activeConversationId.value = conversationList.value[0].id |
| | | // 回调 onConversationClick |
| | | await emits('onConversationClick', conversationList.value[0]) |
| | | conversationList.value.forEach((item) => { |
| | | if(item.title === tempTitle) { |
| | | activeConversationId.value = item.id |
| | | // 回调 onConversationClick |
| | | emits('onConversationClick', item) |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | }) |
| | |
| | | } |
| | | |
| | | .conversation-list { |
| | | overflow: auto; |
| | | overflow-y: auto; |
| | | height: 100%; |
| | | margin-top: 10px; |
| | | /* Firefox */ |
| | | scrollbar-width: thin; |
| | | scrollbar-color: rgba(0, 0, 0, 0.15) transparent; |
| | | |
| | | /* WebKit */ |
| | | &::-webkit-scrollbar { |
| | | width: 6px; |
| | | background: transparent; |
| | | } |
| | | &::-webkit-scrollbar-thumb { |
| | | border-radius: 4px; |
| | | background: rgba(0, 0, 0, 0.15); |
| | | transition: background 0.3s; |
| | | &:hover { background: rgba(0, 0, 0, 0.25); } |
| | | } |
| | | .classify-title { |
| | | padding-top: 10px; |
| | | b { |
| | | color: white; |
| | | } |
| | | } |
| | | |
| | | .conversation-item { |
| | | margin-top: 5px; |
| | | } |
| | |
| | | } |
| | | |
| | | .title { |
| | | padding: 2px 10px; |
| | | padding: 2px 5px; |
| | | max-width: 220px; |
| | | font-size: 14px; |
| | | font-weight: 400; |
| | | font-size: 13px; |
| | | color: rgba(0, 0, 0, 0.77); |
| | | overflow: hidden; |
| | | white-space: nowrap; |
| | |
| | | } |
| | | } |
| | | |
| | | // 角色仓库、清空未设置对话 |
| | | // 清空未设置对话 |
| | | .tool-box { |
| | | bottom: 0; |
| | | display: flex; |
| | | padding: 0 20px; |
| | | width: 90%; |
| | | margin-left: 5%; |
| | | background-color: rgba(69,133,255,0.2); |
| | | box-shadow: 0 0 1px 1px rgba(69,133,255,0.4); |
| | | line-height: 35px; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | color: var(--el-text-color); |
| | | |
| | | border-radius: 2px; |
| | | div { |
| | | display: flex; |
| | | margin-left: 20%; |