perf: User input interaction style optimization

This commit is contained in:
wangdan-fit2cloud 2025-03-14 19:06:03 +08:00 committed by GitHub
parent b8960d57c8
commit a09f5c0577
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 159 additions and 67 deletions

View File

@ -1,7 +1,6 @@
<template> <template>
<div class="ai-chat__operate p-16-24"> <div class="ai-chat__operate p-16-24">
<slot name="operateBefore" /> <slot name="operateBefore" />
<div class="operate-textarea"> <div class="operate-textarea">
<el-scrollbar max-height="136"> <el-scrollbar max-height="136">
<div <div

View File

@ -4,20 +4,16 @@
(inputFieldList.length > 0 || (type === 'debug-ai-chat' && apiInputFieldList.length > 0)) && (inputFieldList.length > 0 || (type === 'debug-ai-chat' && apiInputFieldList.length > 0)) &&
type !== 'log' type !== 'log'
" "
class="mb-16" class="mb-16 w-full"
style="padding: 0 24px" style="padding: 0 24px; max-width: 400px"
> >
<el-card shadow="always" class="border-r-8" style="--el-card-padding: 16px 8px"> <el-card shadow="always" class="border-r-8" style="--el-card-padding: 16px 8px">
<div <div class="flex align-center cursor w-full" style="padding: 0 8px">
class="flex align-center cursor w-full" <!-- <el-icon class="mr-8 arrow-icon" :class="showUserInput ? 'rotate-90' : ''"
style="padding: 0 8px"
@click="showUserInput = !showUserInput"
>
<el-icon class="mr-8 arrow-icon" :class="showUserInput ? 'rotate-90' : ''"
><CaretRight ><CaretRight
/></el-icon> /></el-icon> -->
<span class="break-all ellipsis-1 mr-16" :title="inputFieldConfig.title"> <span class="break-all ellipsis-1 mr-16" :title="inputFieldConfig.title">
{{ inputFieldConfig.title }} {{ inputFieldConfig.title }}
</span> </span>
</div> </div>
<el-scrollbar max-height="160"> <el-scrollbar max-height="160">
@ -44,6 +40,15 @@
</div> </div>
</el-collapse-transition> </el-collapse-transition>
</el-scrollbar> </el-scrollbar>
<div class="text-right mr-8">
<el-button type="primary" v-if="first" @click="confirmHandle">{{
$t('chat.operation.startChat')
}}</el-button>
<el-button v-if="!first" @click="cancelHandle">{{ $t('common.cancel') }}</el-button>
<el-button type="primary" v-if="!first" @click="confirmHandle">{{
$t('common.confirm')
}}</el-button>
</div>
</el-card> </el-card>
</div> </div>
</template> </template>
@ -60,6 +65,7 @@ const props = defineProps<{
type: 'log' | 'ai-chat' | 'debug-ai-chat' type: 'log' | 'ai-chat' | 'debug-ai-chat'
api_form_data: any api_form_data: any
form_data: any form_data: any
first: boolean
}>() }>()
// //
const dynamicsFormRefresh = ref(0) const dynamicsFormRefresh = ref(0)
@ -67,7 +73,7 @@ const inputFieldList = ref<FormField[]>([])
const apiInputFieldList = ref<FormField[]>([]) const apiInputFieldList = ref<FormField[]>([])
const inputFieldConfig = ref({ title: t('chat.userInput') }) const inputFieldConfig = ref({ title: t('chat.userInput') })
const showUserInput = ref(true) const showUserInput = ref(true)
const emit = defineEmits(['update:api_form_data', 'update:form_data']) const emit = defineEmits(['update:api_form_data', 'update:form_data', 'confirm', 'cancel'])
const api_form_data_context = computed({ const api_form_data_context = computed({
get: () => { get: () => {
@ -324,6 +330,14 @@ const decodeQuery = (query: string) => {
return query return query
} }
} }
const confirmHandle = () => {
if (checkInputParam()) {
emit('confirm')
}
}
const cancelHandle = () => {
emit('cancel')
}
defineExpose({ checkInputParam }) defineExpose({ checkInputParam })
onMounted(() => { onMounted(() => {
handleInputFieldList() handleInputFieldList()

View File

@ -1,56 +1,76 @@
<template> <template>
<div ref="aiChatRef" class="ai-chat" :class="type"> <div ref="aiChatRef" class="ai-chat" :class="type">
<UserForm <div
v-model:api_form_data="api_form_data" v-show="(isUserInput && firsUserInput) || showUserInput"
v-model:form_data="form_data" :class="firsUserInput ? 'firstUserInput' : 'popperUserInput'"
:application="applicationDetails"
:type="type"
ref="userFormRef"
></UserForm>
<el-scrollbar ref="scrollDiv" @scroll="handleScrollTop">
<div ref="dialogScrollbar" class="ai-chat__content p-24">
<PrologueContent
:type="type"
:application="applicationDetails"
:available="available"
:send-message="sendMessage"
></PrologueContent>
<template v-for="(item, index) in chatList" :key="index">
<!-- 问题 -->
<QuestionContent
:type="type"
:application="applicationDetails"
:chat-record="item"
></QuestionContent>
<!-- 回答 -->
<AnswerContent
:application="applicationDetails"
:loading="loading"
v-model:chat-record="chatList[index]"
:type="type"
:send-message="sendMessage"
:chat-management="ChatManagement"
></AnswerContent>
</template>
</div>
</el-scrollbar>
<ChatInputOperate
:app-id="appId"
:application-details="applicationDetails"
:is-mobile="isMobile"
:type="type"
:send-message="sendMessage"
:open-chat-id="openChatId"
:chat-management="ChatManagement"
v-model:chat-id="chartOpenId"
v-model:loading="loading"
v-if="type !== 'log'"
> >
<template #operateBefore> <slot name="operateBefore" /> </template> <UserForm
</ChatInputOperate> v-model:api_form_data="api_form_data"
<Control></Control> v-model:form_data="form_data"
:application="applicationDetails"
:type="type"
:first="firsUserInput"
@confirm="UserFormConfirm"
@cancel="() => (showUserInput = false)"
ref="userFormRef"
></UserForm>
</div>
<template v-if="!firsUserInput">
<el-scrollbar ref="scrollDiv" @scroll="handleScrollTop">
<div ref="dialogScrollbar" class="ai-chat__content p-24">
<PrologueContent
:type="type"
:application="applicationDetails"
:available="available"
:send-message="sendMessage"
></PrologueContent>
<template v-for="(item, index) in chatList" :key="index">
<!-- 问题 -->
<QuestionContent
:type="type"
:application="applicationDetails"
:chat-record="item"
></QuestionContent>
<!-- 回答 -->
<AnswerContent
:application="applicationDetails"
:loading="loading"
v-model:chat-record="chatList[index]"
:type="type"
:send-message="sendMessage"
:chat-management="ChatManagement"
></AnswerContent>
</template>
</div>
</el-scrollbar>
<ChatInputOperate
:app-id="appId"
:application-details="applicationDetails"
:is-mobile="isMobile"
:type="type"
:send-message="sendMessage"
:open-chat-id="openChatId"
:chat-management="ChatManagement"
v-model:chat-id="chartOpenId"
v-model:loading="loading"
v-if="type !== 'log'"
>
<template #operateBefore>
<div class="flex-between">
<slot name="operateBefore">
<span></span>
</slot>
<el-button class="user-input-button mb-8" type="primary" text @click="toggleUserInput">
<AppIcon iconName="app-user-input"></AppIcon>
</el-button>
</div>
</template>
</ChatInputOperate>
<Control></Control>
</template>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -62,7 +82,7 @@ import { ChatManagement, type chatType } from '@/api/type/application'
import { randomId } from '@/utils/utils' import { randomId } from '@/utils/utils'
import useStore from '@/stores' import useStore from '@/stores'
import { isWorkFlow } from '@/utils/application' import { isWorkFlow } from '@/utils/application'
import { debounce } from 'lodash' import { debounce, first } from 'lodash'
import AnswerContent from '@/components/ai-chat/component/answer-content/index.vue' import AnswerContent from '@/components/ai-chat/component/answer-content/index.vue'
import QuestionContent from '@/components/ai-chat/component/question-content/index.vue' import QuestionContent from '@/components/ai-chat/component/question-content/index.vue'
import ChatInputOperate from '@/components/ai-chat/component/chat-input-operate/index.vue' import ChatInputOperate from '@/components/ai-chat/component/chat-input-operate/index.vue'
@ -106,13 +126,25 @@ const chatList = ref<any[]>([])
const form_data = ref<any>({}) const form_data = ref<any>({})
const api_form_data = ref<any>({}) const api_form_data = ref<any>({})
const userFormRef = ref<InstanceType<typeof UserForm>>() const userFormRef = ref<InstanceType<typeof UserForm>>()
//
const firsUserInput = ref(true)
const showUserInput = ref(false)
const isUserInput = computed(
() =>
props.applicationDetails.work_flow?.nodes?.filter((v: any) => v.id === 'base-node')[0]
.properties.user_input_field_list.length > 0
)
watch( watch(
() => props.chatId, () => props.chatId,
(val) => { (val) => {
if (val && val !== 'new') { if (val && val !== 'new') {
chartOpenId.value = val chartOpenId.value = val
firsUserInput.value = false
} else { } else {
chartOpenId.value = '' chartOpenId.value = ''
firsUserInput.value = true
} }
}, },
{ deep: true } { deep: true }
@ -136,6 +168,15 @@ watch(
} }
) )
const toggleUserInput = () => {
showUserInput.value = !showUserInput.value
}
function UserFormConfirm() {
firsUserInput.value = false
showUserInput.value = false
}
function sendMessage(val: string, other_params_data?: any, chat?: chatType) { function sendMessage(val: string, other_params_data?: any, chat?: chatType) {
if (!userFormRef.value?.checkInputParam()) { if (!userFormRef.value?.checkInputParam()) {
return return
@ -467,4 +508,18 @@ defineExpose({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import './index.scss'; @import './index.scss';
.firstUserInput {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.popperUserInput {
position: absolute;
z-index: 999;
right: 50px;
bottom: 80px;
width: calc(100% - 50px);
max-width: 400px;
}
</style> </style>

View File

@ -1394,5 +1394,26 @@ export const iconMap: any = {
) )
]) ])
} }
},
'app-user-input': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 1024 1024',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg'
},
[
h('path', {
d: 'M85.333333 234.666667a149.333333 149.333333 0 0 1 292.48-42.666667H917.333333a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333H377.813333A149.418667 149.418667 0 0 1 85.333333 234.666667z m21.333334 320a21.333333 21.333333 0 0 1-21.333334-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333334-21.333334h262.186666a149.418667 149.418667 0 0 1 286.293334 0H917.333333a21.333333 21.333333 0 0 1 21.333334 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333334 21.333334h-262.186666a149.418667 149.418667 0 0 1-286.293334 0H106.666667z m405.333333 21.333333a64 64 0 1 0 0-128 64 64 0 0 0 0 128z m-405.333333 256A21.333333 21.333333 0 0 1 85.333333 810.666667v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333h539.52a149.418667 149.418667 0 0 1 292.48 42.666666 149.333333 149.333333 0 0 1-292.48 42.666667H106.666667z m682.666666-106.666667a64 64 0 1 0 0 128 64 64 0 0 0 0-128zM234.666667 298.666667a64 64 0 1 0 0-128 64 64 0 0 0 0 128z',
fill: 'currentColor'
})
]
)
])
}
} }
} }

