Commit f03284ec authored by nick zheng's avatar nick zheng

feat: ai生成应用头像及追问问题

parent e02481b6
...@@ -69,6 +69,24 @@ export function fetchCreateDialogues<T>(agentId: string) { ...@@ -69,6 +69,24 @@ export function fetchCreateDialogues<T>(agentId: string) {
return request.post<T>(`/agentApplicationRest/createDialogues.json?agentId=${agentId}`) return request.post<T>(`/agentApplicationRest/createDialogues.json?agentId=${agentId}`)
} }
/**
* * @param { input: AI回答内容 }
* @returns 生成追问问题
*/
export function fetchCreateContinueQuestions<T>(payload: { input: string }) {
return request.post<T>('/agentApplicationRest/createContinueQuestions.json', payload)
}
/**
* * @param { agentTitle: 标题 agentDesc: 描述 }
* @returns AI生成应用头像
*/
export function fetchCreateAgentAvatar<T>(payload: { agentTitle: string; agentDesc: string }) {
return request.post<T>('/agentApplicationInfoRest/createAgentApplicationAvatar.json', payload, {
timeout: 120000,
})
}
/** /**
* * @param { agentTitle: 标题 agentDesc: 描述 } * * @param { agentTitle: 标题 agentDesc: 描述 }
* @returns AI生成开场白 * @returns AI生成开场白
......
...@@ -5,10 +5,11 @@ import { fetchUpload } from '@/apis/upload' ...@@ -5,10 +5,11 @@ import { fetchUpload } from '@/apis/upload'
interface Emit { interface Emit {
(e: 'afterUpload', data: UploadFileInfo[]): void (e: 'afterUpload', data: UploadFileInfo[]): void
(e: 'remove', data: number): void
} }
const props = defineProps({ const props = defineProps({
saveFileArr: { fileArr: {
type: Array, type: Array,
default: () => { default: () => {
return [] return []
...@@ -34,10 +35,9 @@ const fileList = ref<UploadFileInfo[]>([]) ...@@ -34,10 +35,9 @@ const fileList = ref<UploadFileInfo[]>([])
const previewImageUrl = ref('') const previewImageUrl = ref('')
const uploadPhotoRef = ref() const uploadPhotoRef = ref()
const imageRef = ref() const imageRef = ref()
const showImage = false
watch( watch(
() => props.saveFileArr, () => props.fileArr,
(newValue) => { (newValue) => {
fileList.value = [] fileList.value = []
if (newValue?.length) { if (newValue?.length) {
...@@ -99,6 +99,10 @@ function handlePreview(file: UploadFileInfo) { ...@@ -99,6 +99,10 @@ function handlePreview(file: UploadFileInfo) {
imageRef.value.click() imageRef.value.click()
}) })
} }
function handleRemove(options: { file: UploadFileInfo; fileList: UploadFileInfo[]; index: number }) {
emit('remove', options.index)
}
</script> </script>
<template> <template>
...@@ -109,14 +113,7 @@ function handlePreview(file: UploadFileInfo) { ...@@ -109,14 +113,7 @@ function handlePreview(file: UploadFileInfo) {
:list-type="props.fileType" :list-type="props.fileType"
@change="handleUpload" @change="handleUpload"
@preview="handlePreview" @preview="handlePreview"
/> @remove="handleRemove"
>
<NImage </NUpload>
v-show="showImage"
ref="imageRef"
width="100"
:src="previewImageUrl"
:show-toolbar="false"
:preview-src="previewImageUrl"
/>
</template> </template>
...@@ -10,7 +10,7 @@ export interface PersonalAppConfigState { ...@@ -10,7 +10,7 @@ export interface PersonalAppConfigState {
commConfig: { commConfig: {
preamble: string //开场白 preamble: string //开场白
featuredQuestions: string[] //推荐问 featuredQuestions: string[] //推荐问
continuousQuestionStatus: 'default' | 'customizable' | 'close' //追问状态 continuousQuestionStatus: 'default' | 'close' //追问状态
continuousQuestionSystem: string // 追问提示词 customizable时必填 continuousQuestionSystem: string // 追问提示词 customizable时必填
continuousQuestionTurn: number // 追问轮次 1-5 customizable时必填 continuousQuestionTurn: number // 追问轮次 1-5 customizable时必填
} }
......
...@@ -3,11 +3,20 @@ import { ref } from 'vue' ...@@ -3,11 +3,20 @@ import { ref } from 'vue'
import Preamble from './preamble.vue' import Preamble from './preamble.vue'
import MessageList from './message-list.vue' import MessageList from './message-list.vue'
import FooterInput from './footer-input.vue' import FooterInput from './footer-input.vue'
import { fetchCreateContinueQuestions } from '@/apis/agent-application'
import { usePersonalAppConfigStore } from '@/store/modules/personal-app-config'
const personalAppConfigStore = usePersonalAppConfigStore()
const messageListRef = ref<InstanceType<typeof MessageList> | null>(null) const messageListRef = ref<InstanceType<typeof MessageList> | null>(null)
const footerInputRef = ref<InstanceType<typeof FooterInput> | null>(null)
const messageList = ref<ConversationMessageItem[]>([]) const messageList = ref<ConversationMessageItem[]>([])
const continuousQuestionStatus = ref<'default' | 'close'>(personalAppConfigStore.commConfig.continuousQuestionStatus)
const continuousQuestionList = ref<string[]>([])
function handleAddMessageItem(messageItem: ConversationMessageItem) { function handleAddMessageItem(messageItem: ConversationMessageItem) {
messageList.value.push(messageItem) messageList.value.push(messageItem)
} }
...@@ -35,11 +44,26 @@ function handleClearAllMessage() { ...@@ -35,11 +44,26 @@ function handleClearAllMessage() {
negativeText: '取消', negativeText: '取消',
positiveText: '确定', positiveText: '确定',
onPositiveClick: () => { onPositiveClick: () => {
footerInputRef.value?.blockMessageResponse()
messageList.value = [] messageList.value = []
window.$message.success('清空成功') window.$message.success('清空成功')
}, },
}) })
} }
async function handleCreateContinueQuestions(replyTextContent: string) {
const res = await fetchCreateContinueQuestions<string[]>({ input: replyTextContent })
if (res.code === 0) {
continuousQuestionList.value = res.data
handleUpdatePageScroll()
}
}
function handleUpdateContinueQuestionStatus(status: 'default' | 'close') {
continuousQuestionStatus.value = status
continuousQuestionList.value = []
}
</script> </script>
<template> <template>
...@@ -52,17 +76,26 @@ function handleClearAllMessage() { ...@@ -52,17 +76,26 @@ function handleClearAllMessage() {
</div> </div>
<div v-show="messageList.length > 0" class="w-full"> <div v-show="messageList.length > 0" class="w-full">
<MessageList ref="messageListRef" :message-list="messageList" /> <MessageList
ref="messageListRef"
:message-list="messageList"
:continuous-question-status="continuousQuestionStatus"
:continuous-question-list="continuousQuestionList"
/>
</div> </div>
</div> </div>
<FooterInput <FooterInput
ref="footerInputRef"
:continuous-question-status="continuousQuestionStatus"
:message-list="messageList" :message-list="messageList"
@add-message-item="handleAddMessageItem" @add-message-item="handleAddMessageItem"
@update-specify-message-item="handleUpdateSpecifyMessageItem" @update-specify-message-item="handleUpdateSpecifyMessageItem"
@delete-last-message-item="handleDeleteLastMessageItem" @delete-last-message-item="handleDeleteLastMessageItem"
@update-page-scroll="handleUpdatePageScroll" @update-page-scroll="handleUpdatePageScroll"
@clear-all-message="handleClearAllMessage" @clear-all-message="handleClearAllMessage"
@create-continue-questions="handleCreateContinueQuestions"
@update-continuous-question-status="handleUpdateContinueQuestionStatus"
/> />
</div> </div>
</template> </template>
......
...@@ -3,12 +3,12 @@ import { computed, onMounted, reactive, ref, watch } from 'vue' ...@@ -3,12 +3,12 @@ import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { SelectOption, UploadFileInfo } from 'naive-ui' import { SelectOption, UploadFileInfo } from 'naive-ui'
import { useThrottleFn } from '@vueuse/core' import { useThrottleFn } from '@vueuse/core'
import AdditionalPromptModal from './additional-prompt-modal.vue'
import CustomIcon from '@/components/custom-icon/custom-icon.vue' import CustomIcon from '@/components/custom-icon/custom-icon.vue'
import UploadPhoto from '@/components/upload-photo/upload-photo.vue' import UploadPhoto from '@/components/upload-photo/upload-photo.vue'
import { usePersonalAppConfigStore } from '@/store/modules/personal-app-config' import { usePersonalAppConfigStore } from '@/store/modules/personal-app-config'
import { PersonalAppConfigState } from '@/store/types/personal-app-config' import { PersonalAppConfigState } from '@/store/types/personal-app-config'
import { import {
fetchCreateAgentAvatar,
fetchCreateFeaturedQuestions, fetchCreateFeaturedQuestions,
fetchCreatePreamble, fetchCreatePreamble,
fetchGetDebugApplicationInfo, fetchGetDebugApplicationInfo,
...@@ -38,11 +38,6 @@ const questionSettingOptions = [ ...@@ -38,11 +38,6 @@ const questionSettingOptions = [
value: 'default', value: 'default',
style: { fontSize: '12px' }, style: { fontSize: '12px' },
}, },
{
label: '自定义',
value: 'customizable',
style: { fontSize: '12px' },
},
{ {
label: '关闭', label: '关闭',
value: 'close', value: 'close',
...@@ -54,10 +49,9 @@ const modalListOptions = reactive<SelectOption[]>([]) ...@@ -54,10 +49,9 @@ const modalListOptions = reactive<SelectOption[]>([])
const commConfigExpandedNames = ref<string[]>(['continuousQuestion']) const commConfigExpandedNames = ref<string[]>(['continuousQuestion'])
const showAdditionalPromptModal = ref(false)
const isInitGetAgentAppDetail = ref(false) const isInitGetAgentAppDetail = ref(false)
const generateAgentAvatarLoading = ref(false) // 是否正在生成图片
const generatePreambleLoading = ref(false) // 是否正在生成开场白 const generatePreambleLoading = ref(false) // 是否正在生成开场白
const generateFeaturedQuestionsLoading = ref(false) // 是否正在生成推荐词 const generateFeaturedQuestionsLoading = ref(false) // 是否正在生成推荐词
...@@ -66,11 +60,7 @@ const personalAppConfig = computed(() => { ...@@ -66,11 +60,7 @@ const personalAppConfig = computed(() => {
}) })
const continuousQuestionStatusText = computed(() => { const continuousQuestionStatusText = computed(() => {
return personalAppConfig.value.commConfig.continuousQuestionStatus === 'default' return personalAppConfig.value.commConfig.continuousQuestionStatus === 'default' ? '默认' : '关闭'
? '默认'
: personalAppConfig.value.commConfig.continuousQuestionStatus === 'customizable'
? '自定义'
: '关闭'
}) })
watch( watch(
...@@ -157,24 +147,25 @@ function handleUploadAppAvatar(file: UploadFileInfo[]) { ...@@ -157,24 +147,25 @@ function handleUploadAppAvatar(file: UploadFileInfo[]) {
personalAppConfig.value.baseInfo.agentAvatar = file?.[0]?.url || '' personalAppConfig.value.baseInfo.agentAvatar = file?.[0]?.url || ''
} }
function handleQuestionSettingChange(continuousQuestionStatus: string) { function handleRemoveAppAvatar() {
if (continuousQuestionStatus !== 'customizable') { personalAppConfig.value.baseInfo.agentAvatar = ''
personalAppConfig.value.commConfig.continuousQuestionSystem = ''
personalAppConfig.value.commConfig.continuousQuestionTurn = 3
}
} }
function handleShowAdditionalPromptModal() { function handleUpdateCommConfigExpandedNames(expandedNames: string[]) {
showAdditionalPromptModal.value = true commConfigExpandedNames.value = expandedNames
} }
function handleAdditionalPrompt(continuousQuestionSystem: string) { async function handleAIGenerateAgentAvatar() {
personalAppConfig.value.commConfig.continuousQuestionSystem = continuousQuestionSystem generateAgentAvatarLoading.value = true
showAdditionalPromptModal.value = false
}
function handleUpdateCommConfigExpandedNames(expandedNames: any) { const res = await fetchCreateAgentAvatar({
commConfigExpandedNames.value = expandedNames agentTitle: personalAppConfig.value.baseInfo.agentTitle,
agentDesc: personalAppConfig.value.baseInfo.agentDesc,
}).finally(() => (generateAgentAvatarLoading.value = false))
if (res.code === 0) {
personalAppConfig.value.baseInfo.agentAvatar = res.data as string
}
} }
async function handleAIGeneratePreamble() { async function handleAIGeneratePreamble() {
...@@ -332,24 +323,32 @@ async function handleAIGenerateFeaturedQuestions() { ...@@ -332,24 +323,32 @@ async function handleAIGenerateFeaturedQuestions() {
</template> </template>
<NCollapseItem title="基本信息" name="1" class="my-[13px]!"> <NCollapseItem title="基本信息" name="1" class="my-[13px]!">
<div class="justify-left flex items-start pl-5"> <div class="justify-left flex items-start pl-5">
<div class="mr-2 h-[72px] w-[72px]"> <div class="relative mr-2 w-[72px]">
<UploadPhoto <UploadPhoto
:save-file-arr="[personalAppConfig.baseInfo.agentAvatar]" :file-arr="[personalAppConfig.baseInfo.agentAvatar]"
@after-upload="handleUploadAppAvatar" @after-upload="handleUploadAppAvatar"
@remove="handleRemoveAppAvatar"
/> />
<!-- <div <div
v-show="generateAgentAvatarLoading"
class="absolute left-0 top-0 z-10 flex h-[72px] w-[72px] cursor-not-allowed items-center justify-center rounded-xl bg-[#151b2680]"
>
<CustomIcon icon="eos-icons:bubble-loading" class="text-xl text-white" />
</div>
<div
class="text-theme-color mt-3 flex h-[28px] items-center justify-between rounded-md border border-[#d4d6d9] px-2" class="text-theme-color mt-3 flex h-[28px] items-center justify-between rounded-md border border-[#d4d6d9] px-2"
:class=" :class="
generateAgentAvatarLoading generateAgentAvatarLoading
? 'cursor-not-allowed opacity-50' ? 'cursor-not-allowed opacity-50'
: 'cursor-pointer hover:border-[#d4e5ff] hover:bg-[#e6f0ff]' : 'cursor-pointer hover:border-[#d4e5ff] hover:bg-[#e6f0ff]'
" "
@click="handleGenerateAgentAvatar" @click="handleAIGenerateAgentAvatar"
> >
<div class="mt-[-2px] h-[14px] w-[14px] bg-[url(@/assets/svgs/star.svg)] bg-[length:100%_100%]" /> <div class="mt-[-2px] h-[14px] w-[14px] bg-[url(@/assets/svgs/star.svg)] bg-[length:100%_100%]" />
<span class="text-xs">AI生成</span> <span class="text-xs">AI生成</span>
</div> --> </div>
</div> </div>
<div class="flex flex-1"> <div class="flex flex-1">
<NForm label-placement="left" class="flex-1"> <NForm label-placement="left" class="flex-1">
...@@ -534,7 +533,6 @@ async function handleAIGenerateFeaturedQuestions() { ...@@ -534,7 +533,6 @@ async function handleAIGenerateFeaturedQuestions() {
:options="questionSettingOptions" :options="questionSettingOptions"
trigger="click" trigger="click"
content-class="text-xs" content-class="text-xs"
@update:value="handleQuestionSettingChange"
> >
<div class="text-theme-color flex cursor-pointer items-center justify-between text-xs"> <div class="text-theme-color flex cursor-pointer items-center justify-between text-xs">
<span> {{ continuousQuestionStatusText }}</span> <span> {{ continuousQuestionStatusText }}</span>
...@@ -548,59 +546,8 @@ async function handleAIGenerateFeaturedQuestions() { ...@@ -548,59 +546,8 @@ async function handleAIGenerateFeaturedQuestions() {
v-show="personalAppConfig.commConfig.continuousQuestionStatus === 'default'" v-show="personalAppConfig.commConfig.continuousQuestionStatus === 'default'"
class="text-xs text-[#84868c]" class="text-xs text-[#84868c]"
> >
根据用户最近3轮对话,在最后一轮回复后自动提供3个提问建议。 根据用户最近一轮对话,在回复后自动提供3个提问建议。
</span>
<div v-show="personalAppConfig.commConfig.continuousQuestionStatus === 'customizable'">
<span class="text-xs text-[#84868c]">
根据最近轮次的参考对话,在最后一轮回复后自动提供3个提问建议。
</span>
<div class="mt-4 text-xs">
<div class="mb-2.5 flex h-[34px] items-center justify-between">
<span>参考对话轮数:</span>
<div class="mx-5 flex flex-1">
<NSlider
v-model:value="personalAppConfig.commConfig.continuousQuestionTurn"
:default-value="3"
:step="1"
:min="1"
:max="5"
/>
<span class="ml-4">{{ personalAppConfig.commConfig.continuousQuestionTurn }}</span>
</div>
<NPopover trigger="hover">
<template #trigger>
<CustomIcon
icon="mingcute:question-line"
class="ml-1 cursor-pointer text-base text-[#999]"
/>
</template>
<span class="text-xs"> 大模型在追问生成过程中可参考1、3、5轮的对话轮数 </span>
</NPopover>
</div>
<div class="mb-2.5 flex h-[34px] items-center justify-between">
<span>追问prompt:</span>
<div class="mx-5 flex flex-1">
<span
class="text-theme-color cursor-pointer hover:opacity-80"
@click="handleShowAdditionalPromptModal"
>
编辑
</span> </span>
</div>
<NPopover trigger="hover">
<template #trigger>
<CustomIcon
icon="mingcute:question-line"
class="ml-1 cursor-pointer text-base text-[#999]"
/>
</template>
<span class="text-xs"> 追问prompt可以控制追问的字数、风格和内容范围 </span>
</NPopover>
</div>
</div>
</div>
<span <span
v-show="personalAppConfig.commConfig.continuousQuestionStatus === 'close'" v-show="personalAppConfig.commConfig.continuousQuestionStatus === 'close'"
...@@ -616,14 +563,6 @@ async function handleAIGenerateFeaturedQuestions() { ...@@ -616,14 +563,6 @@ async function handleAIGenerateFeaturedQuestions() {
</div> </div>
</div> </div>
</div> </div>
<AdditionalPromptModal
v-model:isShowModal="showAdditionalPromptModal"
:question-system="personalAppConfig.commConfig.continuousQuestionSystem"
:btn-loading="false"
modal-title="追加Prompt"
@comfirm="handleAdditionalPrompt"
/>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
......
<script lang="ts" setup>
import { inject } from 'vue'
import { Emitter } from 'mitt'
import CustomLoading from './custom-loading.vue'
interface Props {
continuousQuestionList: string[]
}
defineProps<Props>()
const emitter = inject<Emitter<MittEvents>>('emitter')
function handleSelectContinueQuestion(continueQuestion: string) {
emitter?.emit('selectQuestion', continueQuestion)
}
</script>
<template>
<div class="pl-10 text-xs">
<p class="mb-3 text-[#84868c]">你可以继续提问</p>
<div v-if="continuousQuestionList.length === 0" class="mt-4 px-4">
<CustomLoading />
</div>
<ul v-else class="flex max-w-full flex-col items-start justify-center gap-3 overflow-hidden">
<li v-for="(continueQuestionItem, index) in continuousQuestionList" :key="index">
<div
v-show="continueQuestionItem"
class="w-full cursor-pointer rounded-xl border border-[#d4d6d9] bg-[#ffffff80] px-[14px] py-[11px] text-[#5c5f66] hover:text-[#151b26]"
@click="handleSelectContinueQuestion(continueQuestionItem)"
>
<span class="break-all">{{ continueQuestionItem }}</span>
</div>
</li>
</ul>
</div>
</template>
...@@ -7,6 +7,7 @@ import { usePersonalAppConfigStore } from '@/store/modules/personal-app-config' ...@@ -7,6 +7,7 @@ import { usePersonalAppConfigStore } from '@/store/modules/personal-app-config'
interface Props { interface Props {
messageList: ConversationMessageItem[] messageList: ConversationMessageItem[]
continuousQuestionStatus: 'default' | 'close'
} }
const props = defineProps<Props>() const props = defineProps<Props>()
...@@ -17,6 +18,8 @@ const emit = defineEmits<{ ...@@ -17,6 +18,8 @@ const emit = defineEmits<{
deleteLastMessageItem: [] deleteLastMessageItem: []
updatePageScroll: [] updatePageScroll: []
clearAllMessage: [] clearAllMessage: []
createContinueQuestions: [value: string]
updateContinuousQuestionStatus: [value: 'default' | 'close']
}>() }>()
const personalAppConfigStore = usePersonalAppConfigStore() const personalAppConfigStore = usePersonalAppConfigStore()
...@@ -33,6 +36,10 @@ const agentId = computed(() => { ...@@ -33,6 +36,10 @@ const agentId = computed(() => {
return personalAppConfigStore.baseInfo.agentId return personalAppConfigStore.baseInfo.agentId
}) })
const isCreateContinueQuestions = computed(() => {
return props.continuousQuestionStatus === 'default'
})
const isAllowClearMessage = computed(() => { const isAllowClearMessage = computed(() => {
return props.messageList.length > 0 return props.messageList.length > 0
}) })
...@@ -43,10 +50,10 @@ const isSendBtnDisabled = computed(() => { ...@@ -43,10 +50,10 @@ const isSendBtnDisabled = computed(() => {
onUnmounted(() => { onUnmounted(() => {
blockMessageResponse() blockMessageResponse()
emitter?.off('selectFeaturedQuestion') emitter?.off('selectQuestion')
}) })
emitter?.on('selectFeaturedQuestion', (featuredQuestion) => { emitter?.on('selectQuestion', (featuredQuestion) => {
inputeMessageContent.value = featuredQuestion inputeMessageContent.value = featuredQuestion
handleMessageSend() handleMessageSend()
}) })
...@@ -86,6 +93,7 @@ function handleMessageSend() { ...@@ -86,6 +93,7 @@ function handleMessageSend() {
role: string role: string
}[] = [] }[] = []
emit('updateContinuousQuestionStatus', personalAppConfigStore.commConfig.continuousQuestionStatus)
emit('addMessageItem', { ...messageItemFactory(), textContent: inputeMessageContent.value }) emit('addMessageItem', { ...messageItemFactory(), textContent: inputeMessageContent.value })
emit('updatePageScroll') emit('updatePageScroll')
...@@ -134,6 +142,7 @@ function handleMessageSend() { ...@@ -134,6 +142,7 @@ function handleMessageSend() {
isTextContentLoading: false, isTextContentLoading: false,
isAnswerResponseLoading: false, isAnswerResponseLoading: false,
}) })
isCreateContinueQuestions.value && emit('createContinueQuestions', replyTextContent)
emit('updatePageScroll') emit('updatePageScroll')
blockMessageResponse() blockMessageResponse()
return return
...@@ -174,7 +183,6 @@ function errorMessageResponse() { ...@@ -174,7 +183,6 @@ function errorMessageResponse() {
function handleClearAllMessage() { function handleClearAllMessage() {
if (!isAllowClearMessage.value) return if (!isAllowClearMessage.value) return
blockMessageResponse()
emit('clearAllMessage') emit('clearAllMessage')
} }
...@@ -182,6 +190,10 @@ function blockMessageResponse() { ...@@ -182,6 +190,10 @@ function blockMessageResponse() {
controller?.abort() controller?.abort()
isAnswerResponseWait.value = false isAnswerResponseWait.value = false
} }
defineExpose({
blockMessageResponse,
})
</script> </script>
<template> <template>
......
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'
import MessageItem from './message-item.vue' import MessageItem from './message-item.vue'
import ContinueQuestion from './continue-question.vue'
import { useScroll } from '@/composables/useScroll' import { useScroll } from '@/composables/useScroll'
interface Props { interface Props {
messageList: ConversationMessageItem[] messageList: ConversationMessageItem[]
continuousQuestionStatus: 'default' | 'close'
continuousQuestionList: string[]
} }
defineProps<Props>() const props = defineProps<Props>()
const { scrollRef, scrollToBottom } = useScroll() const { scrollRef, scrollToBottom } = useScroll()
const isShowContinueQuestion = computed(() => {
return (
props.continuousQuestionStatus === 'default' &&
props.messageList.length > 1 &&
!props.messageList[props.messageList.length - 1].isAnswerResponseLoading
)
})
defineExpose({ defineExpose({
scrollToBottom, scrollToBottom,
}) })
...@@ -17,11 +29,17 @@ defineExpose({ ...@@ -17,11 +29,17 @@ defineExpose({
<template> <template>
<main ref="scrollRef" class="h-full overflow-y-auto px-5"> <main ref="scrollRef" class="h-full overflow-y-auto px-5">
<div>
<MessageItem <MessageItem
v-for="messageItem in messageList" v-for="messageItem in messageList"
:key="messageItem.timestamp" :key="messageItem.timestamp"
:role="messageItem.role" :role="messageItem.role"
:message-item="messageItem" :message-item="messageItem"
/> />
</div>
<div v-show="isShowContinueQuestion">
<ContinueQuestion :continuous-question-list="continuousQuestionList" />
</div>
</main> </main>
</template> </template>
...@@ -8,20 +8,24 @@ const personalAppConfigStore = usePersonalAppConfigStore() ...@@ -8,20 +8,24 @@ const personalAppConfigStore = usePersonalAppConfigStore()
const emitter = inject<Emitter<MittEvents>>('emitter') const emitter = inject<Emitter<MittEvents>>('emitter')
const agentAvatar = computed(() => { const agentAvatar = computed(() => {
return ( return personalAppConfigStore.baseInfo.agentAvatar
personalAppConfigStore.baseInfo.agentAvatar || 'https://gsst-poe-sit.gz.bcebos.com/data/20240911/1726041369632.webp'
)
}) })
function handleSelectFeaturedQuestion(featuredQuestion: string) { function handleSelectFeaturedQuestion(featuredQuestion: string) {
emitter?.emit('selectFeaturedQuestion', featuredQuestion) emitter?.emit('selectQuestion', featuredQuestion)
} }
</script> </script>
<template> <template>
<div class="flex w-full flex-1 flex-col px-5"> <div class="flex w-full flex-1 flex-col px-5">
<div class="mb-5 flex w-full justify-center pt-[50px]"> <div class="mb-5 flex w-full justify-center pt-[50px]">
<img :src="agentAvatar" class="h-[72px] w-[72px] rounded-xl border" /> <img
v-if="personalAppConfigStore.baseInfo.agentAvatar"
:src="agentAvatar"
class="h-[72px] w-[72px] rounded-xl border"
/>
<div v-else class="h-[72px] w-[72px] rounded-xl border" />
</div> </div>
<div class="flex flex-col items-center justify-center"> <div class="flex flex-col items-center justify-center">
......
<script lang="ts" setup>
import { inject } from 'vue'
import { Emitter } from 'mitt'
import CustomLoading from './custom-loading.vue'
import { useLayoutConfig } from '@/composables/useLayoutConfig'
interface Props {
continuousQuestionList: string[]
}
defineProps<Props>()
const { isMobile } = useLayoutConfig()
const emitter = inject<Emitter<MittEvents>>('emitter')
function handleSelectContinueQuestion(continueQuestion: string) {
emitter?.emit('selectQuestion', continueQuestion)
}
</script>
<template>
<div class="text-xs" :class="isMobile ? 'pl-0' : 'pl-10'">
<p class="mb-3 mt-5 text-[#84868c]">你可以继续提问</p>
<div v-if="continuousQuestionList.length === 0" class="mt-4 px-4">
<CustomLoading />
</div>
<ul v-else class="flex max-w-full flex-col items-start justify-center gap-3 overflow-hidden">
<li v-for="(continueQuestionItem, index) in continuousQuestionList" :key="index">
<div
v-show="continueQuestionItem"
class="w-full cursor-pointer rounded-xl border border-[#d4d6d9] bg-[#ffffff80] px-[14px] py-[11px] text-[#5c5f66] hover:text-[#151b26]"
@click="handleSelectContinueQuestion(continueQuestionItem)"
>
<span class="break-all">{{ continueQuestionItem }}</span>
</div>
</li>
</ul>
</div>
</template>
...@@ -10,6 +10,7 @@ interface Props { ...@@ -10,6 +10,7 @@ interface Props {
agentId: string agentId: string
dialogsId: string dialogsId: string
messageList: ConversationMessageItem[] messageList: ConversationMessageItem[]
continuousQuestionStatus: 'default' | 'close'
} }
const props = defineProps<Props>() const props = defineProps<Props>()
...@@ -21,6 +22,8 @@ const emit = defineEmits<{ ...@@ -21,6 +22,8 @@ const emit = defineEmits<{
updatePageScroll: [] updatePageScroll: []
clearAllMessage: [] clearAllMessage: []
toLogin: [] toLogin: []
createContinueQuestions: [value: string]
resetContinueQuestionList: []
}>() }>()
const { isMobile } = useLayoutConfig() const { isMobile } = useLayoutConfig()
...@@ -51,8 +54,12 @@ const inputPlaceholder = computed(() => { ...@@ -51,8 +54,12 @@ const inputPlaceholder = computed(() => {
return isLogin.value ? '请输入你的问题进行提问' : '' return isLogin.value ? '请输入你的问题进行提问' : ''
}) })
const isCreateContinueQuestions = computed(() => {
return props.continuousQuestionStatus === 'default'
})
onMounted(() => { onMounted(() => {
emitter?.on('selectFeaturedQuestion', (featuredQuestion) => { emitter?.on('selectQuestion', (featuredQuestion) => {
if (!isLogin.value) { if (!isLogin.value) {
window.$message.warning('请先登录') window.$message.warning('请先登录')
return return
...@@ -65,7 +72,7 @@ onMounted(() => { ...@@ -65,7 +72,7 @@ onMounted(() => {
onUnmounted(() => { onUnmounted(() => {
blockMessageResponse() blockMessageResponse()
emitter?.off('selectFeaturedQuestion') emitter?.off('selectQuestion')
}) })
function messageItemFactory() { function messageItemFactory() {
...@@ -97,6 +104,7 @@ function handleMessageSend() { ...@@ -97,6 +104,7 @@ function handleMessageSend() {
if (!inputeMessageContent.value.trim() || isAnswerResponseWait.value) return '' if (!inputeMessageContent.value.trim() || isAnswerResponseWait.value) return ''
emit('resetContinueQuestionList')
emit('addMessageItem', { ...messageItemFactory(), textContent: inputeMessageContent.value }) emit('addMessageItem', { ...messageItemFactory(), textContent: inputeMessageContent.value })
emit('updatePageScroll') emit('updatePageScroll')
...@@ -131,6 +139,7 @@ function handleMessageSend() { ...@@ -131,6 +139,7 @@ function handleMessageSend() {
isTextContentLoading: false, isTextContentLoading: false,
isAnswerResponseLoading: false, isAnswerResponseLoading: false,
}) })
isCreateContinueQuestions.value && emit('createContinueQuestions', replyTextContent)
emit('updatePageScroll') emit('updatePageScroll')
blockMessageResponse() blockMessageResponse()
return return
...@@ -171,7 +180,6 @@ function errorMessageResponse() { ...@@ -171,7 +180,6 @@ function errorMessageResponse() {
function handleClearAllMessage() { function handleClearAllMessage() {
if (!isAllowClearMessage.value) return if (!isAllowClearMessage.value) return
blockMessageResponse()
emit('clearAllMessage') emit('clearAllMessage')
} }
...@@ -183,6 +191,10 @@ function blockMessageResponse() { ...@@ -183,6 +191,10 @@ function blockMessageResponse() {
function handleToLogin() { function handleToLogin() {
emit('toLogin') emit('toLogin')
} }
defineExpose({
blockMessageResponse,
})
</script> </script>
<template> <template>
......
<script setup lang="ts"> <script setup lang="ts">
import MessageItem from './message-item.vue' import MessageItem from './message-item.vue'
import ContinueQuestion from './continue-question.vue'
import { useScroll } from '@/composables/useScroll' import { useScroll } from '@/composables/useScroll'
import { PersonalAppConfigState } from '@/store/types/personal-app-config' import { PersonalAppConfigState } from '@/store/types/personal-app-config'
import { computed } from 'vue'
interface Props { interface Props {
messageList: ConversationMessageItem[] messageList: ConversationMessageItem[]
agentApplicationConfig: PersonalAppConfigState agentApplicationConfig: PersonalAppConfigState
continuousQuestionStatus: 'default' | 'close'
continuousQuestionList: string[]
} }
defineProps<Props>() const props = defineProps<Props>()
const { scrollRef, scrollToBottom } = useScroll() const { scrollRef, scrollToBottom } = useScroll()
const isShowContinueQuestion = computed(() => {
return (
props.continuousQuestionStatus === 'default' &&
props.messageList.length > 1 &&
!props.messageList[props.messageList.length - 1].isAnswerResponseLoading
)
})
defineExpose({ defineExpose({
scrollToBottom, scrollToBottom,
}) })
...@@ -19,6 +31,7 @@ defineExpose({ ...@@ -19,6 +31,7 @@ defineExpose({
<template> <template>
<main ref="scrollRef" class="h-full overflow-y-auto px-5"> <main ref="scrollRef" class="h-full overflow-y-auto px-5">
<div>
<MessageItem <MessageItem
v-for="messageItem in messageList" v-for="messageItem in messageList"
:key="messageItem.timestamp" :key="messageItem.timestamp"
...@@ -26,5 +39,10 @@ defineExpose({ ...@@ -26,5 +39,10 @@ defineExpose({
:message-item="messageItem" :message-item="messageItem"
:agent-application-config="agentApplicationConfig" :agent-application-config="agentApplicationConfig"
/> />
</div>
<div v-show="isShowContinueQuestion">
<ContinueQuestion :continuous-question-list="continuousQuestionList" />
</div>
</main> </main>
</template> </template>
...@@ -21,7 +21,7 @@ const agentAvatar = computed(() => { ...@@ -21,7 +21,7 @@ const agentAvatar = computed(() => {
}) })
function handleSelectFeaturedQuestion(featuredQuestion: string) { function handleSelectFeaturedQuestion(featuredQuestion: string) {
emitter?.emit('selectFeaturedQuestion', featuredQuestion) emitter?.emit('selectQuestion', featuredQuestion)
} }
</script> </script>
......
...@@ -8,7 +8,7 @@ import FooterInput from './components/footer-input.vue' ...@@ -8,7 +8,7 @@ import FooterInput from './components/footer-input.vue'
import { PersonalAppConfigState } from '@/store/types/personal-app-config' import { PersonalAppConfigState } from '@/store/types/personal-app-config'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { defaultPersonalAppConfigState } from '@/store/modules/personal-app-config' import { defaultPersonalAppConfigState } from '@/store/modules/personal-app-config'
import { fetchCreateDialogues, fetchGetApplicationInfo } from '@/apis/agent-application' import { fetchCreateContinueQuestions, fetchCreateDialogues, fetchGetApplicationInfo } from '@/apis/agent-application'
import { useLayoutConfig } from '@/composables/useLayoutConfig' import { useLayoutConfig } from '@/composables/useLayoutConfig'
const router = useRouter() const router = useRouter()
...@@ -24,8 +24,13 @@ const dialogsId = ref('') ...@@ -24,8 +24,13 @@ const dialogsId = ref('')
const agentApplicationConfig = ref<PersonalAppConfigState>(defaultPersonalAppConfigState()) const agentApplicationConfig = ref<PersonalAppConfigState>(defaultPersonalAppConfigState())
const messageListRef = ref<InstanceType<typeof MessageList> | null>(null) const messageListRef = ref<InstanceType<typeof MessageList> | null>(null)
const footerInputRef = ref<InstanceType<typeof FooterInput> | null>(null)
const messageList = ref<ConversationMessageItem[]>([]) const messageList = ref<ConversationMessageItem[]>([])
const continuousQuestionStatus = ref<'default' | 'close'>('default')
const continueQuestionList = ref<string[]>([])
onMounted(async () => { onMounted(async () => {
if (router.currentRoute.value.params.agentId) { if (router.currentRoute.value.params.agentId) {
agentId.value = router.currentRoute.value.params.agentId as string agentId.value = router.currentRoute.value.params.agentId as string
...@@ -53,6 +58,7 @@ async function handleGetApplicationDetail() { ...@@ -53,6 +58,7 @@ async function handleGetApplicationDetail() {
fetchGetApplicationInfo<PersonalAppConfigState>(agentId.value) fetchGetApplicationInfo<PersonalAppConfigState>(agentId.value)
.then((res) => { .then((res) => {
agentApplicationConfig.value = res.data agentApplicationConfig.value = res.data
continuousQuestionStatus.value = res.data.commConfig.continuousQuestionStatus
document.title = agentApplicationConfig.value.baseInfo.agentTitle document.title = agentApplicationConfig.value.baseInfo.agentTitle
}) })
.catch(() => { .catch(() => {
...@@ -110,11 +116,25 @@ function handleClearAllMessage() { ...@@ -110,11 +116,25 @@ function handleClearAllMessage() {
negativeText: '取消', negativeText: '取消',
positiveText: '确定', positiveText: '确定',
onPositiveClick: () => { onPositiveClick: () => {
footerInputRef.value?.blockMessageResponse()
messageList.value = [] messageList.value = []
window.$message.success('清空成功') window.$message.success('清空成功')
}, },
}) })
} }
async function handleCreateContinueQuestions(replyTextContent: string) {
const res = await fetchCreateContinueQuestions<string[]>({ input: replyTextContent })
if (res.code === 0) {
continueQuestionList.value = res.data
handleUpdatePageScroll()
}
}
function handleResetContinueQuestionList() {
continueQuestionList.value = []
}
</script> </script>
<template> <template>
...@@ -136,21 +156,27 @@ function handleClearAllMessage() { ...@@ -136,21 +156,27 @@ function handleClearAllMessage() {
ref="messageListRef" ref="messageListRef"
:agent-application-config="agentApplicationConfig" :agent-application-config="agentApplicationConfig"
:message-list="messageList" :message-list="messageList"
:continuous-question-status="continuousQuestionStatus"
:continuous-question-list="continueQuestionList"
/> />
</div> </div>
</div> </div>
<div class="footer-operation px-4"> <div class="footer-operation px-4">
<FooterInput <FooterInput
ref="footerInputRef"
:message-list="messageList" :message-list="messageList"
:dialogs-id="dialogsId" :dialogs-id="dialogsId"
:agent-id="agentApplicationConfig.baseInfo.agentId" :agent-id="agentApplicationConfig.baseInfo.agentId"
:continuous-question-status="continuousQuestionStatus"
@add-message-item="handleAddMessageItem" @add-message-item="handleAddMessageItem"
@update-specify-message-item="handleUpdateSpecifyMessageItem" @update-specify-message-item="handleUpdateSpecifyMessageItem"
@delete-last-message-item="handleDeleteLastMessageItem" @delete-last-message-item="handleDeleteLastMessageItem"
@update-page-scroll="handleUpdatePageScroll" @update-page-scroll="handleUpdatePageScroll"
@clear-all-message="handleClearAllMessage" @clear-all-message="handleClearAllMessage"
@to-login="handleToLoginPage" @to-login="handleToLoginPage"
@create-continue-questions="handleCreateContinueQuestions"
@reset-continue-question-list="handleResetContinueQuestionList"
/> />
</div> </div>
</div> </div>
......
...@@ -5,7 +5,7 @@ import PageHeader from './components/web-page-header.vue' ...@@ -5,7 +5,7 @@ import PageHeader from './components/web-page-header.vue'
import Preamble from './components/preamble.vue' import Preamble from './components/preamble.vue'
import MessageList from './components/message-list.vue' import MessageList from './components/message-list.vue'
import FooterInput from './components/footer-input.vue' import FooterInput from './components/footer-input.vue'
import { fetchCreateDialogues, fetchGetApplicationInfo } from '@/apis/agent-application' import { fetchCreateContinueQuestions, fetchCreateDialogues, fetchGetApplicationInfo } from '@/apis/agent-application'
import { PersonalAppConfigState } from '@/store/types/personal-app-config' import { PersonalAppConfigState } from '@/store/types/personal-app-config'
import { defaultPersonalAppConfigState } from '@/store/modules/personal-app-config' import { defaultPersonalAppConfigState } from '@/store/modules/personal-app-config'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
...@@ -24,8 +24,13 @@ const dialogsId = ref('') ...@@ -24,8 +24,13 @@ const dialogsId = ref('')
const agentApplicationConfig = ref<PersonalAppConfigState>(defaultPersonalAppConfigState()) const agentApplicationConfig = ref<PersonalAppConfigState>(defaultPersonalAppConfigState())
const messageListRef = ref<InstanceType<typeof MessageList> | null>(null) const messageListRef = ref<InstanceType<typeof MessageList> | null>(null)
const footerInputRef = ref<InstanceType<typeof FooterInput> | null>(null)
const messageList = ref<ConversationMessageItem[]>([]) const messageList = ref<ConversationMessageItem[]>([])
const continuousQuestionStatus = ref<'default' | 'close'>('default')
const continueQuestionList = ref<string[]>([])
onMounted(async () => { onMounted(async () => {
if (router.currentRoute.value.params.agentId) { if (router.currentRoute.value.params.agentId) {
agentId.value = router.currentRoute.value.params.agentId as string agentId.value = router.currentRoute.value.params.agentId as string
...@@ -53,6 +58,7 @@ async function handleGetApplicationDetail() { ...@@ -53,6 +58,7 @@ async function handleGetApplicationDetail() {
fetchGetApplicationInfo<PersonalAppConfigState>(agentId.value) fetchGetApplicationInfo<PersonalAppConfigState>(agentId.value)
.then((res) => { .then((res) => {
agentApplicationConfig.value = res.data agentApplicationConfig.value = res.data
continuousQuestionStatus.value = res.data.commConfig.continuousQuestionStatus
document.title = agentApplicationConfig.value.baseInfo.agentTitle document.title = agentApplicationConfig.value.baseInfo.agentTitle
}) })
.catch(() => { .catch(() => {
...@@ -114,11 +120,25 @@ function handleClearAllMessage() { ...@@ -114,11 +120,25 @@ function handleClearAllMessage() {
negativeText: '取消', negativeText: '取消',
positiveText: '确定', positiveText: '确定',
onPositiveClick: () => { onPositiveClick: () => {
footerInputRef.value?.blockMessageResponse()
messageList.value = [] messageList.value = []
window.$message.success('清空成功') window.$message.success('清空成功')
}, },
}) })
} }
async function handleCreateContinueQuestions(replyTextContent: string) {
const res = await fetchCreateContinueQuestions<string[]>({ input: replyTextContent })
if (res.code === 0) {
continueQuestionList.value = res.data
handleUpdatePageScroll()
}
}
function handleResetContinueQuestionList() {
continueQuestionList.value = []
}
</script> </script>
<template> <template>
...@@ -157,21 +177,27 @@ function handleClearAllMessage() { ...@@ -157,21 +177,27 @@ function handleClearAllMessage() {
ref="messageListRef" ref="messageListRef"
:agent-application-config="agentApplicationConfig" :agent-application-config="agentApplicationConfig"
:message-list="messageList" :message-list="messageList"
:continuous-question-status="continuousQuestionStatus"
:continuous-question-list="continueQuestionList"
/> />
</div> </div>
</div> </div>
<div class="px-5"> <div class="px-5">
<FooterInput <FooterInput
ref="footerInputRef"
:message-list="messageList" :message-list="messageList"
:dialogs-id="dialogsId" :dialogs-id="dialogsId"
:agent-id="agentApplicationConfig.baseInfo.agentId" :agent-id="agentApplicationConfig.baseInfo.agentId"
:continuous-question-status="continuousQuestionStatus"
@add-message-item="handleAddMessageItem" @add-message-item="handleAddMessageItem"
@update-specify-message-item="handleUpdateSpecifyMessageItem" @update-specify-message-item="handleUpdateSpecifyMessageItem"
@delete-last-message-item="handleDeleteLastMessageItem" @delete-last-message-item="handleDeleteLastMessageItem"
@update-page-scroll="handleUpdatePageScroll" @update-page-scroll="handleUpdatePageScroll"
@clear-all-message="handleClearAllMessage" @clear-all-message="handleClearAllMessage"
@to-login="handleToLoginPage" @to-login="handleToLoginPage"
@create-continue-questions="handleCreateContinueQuestions"
@reset-continue-question-list="handleResetContinueQuestionList"
/> />
</div> </div>
</div> </div>
......
declare type MittEvents = { declare type MittEvents = {
selectFeaturedQuestion: string selectQuestion: string
} }
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 to comment