feat: Optimize the mobile voice interaction experience

* fix: Optimize small screen dialogue style

* feat: Mobile voice conversation new UI

* feat: Optimize the mobile voice interaction experience

* feat: Optimize the mobile voice interaction experience
This commit is contained in:
wangdan-fit2cloud 2025-03-21 16:57:04 +08:00 committed by GitHub
parent 2faabbe392
commit 0eebbb094c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
56 changed files with 564 additions and 203 deletions

20
ui/public/index.html Normal file
View File

@ -0,0 +1,20 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no,
viewport-fit=cover"
/>
<title>111</title>
</head>
<body style="margin: 0; padding: 0; height: 100vh">
<script
async
defer
src="http://localhost:3000/api/application/embed?protocol=http&host=localhost:3000&token=7ca95a6b12571284">
</script>
</body>
</html>

View File

@ -0,0 +1,29 @@
<svg width="89" height="22" viewBox="0 0 89 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_11132_142017)">
<path d="M9.58875 8.33325H11.1126V14.0475H9.58875V8.33325Z" fill="#3370FF"/>
<path d="M36.8547 8.33325H38.3785V14.0475H36.8547V8.33325Z" fill="#3370FF"/>
<path d="M59.5767 8.33325H61.1005V14.0475H59.5767V8.33325Z" fill="#3370FF"/>
<path d="M86.843 8.33325H88.3668V14.0475H86.843V8.33325Z" fill="#3370FF"/>
<path d="M41.399 6.80957H42.9229V14.8096H41.399V6.80957Z" fill="#3370FF"/>
<path d="M55.0322 6.04736H56.556V15.5712H55.0322V6.04736Z" fill="#3370FF"/>
<path d="M45.9435 8.71411H47.4673V13.2855H45.9435V8.71411Z" fill="#3370FF"/>
<path d="M50.4879 7.95239H52.0117V14.4286H50.4879V7.95239Z" fill="#3370FF"/>
<path d="M5.04443 6.04736H6.56824V15.5712H5.04443V6.04736Z" fill="#3370FF"/>
<path d="M0.5 8.71411H2.02381V13.2855H0.5V8.71411Z" fill="#3370FF"/>
<path d="M14.1332 6.80957H15.657V15.1905H14.1332V6.80957Z" fill="#3370FF"/>
<path d="M32.3103 6.80957H33.8341V15.1905H32.3103V6.80957Z" fill="#3370FF"/>
<path d="M64.1211 6.80957H65.6449V15.5715H64.1211V6.80957Z" fill="#3370FF"/>
<path d="M82.2986 6.80957H83.8224V15.1905H82.2986V6.80957Z" fill="#3370FF"/>
<path d="M18.6776 6.04736H20.2014V15.9521H18.6776V6.04736Z" fill="#3370FF"/>
<path d="M27.7664 5.6665H29.2902V16.7141H27.7664V5.6665Z" fill="#3370FF"/>
<path d="M68.6654 5.28564H70.1892V16.7142H68.6654V5.28564Z" fill="#3370FF"/>
<path d="M77.7543 5.28564H79.2781V16.7142H77.7543V5.28564Z" fill="#3370FF"/>
<path d="M23.2219 2.6189H24.7457V19.3808H23.2219V2.6189Z" fill="#3370FF"/>
<path d="M73.2098 3.3811H74.7336V18.6192H73.2098V3.3811Z" fill="#3370FF"/>
</g>
<defs>
<clipPath id="clip0_11132_142017">
<rect width="88" height="22" fill="white" transform="translate(0.5)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,29 @@
<svg width="89" height="22" viewBox="0 0 89 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_11133_227282)">
<path d="M9.58875 8.33325H11.1126V14.0475H9.58875V8.33325Z" fill="#8F959E"/>
<path d="M36.8547 8.33325H38.3785V14.0475H36.8547V8.33325Z" fill="#8F959E"/>
<path d="M59.5767 8.33325H61.1005V14.0475H59.5767V8.33325Z" fill="#8F959E"/>
<path d="M86.843 8.33325H88.3668V14.0475H86.843V8.33325Z" fill="#8F959E"/>
<path d="M41.399 6.80957H42.9229V14.8096H41.399V6.80957Z" fill="#8F959E"/>
<path d="M55.0322 6.04736H56.556V15.5712H55.0322V6.04736Z" fill="#8F959E"/>
<path d="M45.9435 8.71411H47.4673V13.2855H45.9435V8.71411Z" fill="#8F959E"/>
<path d="M50.4879 7.95239H52.0117V14.4286H50.4879V7.95239Z" fill="#8F959E"/>
<path d="M5.04443 6.04736H6.56824V15.5712H5.04443V6.04736Z" fill="#8F959E"/>
<path d="M0.5 8.71411H2.02381V13.2855H0.5V8.71411Z" fill="#8F959E"/>
<path d="M14.1332 6.80957H15.657V15.1905H14.1332V6.80957Z" fill="#8F959E"/>
<path d="M32.3103 6.80957H33.8341V15.1905H32.3103V6.80957Z" fill="#8F959E"/>
<path d="M64.1211 6.80957H65.6449V15.5715H64.1211V6.80957Z" fill="#8F959E"/>
<path d="M82.2986 6.80957H83.8224V15.1905H82.2986V6.80957Z" fill="#8F959E"/>
<path d="M18.6776 6.04736H20.2014V15.9521H18.6776V6.04736Z" fill="#8F959E"/>
<path d="M27.7664 5.6665H29.2902V16.7141H27.7664V5.6665Z" fill="#8F959E"/>
<path d="M68.6654 5.28564H70.1892V16.7142H68.6654V5.28564Z" fill="#8F959E"/>
<path d="M77.7543 5.28564H79.2781V16.7142H77.7543V5.28564Z" fill="#8F959E"/>
<path d="M23.2219 2.6189H24.7457V19.3808H23.2219V2.6189Z" fill="#8F959E"/>
<path d="M73.2098 3.3811H74.7336V18.6192H73.2098V3.3811Z" fill="#8F959E"/>
</g>
<defs>
<clipPath id="clip0_11133_227282">
<rect width="88" height="22" fill="white" transform="translate(0.5)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -112,7 +112,7 @@ const uniqueParagraphList = computed(() => {
})
</script>
<style lang="scss" scoped>
@media only screen and (max-width: 430px) {
@media only screen and (max-width: 420px) {
.chat-knowledge-source {
.execution-details {
display: block;

View File

@ -2,8 +2,8 @@
<div class="item-content mb-16 lighter">
<template v-for="(answer_text, index) in answer_text_list" :key="index">
<div class="avatar">
<img v-if="application.avatar" :src="application.avatar" height="32px" width="32px" />
<LogoIcon v-else height="32px" width="32px" />
<img v-if="application.avatar" :src="application.avatar" height="28px" width="28px" />
<LogoIcon v-else height="28px" width="28px" />
</div>
<div class="content" @mouseup="openControl">
<el-card shadow="always" class="mb-8 border-r-8" style="--el-card-padding: 6px 16px">

View File

@ -0,0 +1,161 @@
<template>
<div class="touch-chat w-full mr-8">
<el-button
text
bg
class="microphone-button w-full mt-8 ml-8 mb-8"
style="font-size: 1rem; padding: 1.2rem 0 !important; background-color: #eff0f1"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@touchend="onTouchEnd"
>
按住说话
</el-button>
<!-- 使用 custom-class 自定义样式 -->
<transition name="el-fade-in-linear">
<el-card class="custom-speech-card" :class="isTouching ? '' : 'active'" v-if="dialogVisible">
<p>
<el-text type="info" v-if="isTouching"
>00:{{ props.time < 10 ? `0${props.time}` : props.time }}</el-text
>
<span class="lighter" v-else>
{{ message }}
</span>
</p>
<div class="close">
<el-icon><Close /></el-icon>
</div>
<p class="lighter" :style="{ visibility: isTouching ? 'visible' : 'hidden' }">
{{ message }}
</p>
<div class="speech-img flex-center border-r-4 mt-16">
<img v-if="isTouching" src="@/assets/acoustic-color.svg" alt="" />
<img v-else src="@/assets/acoustic.svg" alt="" />
</div>
</el-card>
</transition>
</div>
</template>
<script setup lang="ts">
import { el } from 'element-plus/es/locale'
import { ref, watch } from 'vue'
const props = defineProps({
time: {
type: Number,
default: 0
},
start: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['TouchStart', 'TouchEnd'])
//
const startY = ref(0)
const isTouching = ref(false)
const dialogVisible = ref(false)
const message = ref('按住说话')
watch(
() => props.time,
(val) => {
if (val && val === 60) {
dialogVisible.value = false
emit('TouchEnd', isTouching.value)
isTouching.value = false
}
}
)
watch(
() => props.start,
(val) => {
if (val) {
isTouching.value = true
dialogVisible.value = true
message.value = '松开发送,上滑取消'
} else {
dialogVisible.value = false
isTouching.value = false
}
}
)
function onTouchStart(event: any) {
emit('TouchStart')
startY.value = event.touches[0].clientY
//
event.preventDefault()
}
function onTouchMove(event: any) {
if (!isTouching.value) return
//
event.preventDefault()
const currentY = event.touches[0].clientY
const deltaY = currentY - startY.value
//
if (deltaY < -50) {
// -50
message.value = '松开取消发送'
isTouching.value = false
}
}
function onTouchEnd() {
emit('TouchEnd', isTouching.value)
}
</script>
<style lang="scss" scoped>
.custom-speech-card {
position: fixed;
bottom: 10px;
left: 50%; /* 水平居中 */
transform: translateX(-50%);
width: 92%;
background: #ffffff;
border: 1px solid #ffffff;
box-shadow: 0px 6px 24px 0px rgba(31, 35, 41, 0.08);
z-index: 999;
text-align: center;
color: var(--app-text-color-secondary);
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
.close {
box-shadow: 0px 4px 8px 0px rgba(31, 35, 41, 0.1);
border: 1px solid rgba(222, 224, 227, 1);
background: rgba(255, 255, 255, 1);
border-radius: 100px;
display: inline-block;
width: 43px;
height: 43px;
line-height: 50px;
font-size: 1.8rem;
margin: 20px 0;
}
.speech-img {
text-align: center;
background: #ebf1ff;
padding: 8px;
img {
height: 25px;
}
}
&.active {
.close {
background: #f54a45;
color: #ffffff;
width: 50px;
height: 50px;
line-height: 57px;
font-size: 2rem;
}
.speech-img {
background: #eff0f1;
}
}
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div class="ai-chat__operate p-16-24">
<div class="ai-chat__operate p-16">
<slot name="operateBefore" />
<div class="operate-textarea">
<el-scrollbar max-height="136">
@ -114,7 +114,15 @@
</div>
</el-scrollbar>
<div class="flex">
<TouchChat
v-if="isMicrophone"
@TouchStart="startRecording"
@TouchEnd="TouchEnd"
:time="recorderTime"
:start="!mediaRecorderStatus"
/>
<el-input
v-else
ref="quickInputRef"
v-model="inputValue"
:placeholder="
@ -131,61 +139,82 @@
/>
<div class="operate flex align-center">
<span v-if="props.applicationDetails.file_upload_enable" class="flex align-center">
<el-upload
action="#"
multiple
:auto-upload="false"
:show-file-list="false"
:accept="getAcceptList()"
:on-change="(file: any, fileList: any) => uploadFile(file, fileList)"
>
<el-tooltip effect="dark" placement="top" popper-class="upload-tooltip-width">
<template #content>
<div class="break-all pre-wrap">
{{ $t('chat.uploadFile.label') }}{{ $t('chat.uploadFile.most')
}}{{ props.applicationDetails.file_upload_setting.maxFiles
}}{{ $t('chat.uploadFile.limit') }}
{{ props.applicationDetails.file_upload_setting.fileLimit }}MB<br />{{
$t('chat.uploadFile.fileType')
}}{{ getAcceptList().replace(/\./g, '').replace(/,/g, '、').toUpperCase() }}
</div>
</template>
<el-button text :disabled="checkMaxFilesLimit()" class="mt-4">
<el-icon><Paperclip /></el-icon>
</el-button>
</el-tooltip>
</el-upload>
<el-divider direction="vertical" />
</span>
<span v-if="props.applicationDetails.stt_model_enable" class="flex align-center">
<el-button text @click="startRecording" v-if="mediaRecorderStatus">
<el-icon>
<Microphone />
</el-icon>
</el-button>
<div v-else class="operate flex align-center">
<el-text type="info"
>00:{{ recorderTime < 10 ? `0${recorderTime}` : recorderTime }}</el-text
>
<el-button text type="primary" @click="stopRecording" :loading="recorderLoading">
<AppIcon iconName="app-video-stop"></AppIcon>
<template v-if="props.applicationDetails.stt_model_enable">
<span v-if="mode === 'mobile'">
<el-button text @click="isMicrophone = !isMicrophone">
<AppIcon v-if="isMicrophone" iconName="app-keyboard"></AppIcon>
<el-icon v-else>
<Microphone />
</el-icon>
</el-button>
</span>
<span class="flex align-center" v-else>
<el-button text @click="startRecording" v-if="mediaRecorderStatus">
<el-icon>
<Microphone />
</el-icon>
</el-button>
</div>
<el-divider v-if="!startRecorderTime && !recorderLoading" direction="vertical" />
</span>
<el-button
v-if="!startRecorderTime && !recorderLoading"
text
class="sent-button"
:disabled="isDisabledChat || loading"
@click="sendChatHandle"
>
<img v-show="isDisabledChat || loading" src="@/assets/icon_send.svg" alt="" />
<SendIcon v-show="!isDisabledChat && !loading" />
</el-button>
<div v-else class="operate flex align-center">
<el-text type="info"
>00:{{ recorderTime < 10 ? `0${recorderTime}` : recorderTime }}</el-text
>
<el-button text type="primary" @click="stopRecording" :loading="recorderLoading">
<AppIcon iconName="app-video-stop"></AppIcon>
</el-button>
</div>
</span>
</template>
<template v-if="!startRecorderTime && !recorderLoading">
<span v-if="props.applicationDetails.file_upload_enable" class="flex align-center ml-4">
<el-upload
action="#"
multiple
:auto-upload="false"
:show-file-list="false"
:accept="getAcceptList()"
:on-change="(file: any, fileList: any) => uploadFile(file, fileList)"
>
<el-tooltip
:disabled="mode === 'mobile'"
effect="dark"
placement="top"
popper-class="upload-tooltip-width"
>
<template #content>
<div class="break-all pre-wrap">
{{ $t('chat.uploadFile.label') }}{{ $t('chat.uploadFile.most')
}}{{ props.applicationDetails.file_upload_setting.maxFiles
}}{{ $t('chat.uploadFile.limit') }}
{{ props.applicationDetails.file_upload_setting.fileLimit }}MB<br />{{
$t('chat.uploadFile.fileType')
}}{{ getAcceptList().replace(/\./g, '').replace(/,/g, '、').toUpperCase() }}
</div>
</template>
<el-button text :disabled="checkMaxFilesLimit()" class="mt-4">
<el-icon><Paperclip /></el-icon>
</el-button>
</el-tooltip>
</el-upload>
</span>
<el-divider
direction="vertical"
v-if="
props.applicationDetails.file_upload_enable ||
props.applicationDetails.stt_model_enable
"
/>
<el-button
text
class="sent-button"
:disabled="isDisabledChat || loading"
@click="sendChatHandle"
>
<img v-show="isDisabledChat || loading" src="@/assets/icon_send.svg" alt="" />
<SendIcon v-show="!isDisabledChat && !loading" />
</el-button>
</template>
</div>
</div>
</div>
@ -201,6 +230,7 @@
<script setup lang="ts">
import { ref, computed, onMounted, nextTick } from 'vue'
import Recorder from 'recorder-core'
import TouchChat from './TouchChat.vue'
import applicationApi from '@/api/application'
import { MsgAlert } from '@/utils/message'
import { type chatType } from '@/api/type/application'
@ -381,6 +411,8 @@ const uploadFile = async (file: any, fileList: any) => {
}
})
}
const intervalId = ref<number | null>(null)
const recorderTime = ref(0)
const startRecorderTime = ref(false)
const recorderLoading = ref(false)
@ -397,14 +429,24 @@ const mediaRecorder = ref<any>(null)
const isDisabledChat = computed(
() => !(inputValue.value.trim() && (props.appId || props.applicationDetails?.name))
)
//
const isMicrophone = ref(false)
const TouchEnd = (bool: Boolean) => {
if (bool) {
stopRecording()
} else {
stopTimer()
mediaRecorder.value.close()
mediaRecorder.value = null
}
}
//
const startRecording = async () => {
try {
//
Recorder.CLog = function () {}
mediaRecorderStatus.value = false
handleTimeChange()
mediaRecorder.value = new Recorder({
type: 'mp3',
bitRate: 128,
@ -414,8 +456,12 @@ const startRecording = async () => {
mediaRecorder.value.open(
() => {
mediaRecorder.value.start()
mediaRecorderStatus.value = false
handleTimeChange()
},
(err: any) => {
stopTimer()
mediaRecorder.value.close()
MsgAlert(
t('common.tip'),
`${t('chat.tip.recorderTip')}
@ -439,6 +485,8 @@ const startRecording = async () => {
customClass: 'record-tip-confirm'
}
)
mediaRecorder.value.close()
stopTimer()
}
}
@ -479,8 +527,10 @@ const uploadRecording = async (audioBlob: Blob) => {
//
if (props.applicationDetails.stt_autosend) {
nextTick(() => {
autoSendMessage()
autoSendMessage()
})
} else {
isMicrophone.value = false
}
})
.catch((error) => {
@ -492,22 +542,36 @@ const uploadRecording = async (audioBlob: Blob) => {
console.error(`${t('chat.uploadFile.errorMessage')}:`, error)
}
}
const handleTimeChange = () => {
startRecorderTime.value = true
setTimeout(() => {
if (recorderTime.value === 60) {
recorderTime.value = 0
stopRecording()
startRecorderTime.value = false
}
recorderTime.value = 0
intervalId.value = setInterval(() => {
if (!startRecorderTime.value) {
clearInterval(intervalId.value!)
intervalId.value = null
return
}
recorderTime.value++
handleTimeChange()
if (recorderTime.value === 60) {
stopRecording()
clearInterval(intervalId.value!)
intervalId.value = null
startRecorderTime.value = false
}
}, 1000)
}
//
const stopTimer = () => {
if (intervalId.value !== null) {
clearInterval(intervalId.value)
intervalId.value = null
startRecorderTime.value = false
mediaRecorderStatus.value = true
}
}
function autoSendMessage() {
props.sendMessage(inputValue.value, {
@ -604,22 +668,98 @@ onMounted(() => {
}, 1800)
})
</script>
<style lang="scss" scope>
@import '../../index.scss';
<style lang="scss" scoped>
.ai-chat {
&__operate {
background: #f3f7f9;
position: relative;
width: 100%;
box-sizing: border-box;
z-index: 10;
.file {
position: relative;
overflow: inherit;
&:before {
background: linear-gradient(0deg, #f3f7f9 0%, rgba(243, 247, 249, 0) 100%);
content: '';
position: absolute;
width: 100%;
top: -16px;
left: 0;
height: 16px;
}
.delete-icon {
position: absolute;
right: -5px;
top: -5px;
z-index: 1;
:deep(.operate-textarea) {
box-shadow: 0px 6px 24px 0px rgba(31, 35, 41, 0.08);
background-color: #ffffff;
border-radius: 8px;
border: 1px solid #ffffff;
box-sizing: border-box;
&:has(.el-textarea__inner:focus) {
border: 1px solid var(--el-color-primary);
}
.el-textarea__inner {
border-radius: 8px !important;
box-shadow: none;
resize: none;
padding: 13px 16px;
box-sizing: border-box;
}
.operate {
padding: 6px 10px;
.el-icon {
font-size: 20px;
}
.sent-button {
max-height: none;
.el-icon {
font-size: 24px;
}
}
.el-loading-spinner {
margin-top: -15px;
.circular {
width: 31px;
height: 31px;
}
}
}
}
.file {
position: relative;
overflow: inherit;
.delete-icon {
position: absolute;
right: -5px;
top: -5px;
z-index: 1;
}
}
.upload-tooltip-width {
width: 300px;
}
}
}
.upload-tooltip-width {
width: 300px;
@media only screen and (max-width: 768px) {
.ai-chat {
height: calc(100% - 100px);
&__operate {
position: fixed;
bottom: 0;
font-size: 1rem;
.el-icon {
font-size: 1.4rem !important;
}
}
}
}
.chat-pc {
}
</style>

View File

@ -307,7 +307,9 @@ onMounted(() => {
})
</script>
<style lang="scss" scoped>
@media only screen and (max-width: 430px) {
@media only screen and (max-width: 420px) {
.chat-operation-button {
display: block;
}

View File

@ -1,5 +1,7 @@
<template>
<div class="chat-operation-button">
<div class="operation-button-container">
<LogOperationButton
v-if="type === 'log'"
v-bind:data="chatRecord"

View File

@ -2,8 +2,8 @@
<!-- 开场白组件 -->
<div class="item-content mb-16">
<div class="avatar" v-if="prologue">
<img v-if="application.avatar" :src="application.avatar" height="32px" width="32px" />
<LogoIcon v-else height="32px" width="32px" />
<img v-if="application.avatar" :src="application.avatar" height="28px" width="28px" />
<LogoIcon v-else height="28px" width="28px" />
</div>
<div class="content" v-if="prologue">
<el-card shadow="always" class="border-r-8" style="--el-card-padding: 10px 16px 12px">

View File

@ -69,7 +69,7 @@
:src="application.user_avatar"
alt=""
fit="cover"
style="width: 32px; height: 32px; display: block"
style="width: 28px; height: 28px; display: block"
/>
<AppAvatar v-else>
<img src="@/assets/user-icon.svg" style="width: 50%" alt="" />

View File

@ -1,5 +1,5 @@
.ai-chat {
--padding-left: 40px;
--padding-left: 36px;
height: 100%;
display: flex;
flex-direction: column;
@ -24,66 +24,6 @@
}
}
}
&__operate {
background: #f3f7f9;
position: relative;
width: 100%;
box-sizing: border-box;
z-index: 10;
&:before {
background: linear-gradient(0deg, #f3f7f9 0%, rgba(243, 247, 249, 0) 100%);
content: '';
position: absolute;
width: 100%;
top: -16px;
left: 0;
height: 16px;
}
.operate-textarea {
box-shadow: 0px 6px 24px 0px rgba(31, 35, 41, 0.08);
background-color: #ffffff;
border-radius: 8px;
border: 1px solid #ffffff;
box-sizing: border-box;
&:has(.el-textarea__inner:focus) {
border: 1px solid var(--el-color-primary);
}
.el-textarea__inner {
border-radius: 8px !important;
box-shadow: none;
resize: none;
padding: 12px 16px;
box-sizing: border-box;
}
.operate {
padding: 6px 10px;
.el-icon {
font-size: 20px;
}
.sent-button {
max-height: none;
.el-icon {
font-size: 24px;
}
}
.el-loading-spinner {
margin-top: -15px;
.circular {
width: 31px;
height: 31px;
}
}
}
}
}
}
.chat-width {
@ -96,3 +36,14 @@
margin: 0 auto;
}
}
@media only screen and (max-width: 768px) {
.ai-chat {
height: calc(100% - 100px);
}
}
.chat-mobile {
.el-button.is-text:not(.is-disabled):hover {
background: none;
}
}

View File

@ -17,7 +17,7 @@
</div>
<template v-if="!isUserInput || !firsUserInput || type === 'log'">
<el-scrollbar ref="scrollDiv" @scroll="handleScrollTop">
<div ref="dialogScrollbar" class="ai-chat__content p-24">
<div ref="dialogScrollbar" class="ai-chat__content p-16">
<PrologueContent
:type="type"
:application="applicationDetails"
@ -514,7 +514,7 @@ defineExpose({
setScrollBottom
})
</script>
<style lang="scss" scoped>
<style lang="scss">
@import './index.scss';
.firstUserInput {
height: 100%;
@ -526,7 +526,7 @@ defineExpose({
position: absolute;
z-index: 999;
right: 50px;
bottom: 80px;
bottom: 0;
width: calc(100% - 50px);
max-width: 400px;
}

View File

@ -296,4 +296,4 @@ const click = () => {
dynamicsFormRef.value?.validate()
}
</script>
<style lang="scss" scope></style>
<style lang="scss" scoped></style>

View File

@ -219,4 +219,4 @@ defineExpose({
ruleFormRef
})
</script>
<style lang="scss" scope></style>
<style lang="scss" scoped></style>

View File

@ -17,7 +17,7 @@ defineProps<{
tooltip: string
}>()
</script>
<style lang="scss" scope>
<style lang="scss" scoped>
.aiMode-param-dialog {
padding: 8px 8px 24px 8px;

View File

@ -187,4 +187,4 @@ function getModel() {
defineExpose({ open })
</script>
<style lang="scss" scope></style>
<style lang="scss" scoped></style>

View File

@ -1415,5 +1415,30 @@ export const iconMap: any = {
)
])
}
},
'app-keyboard': {
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: 'M373.333333 352a53.333333 53.333333 0 1 1-106.666666 0 53.333333 53.333333 0 0 1 106.666666 0zM320 576a53.333333 53.333333 0 1 0 0-106.666667 53.333333 53.333333 0 0 0 0 106.666667zM565.333333 352a53.333333 53.333333 0 1 1-106.666666 0 53.333333 53.333333 0 0 1 106.666666 0zM512 576a53.333333 53.333333 0 1 0 0-106.666667 53.333333 53.333333 0 0 0 0 106.666667zM757.333333 352a53.333333 53.333333 0 1 1-106.666666 0 53.333333 53.333333 0 0 1 106.666666 0zM704 576a53.333333 53.333333 0 1 0 0-106.666667 53.333333 53.333333 0 0 0 0 106.666667zM362.666667 661.333333a42.666667 42.666667 0 1 0 0 85.333334h298.666666a42.666667 42.666667 0 1 0 0-85.333334h-298.666666z',
fill: 'currentColor'
}),
h('path', {
d: 'M512 42.666667C252.8 42.666667 42.666667 252.8 42.666667 512s210.133333 469.333333 469.333333 469.333333 469.333333-210.133333 469.333333-469.333333S771.2 42.666667 512 42.666667zM128 512a384 384 0 1 1 768 0 384 384 0 0 1-768 0z',
fill: 'currentColor'
})
]
)
])
}
}
}

View File

@ -30,7 +30,7 @@ const showBack = computed(() => {
})
</script>
<style lang="scss" scope>
<style lang="scss" scoped>
.content-container {
transition: 0.3s;
padding: 0 var(--app-view-padding) var(--app-view-padding);

View File

@ -20,7 +20,7 @@ defineProps({
subTitle: String
})
</script>
<style lang="scss" scope>
<style lang="scss" scoped>
.login-form-container {
width: 480px;

View File

@ -84,7 +84,7 @@ const loginImage = computed(() => {
}
})
</script>
<style lang="scss" scope>
<style lang="scss" scoped>
.login-warp {
height: 100vh;

View File

@ -2,8 +2,8 @@ import { nextTick, onBeforeMount, onMounted, onBeforeUnmount } from 'vue'
import { useRoute } from 'vue-router'
import useStore from '@/stores'
import { DeviceType } from '@/enums/common'
/** 参考 Bootstrap 的响应式设计 WIDTH = 600 */
const WIDTH = 600
/** 参考 Bootstrap 的响应式设计 WIDTH = 768 */
const WIDTH = 768
/** 根据大小变化重新布局 */
export default () => {

View File

@ -15,6 +15,7 @@
html {
height: 100%;
box-sizing: border-box;
font-size: 100%;
}
body {

View File

@ -140,4 +140,4 @@ function refresh() {
defineExpose({ open })
</script>
<style lang="scss" scope></style>
<style lang="scss" scoped></style>

View File

@ -99,4 +99,4 @@ const submit = async (formEl: FormInstance | undefined) => {
defineExpose({ open })
</script>
<style lang="scss" scope></style>
<style lang="scss" scoped></style>

View File

@ -139,4 +139,4 @@ function submit() {
defineExpose({ open })
</script>
<style lang="scss" scope></style>
<style lang="scss" scoped></style>

View File

@ -132,7 +132,7 @@ src="${window.location.origin}/api/application/embed?protocol=${window.location.
<\/script>
`
source3.value = `<iframe
src="${application.location + val + urlParams1.value}&mode=mobile"
src="${application.location + val + urlParams1.value}?mode=mobile"
style="width: 100%; height: 100%;"
frameborder="0"
allow="microphone">
@ -144,7 +144,7 @@ allow="microphone">
defineExpose({ open })
</script>
<style lang="scss" scope>
<style lang="scss" scoped>
.embed-dialog {
.title {
color: var(--app-text-color) !important;

View File

@ -186,7 +186,7 @@ function firstGeneration() {
defineExpose({ open })
</script>
<style lang="scss" scope>
<style lang="scss" scoped>
.authentication-append-input {
.el-input-group__append {
padding: 0 !important;

View File

@ -110,4 +110,4 @@ const submit = async (formEl: FormInstance | undefined) => {
defineExpose({ open })
</script>
<style lang="scss" scope></style>
<style lang="scss" scoped></style>

View File

@ -561,7 +561,7 @@ const submit = async (formEl: FormInstance | undefined) => {
defineExpose({ open })
</script>
<style lang="scss" scope>
<style lang="scss" scoped>
.setting-preview {
background: #f5f6f7;
height: 570px;

View File

@ -419,7 +419,7 @@
{{ $t('views.application.applicationForm.title.appTest') }}
</h4>
<div class="dialog-bg">
<div class="flex align-center p-24">
<div class="flex align-center p-16 mb-8">
<div
class="edit-avatar mr-12"
@mouseenter="showEditIcon = true"

View File

@ -144,7 +144,7 @@ const refresh = () => {
defineExpose({ open })
</script>
<style lang="scss" scope>
<style lang="scss" scoped>
.addDataset-dialog {
padding: 0;
.el-dialog__header {

View File

@ -175,4 +175,4 @@ const submitHandle = async (formEl: FormInstance | undefined) => {
defineExpose({ open })
</script>
<style lang="scss" scope></style>
<style lang="scss" scoped></style>

View File

@ -259,7 +259,7 @@ function selectedType(type: string) {
defineExpose({ open })
</script>
<style lang="scss" scope>
<style lang="scss" scoped>
.radio-card {
line-height: 22px;
&.active {

View File

@ -326,7 +326,7 @@ function changeHandle(val: string) {
defineExpose({ open })
</script>
<style lang="scss" scope>
<style lang="scss" scoped>
.param-dialog {
padding: 8px 8px 24px 8px;
.el-dialog__header {

View File

@ -82,7 +82,7 @@ const submit = () => {
defineExpose({ open })
</script>
<style lang="scss" scope>
<style lang="scss" scoped>
.param-dialog {
padding: 8px 8px 24px 8px;

View File

@ -330,8 +330,8 @@ onMounted(() => {
border-bottom: 1px solid var(--el-border-color);
}
&__main {
padding-top: calc(var(--app-header-height) + 24px);
height: calc(100vh - var(--app-header-height) - 24px);
padding-top: calc(var(--app-header-height) + 16px);
height: calc(100vh - var(--app-header-height) - 16px);
overflow: hidden;
}
.new-chat-button {

View File

@ -1,6 +1,6 @@
<template>
<div
class="chat-embed layout-bg"
class="chat-mobile layout-bg"
v-loading="loading"
:style="{
'--el-color-primary': applicationDetail?.custom_theme?.theme_color,
@ -310,7 +310,7 @@ onMounted(() => {
})
</script>
<style lang="scss">
.chat-embed {
.chat-mobile {
overflow: hidden;
&__header {
background: var(--app-header-bg-color);
@ -325,16 +325,17 @@ onMounted(() => {
border-bottom: 1px solid var(--el-border-color);
}
&__main {
padding-top: calc(var(--app-header-height) + 24px);
height: calc(100vh - var(--app-header-height) - 24px);
padding-top: calc(var(--app-header-height) + 16px);
height: calc(100vh - var(--app-header-height) - 16px);
overflow: hidden;
}
.new-chat-button {
z-index: 11;
font-size: 1rem;
}
//
.chat-popover {
position: absolute;
position: fixed;
top: var(--app-header-height);
background: #ffffff;
padding-bottom: 24px;
@ -342,7 +343,7 @@ onMounted(() => {
}
.chat-popover-button {
z-index: 2009;
position: absolute;
position: fixed;
top: 16px;
right: 16px;
font-size: 22px;
@ -386,11 +387,11 @@ onMounted(() => {
top: 50%;
}
}
.AiChat-embed {
.ai-chat__operate {
padding-top: 12px;
}
}
// .AiChat-embed {
// .ai-chat__operate {
// padding-top: 12px;
// }
// }
}
</style>
<style lang="scss" scoped>

View File

@ -326,4 +326,4 @@ function radioChange() {
defineExpose({ open })
</script>
<style lang="scss" scope></style>
<style lang="scss" scoped></style>

View File

@ -132,4 +132,4 @@ const submitHandle = async () => {
defineExpose({ open })
</script>
<style lang="scss" scope></style>
<style lang="scss" scoped></style>

View File

@ -121,7 +121,7 @@ const refresh = () => {
defineExpose({ open })
</script>
<style lang="scss" scope>
<style lang="scss" scoped>
.select-dataset-dialog {
padding: 0;
.el-dialog__header {

View File

@ -128,4 +128,4 @@ function submit() {
defineExpose({open})
</script>
<style lang="scss" scope></style>
<style lang="scss" scoped></style>

View File

@ -295,4 +295,4 @@ const submitForm = async (formEl: FormInstance | undefined) => {
defineExpose({ open })
</script>
<style lang="scss" scope></style>
<style lang="scss" scoped></style>

View File

@ -151,7 +151,7 @@ const submit = async (formEl: FormInstance) => {
defineExpose({ open })
</script>
<style lang="scss" scope>
<style lang="scss" scoped>
.edit-mark-dialog {
.el-dialog__header.show-close {
padding-right: 15px;

View File

@ -137,4 +137,4 @@ const handleTimeChange = () => {
}
}
</script>
<style lang="scss" scope></style>
<style lang="scss" scoped></style>

View File

@ -349,7 +349,7 @@ onMounted(() => {
}
})
</script>
<style lang="scss" scope>
<style lang="scss" scoped>
.login-gradient-divider {
position: relative;
text-align: center;

View File

@ -223,4 +223,4 @@ const handleTimeChange = () => {
}
}
</script>
<style lang="scss" scope></style>
<style lang="scss" scoped></style>

View File

@ -132,4 +132,4 @@ const resetPassword = () => {
})
}
</script>
<style lang="scss" scope></style>
<style lang="scss" scoped></style>

View File

@ -150,4 +150,4 @@ const handleDebounceClick = debounce(() => {
defineExpose({ open })
</script>
<style lang="scss" scope></style>
<style lang="scss" scoped></style>

View File

@ -167,4 +167,4 @@ const submitForm = async (formEl: FormInstance | undefined) => {
defineExpose({ open })
</script>
<style lang="scss" scope></style>
<style lang="scss" scoped></style>

View File

@ -288,7 +288,7 @@ const open = (problemId: any) => {
defineExpose({ open })
</script>
<style lang="scss" scope>
<style lang="scss" scoped>
.paragraph-card {
position: relative;
}

View File

@ -99,7 +99,7 @@ onMounted(() => {})
defineExpose({ open, close })
</script>
<style lang="scss" scope>
<style lang="scss" scoped>
.member-dialog {
.el-dialog__header {
padding-bottom: 19px;

View File

@ -174,4 +174,4 @@ function checkedOperateChange(Name: string | number, row: any, e: boolean) {
})
}
</script>
<style lang="scss" scope></style>
<style lang="scss" scoped></style>

View File

@ -186,4 +186,4 @@ const submit = async (formEl: FormInstance | undefined) => {
defineExpose({ open })
</script>
<style lang="scss" scope></style>
<style lang="scss" scoped></style>

View File

@ -130,4 +130,4 @@ const submit = async (formEl: FormInstance | undefined) => {
defineExpose({ open })
</script>
<style lang="scss" scope></style>
<style lang="scss" scoped></style>

View File

@ -125,7 +125,7 @@ const submit = () => {
defineExpose({ open })
</script>
<style lang="scss" scope>
<style lang="scss" scoped>
.param-dialog {
padding: 8px 8px 24px 8px;
.el-dialog__header {