View File

@ -23,7 +23,8 @@ export default {
oppose: 'Dislike', oppose: 'Dislike',
cancelOppose: 'Undo Dislike', cancelOppose: 'Undo Dislike',
continue: 'Continue', continue: 'Continue',
stopChat: 'Stop Response' stopChat: 'Stop Response',
startChat: 'Start Response',
}, },
tip: { tip: {
error500Message: 'Sorry, the service is currently under maintenance. Please try again later!', error500Message: 'Sorry, the service is currently under maintenance. Please try again later!',

View File

@ -23,7 +23,8 @@ export default {
oppose: '反对', oppose: '反对',
cancelOppose: '取消反对', cancelOppose: '取消反对',
continue: '继续', continue: '继续',
stopChat: '停止回答' stopChat: '停止回答',
startChat: '开始回答',
}, },
tip: { tip: {
error500Message: '抱歉,当前正在维护,无法提供服务,请稍后再试!', error500Message: '抱歉,当前正在维护,无法提供服务,请稍后再试!',

View File

@ -23,7 +23,8 @@ export default {
oppose: '反對', oppose: '反對',
cancelOppose: '取消反對', cancelOppose: '取消反對',
continue: '繼續', continue: '繼續',
stopChat: '停止回答' stopChat: '停止回答',
startChat: '開始回答',
}, },
tip: { tip: {
error500Message: '抱歉,當前正在維護,無法提供服務,請稍後再試!', error500Message: '抱歉,當前正在維護,無法提供服務,請稍後再試!',

View File

@ -156,8 +156,8 @@
</el-button> </el-button>
</div> </div>
</div> </div>
<EditTitleDialog ref="EditTitleDialogRef" @refresh="refreshFieldTitle" />
</div> </div>
<EditTitleDialog ref="EditTitleDialogRef" @refresh="refreshFieldTitle" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">