Commit 1a6afa32 by YunaiV

【代码优化】AI:聊天对话 index.vue 代码梳理 40%(message 部分)

parent 2b078911
...@@ -12,6 +12,7 @@ export interface ChatConversationVO { ...@@ -12,6 +12,7 @@ export interface ChatConversationVO {
temperature: number // 温度参数 temperature: number // 温度参数
maxTokens: number // 单条回复的最大 Token 数量 maxTokens: number // 单条回复的最大 Token 数量
maxContexts: number // 上下文的最大 Message 数量 maxContexts: number // 上下文的最大 Message 数量
createTime?: Date // 创建时间
// 额外字段 // 额外字段
systemMessage?: string // 角色设定 systemMessage?: string // 角色设定
modelName?: string // 模型名字 modelName?: string // 模型名字
......
<template> <template>
<div ref="messageContainer" style="height: 100%; overflow-y: auto; position: relative"> <div ref="messageContainer" class="h-100% overflow-y relative">
<div class="chat-list" v-for="(item, index) in list" :key="index"> <div class="chat-list" v-for="(item, index) in list" :key="index">
<!-- 靠左 message --> <!-- 靠左 message:system、assistant 类型 -->
<div class="left-message message-item" v-if="item.type !== 'user'"> <div class="left-message message-item" v-if="item.type !== 'user'">
<div class="avatar"> <div class="avatar">
<el-avatar :src="roleAvatar" /> <el-avatar :src="roleAvatar" />
...@@ -14,16 +14,16 @@ ...@@ -14,16 +14,16 @@
<MarkdownView class="left-text" :content="item.content" /> <MarkdownView class="left-text" :content="item.content" />
</div> </div>
<div class="left-btns"> <div class="left-btns">
<el-button class="btn-cus" link @click="noCopy(item.content)"> <el-button class="btn-cus" link @click="copyContent(item.content)">
<img class="btn-image" src="@/assets/ai/copy.svg" /> <img class="btn-image" src="@/assets/ai/copy.svg" />
</el-button> </el-button>
<el-button v-if="item.id > 0" class="btn-cus" link @click="onDelete(item.id)"> <el-button v-if="item.id > 0" class="btn-cus" link @click="onDelete(item.id)">
<img class="btn-image" src="@/assets/ai/delete.svg" style="height: 17px" /> <img class="btn-image h-17px" src="@/assets/ai/delete.svg" />
</el-button> </el-button>
</div> </div>
</div> </div>
</div> </div>
<!-- 靠右 message --> <!-- 靠右 message:user 类型 -->
<div class="right-message message-item" v-if="item.type === 'user'"> <div class="right-message message-item" v-if="item.type === 'user'">
<div class="avatar"> <div class="avatar">
<el-avatar :src="userAvatar" /> <el-avatar :src="userAvatar" />
...@@ -36,15 +36,11 @@ ...@@ -36,15 +36,11 @@
<div class="right-text">{{ item.content }}</div> <div class="right-text">{{ item.content }}</div>
</div> </div>
<div class="right-btns"> <div class="right-btns">
<el-button class="btn-cus" link @click="noCopy(item.content)"> <el-button class="btn-cus" link @click="copyContent(item.content)">
<img class="btn-image" src="@/assets/ai/copy.svg" /> <img class="btn-image" src="@/assets/ai/copy.svg" />
</el-button> </el-button>
<el-button class="btn-cus" link @click="onDelete(item.id)"> <el-button class="btn-cus" link @click="onDelete(item.id)">
<img <img class="btn-image h-17px mr-12px" src="@/assets/ai/delete.svg" />
class="btn-image"
src="@/assets/ai/delete.svg"
style="height: 17px; margin-right: 12px"
/>
</el-button> </el-button>
<el-button class="btn-cus" link @click="onRefresh(item)"> <el-button class="btn-cus" link @click="onRefresh(item)">
<el-icon size="17"><RefreshRight /></el-icon> <el-icon size="17"><RefreshRight /></el-icon>
...@@ -63,23 +59,25 @@ ...@@ -63,23 +59,25 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { PropType } from 'vue'
import { formatDate } from '@/utils/formatTime' import { formatDate } from '@/utils/formatTime'
import MarkdownView from '@/components/MarkdownView/index.vue' import MarkdownView from '@/components/MarkdownView/index.vue'
import { ChatMessageApi, ChatMessageVO } from '@/api/ai/chat/message'
import { useClipboard } from '@vueuse/core' import { useClipboard } from '@vueuse/core'
import { PropType } from 'vue'
import { ArrowDownBold, Edit, RefreshRight } from '@element-plus/icons-vue' 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 { ChatConversationVO } from '@/api/ai/chat/conversation'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import userAvatarDefaultImg from '@/assets/imgs/avatar.gif' import userAvatarDefaultImg from '@/assets/imgs/avatar.gif'
import roleAvatarDefaultImg from '@/assets/ai/gpt.svg' import roleAvatarDefaultImg from '@/assets/ai/gpt.svg'
const message = useMessage() // 消息弹窗
const { copy } = useClipboard() // 初始化 copy 到粘贴板 const { copy } = useClipboard() // 初始化 copy 到粘贴板
// 判断 消息列表 滚动的位置(用于判断是否需要滚动到消息最下方) const userStore = useUserStore()
// 判断“消息列表”滚动的位置(用于判断是否需要滚动到消息最下方)
const messageContainer: any = ref(null) const messageContainer: any = ref(null)
const isScrolling = ref(false) //用于判断用户是否在滚动 const isScrolling = ref(false) //用于判断用户是否在滚动
const userStore = useUserStore()
const userAvatar = computed(() => userStore.user.avatar ?? userAvatarDefaultImg) const userAvatar = computed(() => userStore.user.avatar ?? userAvatarDefaultImg)
const roleAvatar = computed(() => props.conversation.roleAvatar ?? roleAvatarDefaultImg) const roleAvatar = computed(() => props.conversation.roleAvatar ?? roleAvatarDefaultImg)
...@@ -95,12 +93,16 @@ const props = defineProps({ ...@@ -95,12 +93,16 @@ const props = defineProps({
} }
}) })
const { list } = toRefs(props) // 消息列表
const emits = defineEmits(['onDeleteSuccess', 'onRefresh', 'onEdit']) // 定义 emits
// ============ 处理对话滚动 ============== // ============ 处理对话滚动 ==============
/** 滚动到底部 */
const scrollToBottom = async (isIgnore?: boolean) => { const scrollToBottom = async (isIgnore?: boolean) => {
// 注意要使用 nextTick 以免获取不到dom
await nextTick(() => { await nextTick(() => {
// TODO @fan:中文写作习惯,中英文之间要有空格;另外,nextick 哈,idea 如果有绿色波兰线,可以关注下
//注意要使用nexttick以免获取不到dom
if (isIgnore || !isScrolling.value) { if (isIgnore || !isScrolling.value) {
messageContainer.value.scrollTop = messageContainer.value.scrollTop =
messageContainer.value.scrollHeight - messageContainer.value.offsetHeight messageContainer.value.scrollHeight - messageContainer.value.offsetHeight
...@@ -122,75 +124,48 @@ function handleScroll() { ...@@ -122,75 +124,48 @@ function handleScroll() {
} }
} }
/** /** 回到底部 */
* 复制 const handleGoBottom = async () => {
*/ const scrollContainer = messageContainer.value
const noCopy = async (content) => { scrollContainer.scrollTop = scrollContainer.scrollHeight
copy(content) }
ElMessage({
message: '复制成功!', /** 回到顶部 */
type: 'success' const handlerGoTop = async () => {
}) const scrollContainer = messageContainer.value
scrollContainer.scrollTop = 0
}
defineExpose({ scrollToBottom, handlerGoTop }) // 提供方法给 parent 调用
// ============ 处理消息操作 ==============
/** 复制 */
const copyContent = async (content) => {
await copy(content)
message.success('复制成功!')
} }
/** /** 删除 */
* 删除
*/
const onDelete = async (id) => { const onDelete = async (id) => {
// 删除 message // 删除 message
await ChatMessageApi.deleteChatMessage(id) await ChatMessageApi.deleteChatMessage(id)
ElMessage({ message.success('删除成功!')
message: '删除成功!',
type: 'success'
})
// 回调 // 回调
emits('onDeleteSuccess') emits('onDeleteSuccess')
} }
/** /** 刷新 */
* 刷新
*/
const onRefresh = async (message: ChatMessageVO) => { const onRefresh = async (message: ChatMessageVO) => {
emits('onRefresh', message) emits('onRefresh', message)
} }
/** /** 编辑 */
* 编辑
*/
const onEdit = async (message: ChatMessageVO) => { const onEdit = async (message: ChatMessageVO) => {
emits('onEdit', message) emits('onEdit', message)
} }
/** /** 初始化 */
* 回到底部
*/
const handleGoBottom = async () => {
const scrollContainer = messageContainer.value
scrollContainer.scrollTop = scrollContainer.scrollHeight
}
/**
* 回到顶部
*/
const handlerGoTop = async () => {
const scrollContainer = messageContainer.value
scrollContainer.scrollTop = 0
}
// 监听 list
// TODO @fan:这个木有,是不是删除啦
const { list, conversationId } = toRefs(props)
watch(list, async (newValue, oldValue) => {
console.log('watch list', list)
})
// 提供方法给 parent 调用
defineExpose({ scrollToBottom, handlerGoTop })
// 定义 emits
const emits = defineEmits(['onDeleteSuccess', 'onRefresh', 'onEdit'])
// onMounted
onMounted(async () => { onMounted(async () => {
messageContainer.value.addEventListener('scroll', handleScroll) messageContainer.value.addEventListener('scroll', handleScroll)
}) })
...@@ -199,15 +174,7 @@ onMounted(async () => { ...@@ -199,15 +174,7 @@ onMounted(async () => {
<style scoped lang="scss"> <style scoped lang="scss">
.message-container { .message-container {
position: relative; position: relative;
//top: 0;
//bottom: 0;
//left: 0;
//right: 0;
//width: 100%;
//height: 100%;
overflow-y: scroll; overflow-y: scroll;
//padding: 0 15px;
//z-index: -1;
} }
// 中间 // 中间
...@@ -231,11 +198,6 @@ onMounted(async () => { ...@@ -231,11 +198,6 @@ onMounted(async () => {
justify-content: flex-start; justify-content: flex-start;
} }
.avatar {
//height: 170px;
//width: 170px;
}
.message { .message {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
...@@ -272,7 +234,6 @@ onMounted(async () => { ...@@ -272,7 +234,6 @@ onMounted(async () => {
color: #fff; color: #fff;
display: inline; display: inline;
background-color: #267fff; background-color: #267fff;
color: #fff;
box-shadow: 0 0 0 1px #267fff; box-shadow: 0 0 0 1px #267fff;
border-radius: 10px; border-radius: 10px;
padding: 10px; padding: 10px;
......
<!-- 消息列表为空时,展示 prompt 列表 -->
<template> <template>
<div class="chat-empty"> <div class="chat-empty">
<!-- title -->
<!-- title -->
<div class="center-container"> <div class="center-container">
<div class="title"> AI</div> <div class="title"> AI</div>
<div class="role-list"> <div class="role-list">
<div class="role-item" v-for="prompt in promptList" :key="prompt.prompt" @click="handlerPromptClick(prompt)"> <div
{{prompt.prompt}} class="role-item"
v-for="prompt in promptList"
:key="prompt.prompt"
@click="handlerPromptClick(prompt)"
>
{{ prompt.prompt }}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const promptList = [
const promptList = ref<any[]>() // 角色列表
promptList.value = [
{ {
"prompt": "今天气怎么样?", prompt: '今天气怎么样?'
}, },
{ {
"prompt": "写一首好听的诗歌?", prompt: '写一首好听的诗歌?'
} }
] ] // prompt 列表
const emits = defineEmits(['onPrompt']) const emits = defineEmits(['onPrompt'])
/** 选中 prompt 点击 */
const handlerPromptClick = async ({ prompt }) => { const handlerPromptClick = async ({ prompt }) => {
emits('onPrompt', prompt) emits('onPrompt', prompt)
} }
......
...@@ -9,13 +9,10 @@ ...@@ -9,13 +9,10 @@
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const emits = defineEmits(['onNewConversation']) const emits = defineEmits(['onNewConversation'])
/** /** 新建 conversation 聊天对话 */
* 新建 conversation 聊天对话
*/
const handlerNewChat = () => { const handlerNewChat = () => {
emits('onNewConversation') emits('onNewConversation')
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment