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>
<div class="ai-chat__operate p-16-24">
<slot name="operateBefore" />
<div class="operate-textarea">
<el-scrollbar max-height="136">
<div

View File

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

View File

@ -1,56 +1,76 @@
<template>
<div ref="aiChatRef" class="ai-chat" :class="type">
<UserForm
v-model:api_form_data="api_form_data"
v-model:form_data="form_data"
: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'"
<div
v-show="(isUserInput && firsUserInput) || showUserInput"
:class="firsUserInput ? 'firstUserInput' : 'popperUserInput'"
>
<template #operateBefore> <slot name="operateBefore" /> </template>
</ChatInputOperate>
<Control></Control>
<UserForm
v-model:api_form_data="api_form_data"
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>
</template>
<script setup lang="ts">
@ -62,7 +82,7 @@ import { ChatManagement, type chatType } from '@/api/type/application'
import { randomId } from '@/utils/utils'
import useStore from '@/stores'
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 QuestionContent from '@/components/ai-chat/component/question-content/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 api_form_data = ref<any>({})
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(
() => props.chatId,
(val) => {
if (val && val !== 'new') {
chartOpenId.value = val
firsUserInput.value = false
} else {
chartOpenId.value = ''
firsUserInput.value = 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) {
if (!userFormRef.value?.checkInputParam()) {
return
@ -467,4 +508,18 @@ defineExpose({
</script>
<style lang="scss" scoped>
@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>

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',
cancelOppose: 'Undo Dislike',
continue: 'Continue',
stopChat: 'Stop Response'
stopChat: 'Stop Response',
startChat: 'Start Response',
},
tip: {
error500Message: 'Sorry, the service is currently under maintenance. Please try again later!',

View File

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

View File

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

View File

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