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

pull/474/MERGE
YunaiV 2024-07-08 13:00:24 +08:00
parent 2b0789112f
commit 1a6afa3263
4 changed files with 65 additions and 102 deletions

View File

@ -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 // 模型名字

View File

@ -1,7 +1,7 @@
<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 --> <!-- 靠左 messagesystemassistant 类型 -->
<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 @@
<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 --> <!-- 靠右 messageuser 类型 -->
<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 @@
<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 @@
</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({
} }
}) })
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 @fannextick idea 绿线
//使nexttickdom
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() {
} }
} }
/** /** 回到底部 */
* 复制
*/
const noCopy = async (content) => {
copy(content)
ElMessage({
message: '复制成功!',
type: 'success'
})
}
/**
* 删除
*/
const onDelete = async (id) => {
// message
await ChatMessageApi.deleteChatMessage(id)
ElMessage({
message: '删除成功!',
type: 'success'
})
//
emits('onDeleteSuccess')
}
/**
* 刷新
*/
const onRefresh = async (message: ChatMessageVO) => {
emits('onRefresh', message)
}
/**
* 编辑
*/
const onEdit = async (message: ChatMessageVO) => {
emits('onEdit', message)
}
/**
* 回到底部
*/
const handleGoBottom = async () => { const handleGoBottom = async () => {
const scrollContainer = messageContainer.value const scrollContainer = messageContainer.value
scrollContainer.scrollTop = scrollContainer.scrollHeight scrollContainer.scrollTop = scrollContainer.scrollHeight
} }
/** /** 回到顶部 */
* 回到顶部
*/
const handlerGoTop = async () => { const handlerGoTop = async () => {
const scrollContainer = messageContainer.value const scrollContainer = messageContainer.value
scrollContainer.scrollTop = 0 scrollContainer.scrollTop = 0
} }
// list defineExpose({ scrollToBottom, handlerGoTop }) // parent
// 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']) const copyContent = async (content) => {
await copy(content)
message.success('复制成功!')
}
// onMounted /** 删除 */
const onDelete = async (id) => {
// message
await ChatMessageApi.deleteChatMessage(id)
message.success('删除成功!')
//
emits('onDeleteSuccess')
}
/** 刷新 */
const onRefresh = async (message: ChatMessageVO) => {
emits('onRefresh', message)
}
/** 编辑 */
const onEdit = async (message: ChatMessageVO) => {
emits('onEdit', message)
}
/** 初始化 */
onMounted(async () => { onMounted(async () => {
messageContainer.value.addEventListener('scroll', handleScroll) messageContainer.value.addEventListener('scroll', handleScroll)
}) })
@ -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 () => {
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 () => {
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;

View File

@ -1,31 +1,35 @@
<!-- 消息列表为空时展示 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)
} }

View File

@ -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')
} }