aigc-h5/src/views/chat/components/content.vue

345 lines
9.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<LayoutContent>
<div class="flex flex-col w-full h-full">
<ScrollContainer id="scrollRef" ref="scrollRef">
<div class="p-4">
<MessageGroup
v-for="(item, i) in dataSources"
:key="i"
:arr="item"
></MessageGroup>
</div>
</ScrollContainer>
<div class="p-2">
<div class="border border-[#414548]">
<div class="border-b border-[#414548] h-9 flex items-center px-5">
<SvgIcon
@click="handleRefresh"
name="refresh"
class="text-white text-base cursor-pointer mr-6"
></SvgIcon>
<SvgIcon
@click="handleStop"
name="stop"
class="text-white text-base cursor-pointer mr-6"
></SvgIcon>
</div>
<a-textarea
ref="inputRef"
class="bg-transparent border-none rounded-none text-white minp focus:shadow-none placeholder-[#FFFFFF40]"
placeholder="请输入内容"
:rows="4"
v-model:value="inputValue"
@pressEnter="handleEnter"
/>
<div class="flex pb-5 px-5 pt-4 justify-between">
<div class="flex items-end">
<a-button
@click="handleClear"
type="primary"
class="rounded-4px h-9.5 px-6 !bg-[#414548] mr-2.5 border-[#414548] text-white"
>
<template #icon>
<SvgIcon name="delete" class="text-lg mr-2" />
</template>
清除</a-button
>
<a-button type="primary" class="rounded-4px h-9.5 px-6">
<template #icon>
<SvgIcon name="cloud-upload" class="text-lg mr-2" />
</template>
上传</a-button
>
<div class="ml-2">
<span class="text-[#EC4B4B] text-base">*</span>
<span class="opacity-40 text-xs">
.doc.docx.pdf20M
</span>
</div>
</div>
<div>
<a-button
@click="handleSubmit"
type="primary"
class="rounded-4px h-9.5 px-6"
>
<template #icon>
<SvgIcon name="generated" class="text-sm mr-2" />
</template>
生成</a-button
>
</div>
</div>
</div>
</div>
</div>
</LayoutContent>
</template>
<script setup>
import { Layout } from 'ant-design-vue'
import MessageGroup from './message-group.vue'
import { ref, computed, onMounted, nextTick, onBeforeMount,getCurrentInstance } from 'vue'
import ScrollContainer from '@/components/ScrollContainer/index.vue'
import http from '@/io/request'
import { v4 as uuidv4 } from 'uuid'
import { useRoute } from 'vue-router'
import { useChat } from '@/stores'
const { proxy } = getCurrentInstance();
let controller = new AbortController()
const inputRef = ref(null)
const LayoutContent = Layout.Content
const route = useRoute()
const { uuid } = route.params
const chatStore = useChat()
const scrollRef = ref(null)
const loading = ref(false)
const inputValue = ref('')
const currentChart = computed(() => chatStore.getCurrentChat)
const conversationList = computed(() => {
return chatStore.getCurrentChat.filter((item) => {
const key = Object.keys(item)[0]
const value = item[key]
return !value.inversion
})
})
const conversationUserList = computed(() => {
return chatStore.getCurrentChat.filter((item) => {
const key = Object.keys(item)[0]
const value = item[key]
return value.inversion
})
})
const dataSources = computed(() => {
const arr = chatStore.getCurrentChat
return groupDataByParentId(arr)
})
function groupDataByParentId(data) {
return data.reduce((groupedData, item) => {
const itemId = Object.keys(item)[0]
const itemData = item[itemId]
if (itemData.parent_id === '') {
groupedData[itemId] = []
} else {
const parentItemId = itemData.parent_id
groupedData[parentItemId] = groupedData[parentItemId] || []
groupedData[parentItemId].push(itemData)
}
return groupedData
}, {})
}
function handleRefresh() {
if (currentChart.value.length && !loading.value) onConversation('variant')
}
function handleStop() {
if (loading.value) {
controller.abort()
loading.value = false
}
}
function handleClear() {
inputValue.value = ''
}
function handleSubmit() {
onConversation()
}
async function onConversation(action = 'next') {
let message = inputValue.value
if (loading.value) return
if ((!message || message.trim() === '') && action == 'next') return
controller = new AbortController()
const params = {
action: action,
conversation_id: chatStore.active,
message: {
text: message,
id: uuidv4(),
},
}
if (action == 'next') {
const lastContext =
conversationList.value[conversationList.value.length - 1]
params.parent_message_id = uuidv4()
if (lastContext) {
params.parent_message_id = lastContext[Object.keys(lastContext)[0]]?.id
}
chatStore.addChatByUuid(chatStore.active, {
text: message,
inversion: true,
parent_id: params.parent_message_id || '',
id: params.message.id,
})
scrollToBottom()
chatStore.addChatByUuid(chatStore.active, {
text: '⋯',
id: uuidv4(),
parent_id: params.message.id,
inversion: false,
})
}
if (action == 'variant') {
const lastUserContext =
conversationUserList.value[conversationUserList.value.length - 1]
if (lastUserContext) {
const obj = lastUserContext[Object.keys(lastUserContext)[0]]
params.message.text = obj.text
params.message.id = obj.id
params.parent_message_id = obj.id
}
chatStore.addChatByUuid(chatStore.active, {
text: '⋯',
id: uuidv4(),
parent_id: params.parent_message_id,
inversion: false,
})
}
loading.value = true
inputValue.value = ''
scrollToBottom()
let tempMessage_id = null
try {
const fetchChatAPIOnce = async () => {
await http.post('/api/v1/conversation', params, {
signal: controller.signal,
requestBaseUrl: 'chat',
onDownloadProgress: async ({ event }) => {
const xhr = event.target
const { responseText } = xhr
if (xhr.status == 200) {
const arr = parseEventMessages(responseText)
const { conversation_id, message_id } = arr[0]
tempMessage_id = message_id
const msg = arr.reduce((acc, item) => {
return acc + item.text
}, '')
chatStore.updateChatByUuid(
conversation_id,
chatStore.getCurrentChat.length - 1,
{
[message_id]: {
conversation_id: conversation_id,
id: message_id,
text: msg,
inversion: false,
loading: true,
parent_id: params.message.id || '',
},
}
)
scrollToBottomIfAtBottom()
}
},
})
chatStore.updateChatSome(chatStore.getCurrentChat.length - 1, {
loading: false,
})
await chatStore.getChat(chatStore.active)
}
await fetchChatAPIOnce()
} catch (error) {
if (error.message === 'canceled') {
chatStore.updateChatSome(chatStore.getCurrentChat.length - 1, {
loading: false,
})
return
}
const errorMessage = error?.errmsg ?? '好像出错了,请稍后再试。'
chatStore.updateChatSome(chatStore.getCurrentChat.length - 1, {
loading: false,
text: errorMessage,
})
} finally {
loading.value = false
}
}
function parseEventMessages(dataString) {
const lines = dataString.trim().split('\n')
const eventMessages = []
let currentEvent = {}
for (const line of lines) {
if (line.startsWith('event:message')) {
if (Object.keys(currentEvent).length > 0) {
eventMessages.push(currentEvent)
currentEvent = {}
}
} else if (line.startsWith('data:')) {
const jsonData = line.substring('data:'.length)
currentEvent = JSON.parse(jsonData)
}
}
if (Object.keys(currentEvent).length > 0) {
eventMessages.push(currentEvent)
}
return eventMessages
}
function handleEnter(event) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault()
handleSubmit()
}
}
function handleDataChange(data) {
inputValue.value = data
}
onMounted(async () => {
proxy.$mitt.on('temp-copy', handleDataChange)
if (inputRef.value) {
inputRef.value.focus()
}
await chatStore.getChat(uuid)
scrollToBottom()
})
function scrollToBottom() {
nextTick()
scrollRef.value.scrollBottom()
}
function scrollToBottomIfAtBottom() {
nextTick()
scrollRef.value.scrollToBottomIfAtBottom()
}
</script>
<style lang="scss" scoped>
.scroll-smooth {
scroll-behavior: smooth;
}
</style>