2024-11-13 02:37:16 +00:00
|
|
|
|
<template>
|
2025-03-21 08:57:04 +00:00
|
|
|
|
<div class="ai-chat__operate p-16">
|
2024-11-13 02:37:16 +00:00
|
|
|
|
<slot name="operateBefore" />
|
2024-11-18 10:08:12 +00:00
|
|
|
|
<div class="operate-textarea">
|
|
|
|
|
|
<el-scrollbar max-height="136">
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="p-8-12"
|
|
|
|
|
|
v-loading="localLoading"
|
2024-12-10 03:01:35 +00:00
|
|
|
|
v-if="
|
|
|
|
|
|
uploadDocumentList.length ||
|
|
|
|
|
|
uploadImageList.length ||
|
|
|
|
|
|
uploadAudioList.length ||
|
|
|
|
|
|
uploadVideoList.length
|
|
|
|
|
|
"
|
2024-11-18 10:08:12 +00:00
|
|
|
|
>
|
2025-01-21 12:05:31 +00:00
|
|
|
|
<el-row :gutter="10">
|
|
|
|
|
|
<el-col
|
|
|
|
|
|
v-for="(item, index) in uploadDocumentList"
|
|
|
|
|
|
:key="index"
|
|
|
|
|
|
:xs="24"
|
2025-02-09 05:36:08 +00:00
|
|
|
|
:sm="props.type === 'debug-ai-chat' ? 24 : 12"
|
|
|
|
|
|
:md="props.type === 'debug-ai-chat' ? 24 : 12"
|
|
|
|
|
|
:lg="props.type === 'debug-ai-chat' ? 24 : 12"
|
|
|
|
|
|
:xl="props.type === 'debug-ai-chat' ? 24 : 12"
|
2025-01-21 12:05:31 +00:00
|
|
|
|
class="mb-8"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-card
|
|
|
|
|
|
shadow="never"
|
|
|
|
|
|
style="--el-card-padding: 8px; max-width: 100%"
|
|
|
|
|
|
class="file cursor"
|
|
|
|
|
|
>
|
2024-11-18 10:08:12 +00:00
|
|
|
|
<div
|
|
|
|
|
|
class="flex align-center"
|
|
|
|
|
|
@mouseenter.stop="mouseenter(item)"
|
|
|
|
|
|
@mouseleave.stop="mouseleave()"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div
|
|
|
|
|
|
@click="deleteFile(index, 'document')"
|
|
|
|
|
|
class="delete-icon color-secondary"
|
|
|
|
|
|
v-if="showDelete === item.url"
|
|
|
|
|
|
>
|
2024-12-17 10:21:33 +00:00
|
|
|
|
<el-icon>
|
|
|
|
|
|
<CircleCloseFilled />
|
|
|
|
|
|
</el-icon>
|
2024-11-18 10:08:12 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<img :src="getImgUrl(item && item?.name)" alt="" width="24" />
|
2025-01-21 12:05:31 +00:00
|
|
|
|
<div class="ml-4 ellipsis-1" :title="item && item?.name">
|
2024-11-18 10:08:12 +00:00
|
|
|
|
{{ item && item?.name }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-card>
|
2025-01-21 12:05:31 +00:00
|
|
|
|
</el-col>
|
2025-02-07 07:50:19 +00:00
|
|
|
|
|
2025-01-21 12:05:31 +00:00
|
|
|
|
<el-col
|
|
|
|
|
|
:xs="24"
|
2025-02-09 05:36:08 +00:00
|
|
|
|
:sm="props.type === 'debug-ai-chat' ? 24 : 12"
|
|
|
|
|
|
:md="props.type === 'debug-ai-chat' ? 24 : 12"
|
|
|
|
|
|
:lg="props.type === 'debug-ai-chat' ? 24 : 12"
|
|
|
|
|
|
:xl="props.type === 'debug-ai-chat' ? 24 : 12"
|
2025-01-21 12:05:31 +00:00
|
|
|
|
class="mb-8"
|
|
|
|
|
|
v-for="(item, index) in uploadAudioList"
|
|
|
|
|
|
:key="index"
|
|
|
|
|
|
>
|
2024-12-06 10:14:04 +00:00
|
|
|
|
<el-card shadow="never" style="--el-card-padding: 8px" class="file cursor">
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="flex align-center"
|
|
|
|
|
|
@mouseenter.stop="mouseenter(item)"
|
|
|
|
|
|
@mouseleave.stop="mouseleave()"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div
|
|
|
|
|
|
@click="deleteFile(index, 'audio')"
|
|
|
|
|
|
class="delete-icon color-secondary"
|
|
|
|
|
|
v-if="showDelete === item.url"
|
|
|
|
|
|
>
|
2024-12-17 10:21:33 +00:00
|
|
|
|
<el-icon>
|
|
|
|
|
|
<CircleCloseFilled />
|
|
|
|
|
|
</el-icon>
|
2024-12-06 10:14:04 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<img :src="getImgUrl(item && item?.name)" alt="" width="24" />
|
2025-01-21 12:05:31 +00:00
|
|
|
|
<div class="ml-4 ellipsis-1" :title="item && item?.name">
|
2024-12-06 10:14:04 +00:00
|
|
|
|
{{ item && item?.name }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-card>
|
2025-01-21 12:05:31 +00:00
|
|
|
|
</el-col>
|
|
|
|
|
|
</el-row>
|
2025-02-09 05:36:08 +00:00
|
|
|
|
<el-space wrap>
|
2025-02-07 07:50:19 +00:00
|
|
|
|
<template v-for="(item, index) in uploadImageList" :key="index">
|
|
|
|
|
|
<div
|
2025-02-09 05:36:08 +00:00
|
|
|
|
class="file cursor border border-r-4"
|
2025-02-07 07:50:19 +00:00
|
|
|
|
v-if="item.url"
|
|
|
|
|
|
@mouseenter.stop="mouseenter(item)"
|
|
|
|
|
|
@mouseleave.stop="mouseleave()"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div
|
|
|
|
|
|
@click="deleteFile(index, 'image')"
|
|
|
|
|
|
class="delete-icon color-secondary"
|
|
|
|
|
|
v-if="showDelete === item.url"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-icon>
|
|
|
|
|
|
<CircleCloseFilled />
|
|
|
|
|
|
</el-icon>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<el-image
|
|
|
|
|
|
:src="item.url"
|
|
|
|
|
|
alt=""
|
|
|
|
|
|
fit="cover"
|
|
|
|
|
|
style="width: 40px; height: 40px; display: block"
|
|
|
|
|
|
class="border-r-4"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
2025-02-09 05:36:08 +00:00
|
|
|
|
</el-space>
|
2024-11-18 10:08:12 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</el-scrollbar>
|
2025-04-01 11:41:01 +00:00
|
|
|
|
<div class="flex" :style="{ alignItems: isMicrophone ? 'center' : 'end' }">
|
2025-03-21 08:57:04 +00:00
|
|
|
|
<TouchChat
|
|
|
|
|
|
v-if="isMicrophone"
|
|
|
|
|
|
@TouchStart="startRecording"
|
|
|
|
|
|
@TouchEnd="TouchEnd"
|
|
|
|
|
|
:time="recorderTime"
|
2025-03-27 11:53:35 +00:00
|
|
|
|
:start="recorderStatus === 'START'"
|
2025-03-26 10:55:39 +00:00
|
|
|
|
:disabled="loading"
|
2025-03-21 08:57:04 +00:00
|
|
|
|
/>
|
2024-11-18 10:08:12 +00:00
|
|
|
|
<el-input
|
2025-03-21 08:57:04 +00:00
|
|
|
|
v-else
|
2024-11-18 10:08:12 +00:00
|
|
|
|
ref="quickInputRef"
|
|
|
|
|
|
v-model="inputValue"
|
|
|
|
|
|
:placeholder="
|
2025-03-27 11:53:35 +00:00
|
|
|
|
recorderStatus === 'START'
|
2025-01-21 07:25:21 +00:00
|
|
|
|
? `${$t('chat.inputPlaceholder.speaking')}...`
|
2025-03-27 11:53:35 +00:00
|
|
|
|
: recorderStatus === 'TRANSCRIBING'
|
2025-01-21 07:25:21 +00:00
|
|
|
|
? `${$t('chat.inputPlaceholder.recorderLoading')}...`
|
|
|
|
|
|
: $t('chat.inputPlaceholder.default')
|
2024-11-18 10:08:12 +00:00
|
|
|
|
"
|
|
|
|
|
|
:autosize="{ minRows: 1, maxRows: isMobile ? 4 : 10 }"
|
|
|
|
|
|
type="textarea"
|
|
|
|
|
|
:maxlength="100000"
|
|
|
|
|
|
@keydown.enter="sendChatHandle($event)"
|
|
|
|
|
|
/>
|
2024-11-13 02:37:16 +00:00
|
|
|
|
|
2024-11-18 10:08:12 +00:00
|
|
|
|
<div class="operate flex align-center">
|
2025-03-21 08:57:04 +00:00
|
|
|
|
<template v-if="props.applicationDetails.stt_model_enable">
|
|
|
|
|
|
<span v-if="mode === 'mobile'">
|
2025-03-28 05:52:54 +00:00
|
|
|
|
<el-button text @click="switchMicrophone(!isMicrophone)">
|
2025-03-27 11:53:35 +00:00
|
|
|
|
<!-- 键盘 -->
|
2025-03-21 08:57:04 +00:00
|
|
|
|
<AppIcon v-if="isMicrophone" iconName="app-keyboard"></AppIcon>
|
|
|
|
|
|
<el-icon v-else>
|
2025-03-27 11:53:35 +00:00
|
|
|
|
<!-- 录音 -->
|
2025-03-21 08:57:04 +00:00
|
|
|
|
<Microphone />
|
|
|
|
|
|
</el-icon>
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span class="flex align-center" v-else>
|
2025-03-26 10:55:39 +00:00
|
|
|
|
<el-button
|
|
|
|
|
|
:disabled="loading"
|
|
|
|
|
|
text
|
|
|
|
|
|
@click="startRecording"
|
2025-03-27 11:53:35 +00:00
|
|
|
|
v-if="recorderStatus === 'STOP'"
|
2025-03-26 10:55:39 +00:00
|
|
|
|
>
|
2025-03-21 08:57:04 +00:00
|
|
|
|
<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
|
|
|
|
|
|
>
|
2025-03-27 11:53:35 +00:00
|
|
|
|
<el-button
|
|
|
|
|
|
text
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
@click="stopRecording"
|
|
|
|
|
|
:loading="recorderStatus === 'TRANSCRIBING'"
|
|
|
|
|
|
>
|
2025-03-21 08:57:04 +00:00
|
|
|
|
<AppIcon iconName="app-video-stop"></AppIcon>
|
2024-11-22 08:27:43 +00:00
|
|
|
|
</el-button>
|
2025-03-21 08:57:04 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</template>
|
2024-11-22 08:27:43 +00:00
|
|
|
|
|
2025-03-27 11:53:35 +00:00
|
|
|
|
<template v-if="recorderStatus === 'STOP' || mode === 'mobile'">
|
2025-03-21 08:57:04 +00:00
|
|
|
|
<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)"
|
2024-11-18 10:08:12 +00:00
|
|
|
|
>
|
2025-03-21 08:57:04 +00:00
|
|
|
|
<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>
|
2025-03-26 12:46:14 +00:00
|
|
|
|
<el-button text :disabled="checkMaxFilesLimit() || loading" class="mt-4">
|
2025-03-21 08:57:04 +00:00
|
|
|
|
<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>
|
2024-11-18 10:08:12 +00:00
|
|
|
|
</div>
|
2024-11-13 02:37:16 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2024-11-18 10:08:12 +00:00
|
|
|
|
<div class="text-center" v-if="applicationDetails.disclaimer" style="margin-top: 8px">
|
2024-11-13 02:37:16 +00:00
|
|
|
|
<el-text type="info" v-if="applicationDetails.disclaimer" style="font-size: 12px">
|
|
|
|
|
|
<auto-tooltip :content="applicationDetails.disclaimer_value">
|
|
|
|
|
|
{{ applicationDetails.disclaimer_value }}
|
|
|
|
|
|
</auto-tooltip>
|
|
|
|
|
|
</el-text>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
2025-03-27 11:53:35 +00:00
|
|
|
|
import { ref, computed, onMounted, nextTick, watch } from 'vue'
|
2024-11-13 02:37:16 +00:00
|
|
|
|
import Recorder from 'recorder-core'
|
2025-03-21 08:57:04 +00:00
|
|
|
|
import TouchChat from './TouchChat.vue'
|
2024-11-13 02:37:16 +00:00
|
|
|
|
import applicationApi from '@/api/application'
|
|
|
|
|
|
import { MsgAlert } from '@/utils/message'
|
|
|
|
|
|
import { type chatType } from '@/api/type/application'
|
2024-12-27 10:31:27 +00:00
|
|
|
|
import { useRoute, useRouter } from 'vue-router'
|
2024-11-18 10:08:12 +00:00
|
|
|
|
import { getImgUrl } from '@/utils/utils'
|
2025-01-09 09:59:44 +00:00
|
|
|
|
import bus from '@/bus'
|
2024-11-13 02:37:16 +00:00
|
|
|
|
import 'recorder-core/src/engine/mp3'
|
|
|
|
|
|
import 'recorder-core/src/engine/mp3-engine'
|
|
|
|
|
|
import { MsgWarning } from '@/utils/message'
|
2025-01-20 10:00:53 +00:00
|
|
|
|
import { t } from '@/locales'
|
2024-12-27 10:31:27 +00:00
|
|
|
|
const router = useRouter()
|
2024-11-13 02:37:16 +00:00
|
|
|
|
const route = useRoute()
|
|
|
|
|
|
const {
|
2025-01-21 12:05:31 +00:00
|
|
|
|
query: { mode, question }
|
2024-11-13 02:37:16 +00:00
|
|
|
|
} = route as any
|
|
|
|
|
|
const quickInputRef = ref()
|
|
|
|
|
|
const props = withDefaults(
|
|
|
|
|
|
defineProps<{
|
|
|
|
|
|
applicationDetails: any
|
|
|
|
|
|
type: 'log' | 'ai-chat' | 'debug-ai-chat'
|
|
|
|
|
|
loading: boolean
|
|
|
|
|
|
isMobile: boolean
|
|
|
|
|
|
appId?: string
|
|
|
|
|
|
chatId: string
|
2025-04-02 07:51:15 +00:00
|
|
|
|
showUserInput?: boolean
|
2024-11-13 02:37:16 +00:00
|
|
|
|
sendMessage: (question: string, other_params_data?: any, chat?: chatType) => void
|
2024-11-14 07:28:15 +00:00
|
|
|
|
openChatId: () => Promise<string>
|
2025-04-10 01:48:14 +00:00
|
|
|
|
validate: () => Promise<any>
|
2024-11-13 02:37:16 +00:00
|
|
|
|
}>(),
|
|
|
|
|
|
{
|
|
|
|
|
|
applicationDetails: () => ({}),
|
|
|
|
|
|
available: true
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
2025-04-02 07:51:15 +00:00
|
|
|
|
const emit = defineEmits(['update:chatId', 'update:loading', 'update:showUserInput'])
|
2024-11-13 02:37:16 +00:00
|
|
|
|
const chartOpenId = ref<string>()
|
|
|
|
|
|
const chatId_context = computed({
|
|
|
|
|
|
get: () => {
|
|
|
|
|
|
if (chartOpenId.value) {
|
|
|
|
|
|
return chartOpenId.value
|
|
|
|
|
|
}
|
|
|
|
|
|
return props.chatId
|
|
|
|
|
|
},
|
|
|
|
|
|
set: (v) => {
|
|
|
|
|
|
chartOpenId.value = v
|
|
|
|
|
|
emit('update:chatId', v)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
const localLoading = computed({
|
|
|
|
|
|
get: () => {
|
|
|
|
|
|
return props.loading
|
|
|
|
|
|
},
|
|
|
|
|
|
set: (v) => {
|
|
|
|
|
|
emit('update:loading', v)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2024-11-13 06:48:01 +00:00
|
|
|
|
|
|
|
|
|
|
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp']
|
2024-11-13 10:23:00 +00:00
|
|
|
|
const documentExtensions = ['pdf', 'docx', 'txt', 'xls', 'xlsx', 'md', 'html', 'csv']
|
2024-11-13 06:48:01 +00:00
|
|
|
|
const videoExtensions = ['mp4', 'avi', 'mov', 'mkv', 'flv']
|
2025-02-14 01:53:23 +00:00
|
|
|
|
const audioExtensions = ['mp3', 'wav', 'ogg', 'aac', 'm4a']
|
2024-11-13 06:48:01 +00:00
|
|
|
|
|
2024-11-19 07:29:40 +00:00
|
|
|
|
const getAcceptList = () => {
|
|
|
|
|
|
const { image, document, audio, video } = props.applicationDetails.file_upload_setting
|
2024-11-25 02:21:57 +00:00
|
|
|
|
let accepts: any = []
|
2024-11-19 07:29:40 +00:00
|
|
|
|
if (image) {
|
2024-11-25 02:21:57 +00:00
|
|
|
|
accepts = [...imageExtensions]
|
2024-11-19 07:29:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
if (document) {
|
2024-11-25 02:21:57 +00:00
|
|
|
|
accepts = [...accepts, ...documentExtensions]
|
2024-11-19 07:29:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
if (audio) {
|
2024-11-25 02:21:57 +00:00
|
|
|
|
accepts = [...accepts, ...audioExtensions]
|
2024-11-19 07:29:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
if (video) {
|
2024-11-25 02:21:57 +00:00
|
|
|
|
accepts = [...accepts, ...videoExtensions]
|
2024-11-19 07:29:40 +00:00
|
|
|
|
}
|
2025-01-06 03:08:57 +00:00
|
|
|
|
|
2024-11-27 04:27:27 +00:00
|
|
|
|
if (accepts.length === 0) {
|
2025-01-21 07:25:21 +00:00
|
|
|
|
return `.${t('chat.uploadFile.tipMessage')}`
|
2024-11-27 04:27:27 +00:00
|
|
|
|
}
|
2024-11-25 02:21:57 +00:00
|
|
|
|
return accepts.map((ext: any) => '.' + ext).join(',')
|
2024-11-19 07:29:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-03 09:51:21 +00:00
|
|
|
|
const checkMaxFilesLimit = () => {
|
2024-12-03 09:54:02 +00:00
|
|
|
|
return (
|
|
|
|
|
|
props.applicationDetails.file_upload_setting.maxFiles <=
|
2024-12-10 03:01:35 +00:00
|
|
|
|
uploadImageList.value.length +
|
|
|
|
|
|
uploadDocumentList.value.length +
|
|
|
|
|
|
uploadAudioList.value.length +
|
|
|
|
|
|
uploadVideoList.value.length
|
2024-12-03 09:54:02 +00:00
|
|
|
|
)
|
2024-12-03 09:51:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-13 02:37:16 +00:00
|
|
|
|
const uploadFile = async (file: any, fileList: any) => {
|
|
|
|
|
|
const { maxFiles, fileLimit } = props.applicationDetails.file_upload_setting
|
2024-11-22 07:16:51 +00:00
|
|
|
|
// 单次上传文件数量限制
|
2024-12-10 03:01:35 +00:00
|
|
|
|
const file_limit_once =
|
|
|
|
|
|
uploadImageList.value.length +
|
|
|
|
|
|
uploadDocumentList.value.length +
|
|
|
|
|
|
uploadAudioList.value.length +
|
|
|
|
|
|
uploadVideoList.value.length
|
2024-11-22 07:16:51 +00:00
|
|
|
|
if (file_limit_once >= maxFiles) {
|
2025-01-21 07:25:21 +00:00
|
|
|
|
MsgWarning(t('chat.uploadFile.limitMessage1') + maxFiles + t('chat.uploadFile.limitMessage2'))
|
2024-11-22 07:23:11 +00:00
|
|
|
|
fileList.splice(0, fileList.length)
|
2024-11-13 02:37:16 +00:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (fileList.filter((f: any) => f.size > fileLimit * 1024 * 1024).length > 0) {
|
|
|
|
|
|
// MB
|
2025-01-21 07:25:21 +00:00
|
|
|
|
MsgWarning(t('chat.uploadFile.sizeLimit') + fileLimit + 'MB')
|
2024-11-13 02:37:16 +00:00
|
|
|
|
fileList.splice(0, fileList.length)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2024-12-04 06:36:11 +00:00
|
|
|
|
|
2024-11-13 02:37:16 +00:00
|
|
|
|
const formData = new FormData()
|
2024-12-04 06:36:11 +00:00
|
|
|
|
formData.append('file', file.raw, file.name)
|
|
|
|
|
|
//
|
|
|
|
|
|
const extension = file.name.split('.').pop().toLowerCase() // 获取文件后缀名并转为小写
|
2024-11-13 06:48:01 +00:00
|
|
|
|
|
2024-12-04 06:36:11 +00:00
|
|
|
|
if (imageExtensions.includes(extension)) {
|
|
|
|
|
|
uploadImageList.value.push(file)
|
|
|
|
|
|
} else if (documentExtensions.includes(extension)) {
|
|
|
|
|
|
uploadDocumentList.value.push(file)
|
|
|
|
|
|
} else if (videoExtensions.includes(extension)) {
|
2024-12-06 10:14:04 +00:00
|
|
|
|
uploadVideoList.value.push(file)
|
2024-12-04 06:36:11 +00:00
|
|
|
|
} else if (audioExtensions.includes(extension)) {
|
2024-12-06 10:14:04 +00:00
|
|
|
|
uploadAudioList.value.push(file)
|
2024-11-13 02:37:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!chatId_context.value) {
|
2024-11-14 07:28:15 +00:00
|
|
|
|
const res = await props.openChatId()
|
|
|
|
|
|
chatId_context.value = res
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (props.type === 'debug-ai-chat') {
|
|
|
|
|
|
formData.append('debug', 'true')
|
|
|
|
|
|
} else {
|
|
|
|
|
|
formData.append('debug', 'false')
|
2024-11-13 02:37:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
applicationApi
|
|
|
|
|
|
.uploadFile(
|
|
|
|
|
|
props.applicationDetails.id as string,
|
|
|
|
|
|
chatId_context.value as string,
|
|
|
|
|
|
formData,
|
|
|
|
|
|
localLoading
|
|
|
|
|
|
)
|
|
|
|
|
|
.then((response) => {
|
|
|
|
|
|
fileList.splice(0, fileList.length)
|
2024-11-13 06:48:01 +00:00
|
|
|
|
uploadImageList.value.forEach((file: any) => {
|
2025-03-31 06:59:32 +00:00
|
|
|
|
const f = response.data.filter(
|
|
|
|
|
|
(f: any) => f.name.replaceAll(' ', '') === file.name.replaceAll(' ', '')
|
|
|
|
|
|
)
|
2024-11-13 02:37:16 +00:00
|
|
|
|
if (f.length > 0) {
|
|
|
|
|
|
file.url = f[0].url
|
|
|
|
|
|
file.file_id = f[0].file_id
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2024-11-13 10:23:00 +00:00
|
|
|
|
uploadDocumentList.value.forEach((file: any) => {
|
2025-03-31 06:59:32 +00:00
|
|
|
|
const f = response.data.filter(
|
|
|
|
|
|
(f: any) => f.name.replaceAll(' ', '') == file.name.replaceAll(' ', '')
|
|
|
|
|
|
)
|
2024-11-13 10:23:00 +00:00
|
|
|
|
if (f.length > 0) {
|
|
|
|
|
|
file.url = f[0].url
|
|
|
|
|
|
file.file_id = f[0].file_id
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2024-12-06 10:14:04 +00:00
|
|
|
|
uploadAudioList.value.forEach((file: any) => {
|
2025-03-31 06:59:32 +00:00
|
|
|
|
const f = response.data.filter(
|
|
|
|
|
|
(f: any) => f.name.replaceAll(' ', '') === file.name.replaceAll(' ', '')
|
|
|
|
|
|
)
|
2024-12-06 10:14:04 +00:00
|
|
|
|
if (f.length > 0) {
|
|
|
|
|
|
file.url = f[0].url
|
|
|
|
|
|
file.file_id = f[0].file_id
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
uploadVideoList.value.forEach((file: any) => {
|
2025-03-31 06:59:32 +00:00
|
|
|
|
const f = response.data.filter(
|
|
|
|
|
|
(f: any) => f.name.replaceAll(' ', '') === file.name.replaceAll(' ', '')
|
|
|
|
|
|
)
|
2024-12-06 10:14:04 +00:00
|
|
|
|
if (f.length > 0) {
|
|
|
|
|
|
file.url = f[0].url
|
|
|
|
|
|
file.file_id = f[0].file_id
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2024-12-10 03:01:35 +00:00
|
|
|
|
if (!inputValue.value && uploadImageList.value.length > 0) {
|
2025-01-21 07:25:21 +00:00
|
|
|
|
inputValue.value = t('chat.uploadFile.imageMessage')
|
2024-12-10 03:01:35 +00:00
|
|
|
|
}
|
2024-11-13 02:37:16 +00:00
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-03-27 11:53:35 +00:00
|
|
|
|
// 语音录制任务id
|
2025-03-24 02:26:33 +00:00
|
|
|
|
const intervalId = ref<any | null>(null)
|
2025-03-27 11:53:35 +00:00
|
|
|
|
// 语音录制开始秒数
|
2024-11-13 02:37:16 +00:00
|
|
|
|
const recorderTime = ref(0)
|
2025-03-27 11:53:35 +00:00
|
|
|
|
// START:开始录音 TRANSCRIBING:转换文字中
|
|
|
|
|
|
const recorderStatus = ref<'START' | 'TRANSCRIBING' | 'STOP'>('STOP')
|
|
|
|
|
|
|
2024-11-13 02:37:16 +00:00
|
|
|
|
const inputValue = ref<string>('')
|
2024-11-13 06:48:01 +00:00
|
|
|
|
const uploadImageList = ref<Array<any>>([])
|
|
|
|
|
|
const uploadDocumentList = ref<Array<any>>([])
|
2024-12-06 10:14:04 +00:00
|
|
|
|
const uploadVideoList = ref<Array<any>>([])
|
|
|
|
|
|
const uploadAudioList = ref<Array<any>>([])
|
2025-03-27 11:53:35 +00:00
|
|
|
|
|
2024-11-18 10:08:12 +00:00
|
|
|
|
const showDelete = ref('')
|
|
|
|
|
|
|
2025-03-04 11:32:49 +00:00
|
|
|
|
const isDisabledChat = computed(
|
2024-11-13 02:37:16 +00:00
|
|
|
|
() => !(inputValue.value.trim() && (props.appId || props.applicationDetails?.name))
|
|
|
|
|
|
)
|
2025-03-27 11:53:35 +00:00
|
|
|
|
// 是否显示移动端语音按钮
|
2025-03-21 08:57:04 +00:00
|
|
|
|
const isMicrophone = ref(false)
|
2025-03-28 05:52:54 +00:00
|
|
|
|
const switchMicrophone = (status: boolean) => {
|
|
|
|
|
|
if (status) {
|
2025-03-27 11:53:35 +00:00
|
|
|
|
// 如果显示就申请麦克风权限
|
2025-03-28 05:52:54 +00:00
|
|
|
|
recorderManage.open(() => {
|
|
|
|
|
|
isMicrophone.value = true
|
|
|
|
|
|
})
|
2025-03-27 11:53:35 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
// 关闭麦克风
|
|
|
|
|
|
recorderManage.close()
|
2025-03-28 05:52:54 +00:00
|
|
|
|
isMicrophone.value = false
|
2025-03-27 11:53:35 +00:00
|
|
|
|
}
|
2025-03-28 05:52:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-08 02:52:23 +00:00
|
|
|
|
const TouchEnd = (bool?: Boolean) => {
|
2025-03-21 08:57:04 +00:00
|
|
|
|
if (bool) {
|
|
|
|
|
|
stopRecording()
|
2025-03-27 11:53:35 +00:00
|
|
|
|
recorderStatus.value = 'STOP'
|
2025-03-21 08:57:04 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
stopTimer()
|
2025-03-27 11:53:35 +00:00
|
|
|
|
recorderStatus.value = 'STOP'
|
2025-03-21 08:57:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-03-27 11:53:35 +00:00
|
|
|
|
// 取消录音控制台日志
|
|
|
|
|
|
Recorder.CLog = function () {}
|
2024-11-13 02:37:16 +00:00
|
|
|
|
|
2025-03-27 11:53:35 +00:00
|
|
|
|
class RecorderManage {
|
|
|
|
|
|
recorder?: any
|
|
|
|
|
|
uploadRecording: (blob: Blob, duration: number) => void
|
|
|
|
|
|
constructor(uploadRecording: (blob: Blob, duration: number) => void) {
|
|
|
|
|
|
this.uploadRecording = uploadRecording
|
|
|
|
|
|
}
|
2025-03-28 05:52:54 +00:00
|
|
|
|
open(callback?: () => void) {
|
2025-03-27 11:53:35 +00:00
|
|
|
|
const recorder = new Recorder({
|
2024-11-13 02:37:16 +00:00
|
|
|
|
type: 'mp3',
|
|
|
|
|
|
bitRate: 128,
|
|
|
|
|
|
sampleRate: 16000
|
|
|
|
|
|
})
|
2025-03-27 11:53:35 +00:00
|
|
|
|
if (!this.recorder) {
|
|
|
|
|
|
recorder.open(() => {
|
|
|
|
|
|
this.recorder = recorder
|
2025-03-28 05:52:54 +00:00
|
|
|
|
if (callback) {
|
|
|
|
|
|
callback()
|
|
|
|
|
|
}
|
2025-03-27 11:53:35 +00:00
|
|
|
|
}, this.errorCallBack)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
start() {
|
|
|
|
|
|
if (this.recorder) {
|
|
|
|
|
|
this.recorder.start()
|
|
|
|
|
|
recorderStatus.value = 'START'
|
|
|
|
|
|
handleTimeChange()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const recorder = new Recorder({
|
|
|
|
|
|
type: 'mp3',
|
|
|
|
|
|
bitRate: 128,
|
|
|
|
|
|
sampleRate: 16000
|
|
|
|
|
|
})
|
|
|
|
|
|
recorder.open(() => {
|
|
|
|
|
|
this.recorder = recorder
|
|
|
|
|
|
recorder.start()
|
|
|
|
|
|
recorderStatus.value = 'START'
|
2025-03-21 08:57:04 +00:00
|
|
|
|
handleTimeChange()
|
2025-03-27 11:53:35 +00:00
|
|
|
|
}, this.errorCallBack)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
stop() {
|
|
|
|
|
|
if (this.recorder) {
|
|
|
|
|
|
this.recorder.stop(
|
|
|
|
|
|
(blob: Blob, duration: number) => {
|
|
|
|
|
|
if (mode !== 'mobile') {
|
|
|
|
|
|
this.close()
|
|
|
|
|
|
}
|
|
|
|
|
|
this.uploadRecording(blob, duration)
|
|
|
|
|
|
},
|
|
|
|
|
|
(err: any) => {
|
|
|
|
|
|
MsgAlert(t('common.tip'), err, {
|
2025-01-21 07:25:21 +00:00
|
|
|
|
confirmButtonText: t('chat.tip.confirm'),
|
2024-11-13 02:37:16 +00:00
|
|
|
|
dangerouslyUseHTMLString: true,
|
|
|
|
|
|
customClass: 'record-tip-confirm'
|
2025-03-27 11:53:35 +00:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
close() {
|
|
|
|
|
|
if (this.recorder) {
|
|
|
|
|
|
this.recorder.close()
|
|
|
|
|
|
this.recorder = undefined
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private errorCallBack(err: any, isUserNotAllow: boolean) {
|
|
|
|
|
|
if (isUserNotAllow) {
|
|
|
|
|
|
MsgAlert(t('common.tip'), err, {
|
2025-01-21 07:25:21 +00:00
|
|
|
|
confirmButtonText: t('chat.tip.confirm'),
|
2024-11-13 02:37:16 +00:00
|
|
|
|
dangerouslyUseHTMLString: true,
|
|
|
|
|
|
customClass: 'record-tip-confirm'
|
2025-03-27 11:53:35 +00:00
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
MsgAlert(
|
|
|
|
|
|
t('common.tip'),
|
|
|
|
|
|
`${err}
|
2025-04-01 11:41:01 +00:00
|
|
|
|
<div style="width: 100%;height:1px;border-top:1px var(--el-border-color) var(--el-border-style);margin:10px 0;"></div>
|
2025-03-27 11:53:35 +00:00
|
|
|
|
${t('chat.tip.recorderTip')}
|
|
|
|
|
|
<img src="${new URL(`@/assets/tipIMG.jpg`, import.meta.url).href}" style="width: 100%;" />`,
|
|
|
|
|
|
{
|
|
|
|
|
|
confirmButtonText: t('chat.tip.confirm'),
|
|
|
|
|
|
dangerouslyUseHTMLString: true,
|
|
|
|
|
|
customClass: 'record-tip-confirm'
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
2024-11-13 02:37:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 上传录音文件
|
|
|
|
|
|
const uploadRecording = async (audioBlob: Blob) => {
|
|
|
|
|
|
try {
|
2025-03-26 10:55:39 +00:00
|
|
|
|
// 非自动发送切换输入框
|
|
|
|
|
|
if (!props.applicationDetails.stt_autosend) {
|
2025-03-28 05:52:54 +00:00
|
|
|
|
switchMicrophone(false)
|
2025-03-26 10:55:39 +00:00
|
|
|
|
}
|
2025-03-27 11:53:35 +00:00
|
|
|
|
recorderStatus.value = 'TRANSCRIBING'
|
2024-11-13 02:37:16 +00:00
|
|
|
|
const formData = new FormData()
|
|
|
|
|
|
formData.append('file', audioBlob, 'recording.mp3')
|
2025-03-27 04:08:25 +00:00
|
|
|
|
bus.emit('on:transcribing', true)
|
2024-11-13 02:37:16 +00:00
|
|
|
|
applicationApi
|
|
|
|
|
|
.postSpeechToText(props.applicationDetails.id as string, formData, localLoading)
|
|
|
|
|
|
.then((response) => {
|
|
|
|
|
|
inputValue.value = typeof response.data === 'string' ? response.data : ''
|
2025-01-06 03:08:57 +00:00
|
|
|
|
// 自动发送
|
|
|
|
|
|
if (props.applicationDetails.stt_autosend) {
|
|
|
|
|
|
nextTick(() => {
|
2025-03-24 02:26:33 +00:00
|
|
|
|
autoSendMessage()
|
2025-01-06 03:08:57 +00:00
|
|
|
|
})
|
2025-03-21 08:57:04 +00:00
|
|
|
|
} else {
|
2025-03-28 05:52:54 +00:00
|
|
|
|
switchMicrophone(false)
|
2025-01-06 03:08:57 +00:00
|
|
|
|
}
|
2024-11-13 02:37:16 +00:00
|
|
|
|
})
|
2025-02-08 12:58:22 +00:00
|
|
|
|
.catch((error) => {
|
|
|
|
|
|
console.error(`${t('chat.uploadFile.errorMessage')}:`, error)
|
|
|
|
|
|
})
|
2025-03-27 11:53:35 +00:00
|
|
|
|
.finally(() => {
|
|
|
|
|
|
recorderStatus.value = 'STOP'
|
|
|
|
|
|
bus.emit('on:transcribing', false)
|
|
|
|
|
|
})
|
2024-11-13 02:37:16 +00:00
|
|
|
|
} catch (error) {
|
2025-03-27 11:53:35 +00:00
|
|
|
|
recorderStatus.value = 'STOP'
|
2025-01-21 07:25:21 +00:00
|
|
|
|
console.error(`${t('chat.uploadFile.errorMessage')}:`, error)
|
2024-11-13 02:37:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-03-27 11:53:35 +00:00
|
|
|
|
const recorderManage = new RecorderManage(uploadRecording)
|
|
|
|
|
|
// 开始录音
|
|
|
|
|
|
const startRecording = () => {
|
|
|
|
|
|
recorderManage.start()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 停止录音
|
|
|
|
|
|
const stopRecording = () => {
|
|
|
|
|
|
recorderManage.stop()
|
|
|
|
|
|
}
|
2025-03-21 08:57:04 +00:00
|
|
|
|
|
2024-11-13 02:37:16 +00:00
|
|
|
|
const handleTimeChange = () => {
|
2025-03-21 08:57:04 +00:00
|
|
|
|
recorderTime.value = 0
|
2025-03-27 11:53:35 +00:00
|
|
|
|
if (intervalId.value) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-03-21 08:57:04 +00:00
|
|
|
|
intervalId.value = setInterval(() => {
|
2025-03-27 11:53:35 +00:00
|
|
|
|
if (recorderStatus.value === 'STOP') {
|
2025-03-21 08:57:04 +00:00
|
|
|
|
clearInterval(intervalId.value!)
|
|
|
|
|
|
intervalId.value = null
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
recorderTime.value++
|
2024-11-13 02:37:16 +00:00
|
|
|
|
|
|
|
|
|
|
if (recorderTime.value === 60) {
|
2025-03-27 11:53:35 +00:00
|
|
|
|
if (mode !== 'mobile') {
|
|
|
|
|
|
stopRecording()
|
|
|
|
|
|
clearInterval(intervalId.value!)
|
|
|
|
|
|
intervalId.value = null
|
|
|
|
|
|
recorderStatus.value = 'STOP'
|
|
|
|
|
|
}
|
2024-11-13 02:37:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
}, 1000)
|
|
|
|
|
|
}
|
2025-03-21 08:57:04 +00:00
|
|
|
|
// 停止计时的函数
|
|
|
|
|
|
const stopTimer = () => {
|
|
|
|
|
|
if (intervalId.value !== null) {
|
|
|
|
|
|
clearInterval(intervalId.value)
|
2025-03-27 11:53:35 +00:00
|
|
|
|
recorderTime.value = 0
|
2025-03-21 08:57:04 +00:00
|
|
|
|
intervalId.value = null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-11-13 06:48:01 +00:00
|
|
|
|
|
2025-01-06 03:08:57 +00:00
|
|
|
|
function autoSendMessage() {
|
2025-04-09 10:11:50 +00:00
|
|
|
|
props
|
|
|
|
|
|
.validate()
|
|
|
|
|
|
.then(() => {
|
|
|
|
|
|
props.sendMessage(inputValue.value, {
|
|
|
|
|
|
image_list: uploadImageList.value,
|
|
|
|
|
|
document_list: uploadDocumentList.value,
|
|
|
|
|
|
audio_list: uploadAudioList.value,
|
|
|
|
|
|
video_list: uploadVideoList.value
|
|
|
|
|
|
})
|
|
|
|
|
|
inputValue.value = ''
|
|
|
|
|
|
uploadImageList.value = []
|
|
|
|
|
|
uploadDocumentList.value = []
|
|
|
|
|
|
uploadAudioList.value = []
|
|
|
|
|
|
uploadVideoList.value = []
|
|
|
|
|
|
if (quickInputRef.value) {
|
|
|
|
|
|
quickInputRef.value.textareaStyle.height = '45px'
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(() => {
|
|
|
|
|
|
emit('update:showUserInput', true)
|
|
|
|
|
|
})
|
2025-01-06 03:08:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-24 09:23:47 +00:00
|
|
|
|
function sendChatHandle(event?: any) {
|
2025-03-26 08:00:19 +00:00
|
|
|
|
const isMobile = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
|
|
|
|
|
navigator.userAgent
|
|
|
|
|
|
)
|
|
|
|
|
|
// 如果是移动端,且按下回车键,不直接发送
|
|
|
|
|
|
if ((isMobile || mode === 'mobile') && event?.key === 'Enter') {
|
|
|
|
|
|
// 阻止默认事件
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-03-19 07:31:27 +00:00
|
|
|
|
if (!event?.ctrlKey && !event?.shiftKey && !event?.altKey && !event?.metaKey) {
|
|
|
|
|
|
// 如果没有按下组合键,则会阻止默认事件
|
2024-12-24 09:23:47 +00:00
|
|
|
|
event?.preventDefault()
|
2025-03-04 11:32:49 +00:00
|
|
|
|
if (!isDisabledChat.value && !props.loading && !event?.isComposing) {
|
2024-11-13 02:37:16 +00:00
|
|
|
|
if (inputValue.value.trim()) {
|
2025-01-06 03:08:57 +00:00
|
|
|
|
autoSendMessage()
|
2024-11-13 02:37:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2025-03-19 07:31:27 +00:00
|
|
|
|
// 如果同时按下ctrl/shift/cmd/opt +enter,则会换行
|
|
|
|
|
|
insertNewlineAtCursor(event)
|
2024-11-13 02:37:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-03-19 07:31:27 +00:00
|
|
|
|
const insertNewlineAtCursor = (event?: any) => {
|
2025-04-01 06:21:44 +00:00
|
|
|
|
const textarea = quickInputRef.value.$el.querySelector(
|
|
|
|
|
|
'.el-textarea__inner'
|
|
|
|
|
|
) as HTMLTextAreaElement
|
2025-02-28 08:47:48 +00:00
|
|
|
|
const startPos = textarea.selectionStart
|
|
|
|
|
|
const endPos = textarea.selectionEnd
|
2025-03-19 07:31:27 +00:00
|
|
|
|
// 阻止默认行为(避免额外的换行符)
|
|
|
|
|
|
event.preventDefault()
|
2025-02-28 08:47:48 +00:00
|
|
|
|
// 在光标处插入换行符
|
|
|
|
|
|
inputValue.value = inputValue.value.slice(0, startPos) + '\n' + inputValue.value.slice(endPos)
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
textarea.setSelectionRange(startPos + 1, startPos + 1) // 光标定位到换行后位置
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2024-11-18 10:08:12 +00:00
|
|
|
|
|
|
|
|
|
|
function deleteFile(index: number, val: string) {
|
|
|
|
|
|
if (val === 'image') {
|
|
|
|
|
|
uploadImageList.value.splice(index, 1)
|
|
|
|
|
|
} else if (val === 'document') {
|
|
|
|
|
|
uploadDocumentList.value.splice(index, 1)
|
2024-12-06 10:14:04 +00:00
|
|
|
|
} else if (val === 'video') {
|
|
|
|
|
|
uploadVideoList.value.splice(index, 1)
|
|
|
|
|
|
} else if (val === 'audio') {
|
|
|
|
|
|
uploadAudioList.value.splice(index, 1)
|
2024-11-18 10:08:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-12-17 10:21:33 +00:00
|
|
|
|
|
2024-11-18 10:08:12 +00:00
|
|
|
|
function mouseenter(row: any) {
|
|
|
|
|
|
showDelete.value = row.url
|
|
|
|
|
|
}
|
2024-12-17 10:21:33 +00:00
|
|
|
|
|
2024-11-18 10:08:12 +00:00
|
|
|
|
function mouseleave() {
|
|
|
|
|
|
showDelete.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-13 02:37:16 +00:00
|
|
|
|
onMounted(() => {
|
2025-01-09 09:59:44 +00:00
|
|
|
|
bus.on('chat-input', (message: string) => {
|
|
|
|
|
|
inputValue.value = message
|
|
|
|
|
|
})
|
2024-12-24 09:23:47 +00:00
|
|
|
|
if (question) {
|
|
|
|
|
|
inputValue.value = decodeURIComponent(question.trim())
|
|
|
|
|
|
sendChatHandle()
|
2024-12-27 10:31:27 +00:00
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
// 获取当前路由信息
|
|
|
|
|
|
const route = router.currentRoute.value
|
|
|
|
|
|
// 复制query对象
|
|
|
|
|
|
const query = { ...route.query }
|
|
|
|
|
|
// 删除特定的参数
|
|
|
|
|
|
delete query.question
|
|
|
|
|
|
const newRoute =
|
|
|
|
|
|
Object.entries(query)?.length > 0
|
|
|
|
|
|
? route.path +
|
|
|
|
|
|
'?' +
|
|
|
|
|
|
Object.entries(query)
|
|
|
|
|
|
.map(([key, value]) => `${key}=${value}`)
|
|
|
|
|
|
.join('&')
|
|
|
|
|
|
: route.path
|
|
|
|
|
|
|
|
|
|
|
|
history.pushState(null, '', '/ui' + newRoute)
|
|
|
|
|
|
}, 100)
|
2024-12-24 09:23:47 +00:00
|
|
|
|
}
|
2024-11-13 02:37:16 +00:00
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
if (quickInputRef.value && mode === 'embed') {
|
|
|
|
|
|
quickInputRef.value.textarea.style.height = '0'
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 1800)
|
|
|
|
|
|
})
|
|
|
|
|
|
</script>
|
2025-03-21 08:57:04 +00:00
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.ai-chat {
|
|
|
|
|
|
&__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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
: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;
|
|
|
|
|
|
}
|
2024-11-18 10:08:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-12-17 10:21:33 +00:00
|
|
|
|
|
2025-03-21 08:57:04 +00:00
|
|
|
|
@media only screen and (max-width: 768px) {
|
|
|
|
|
|
.ai-chat {
|
|
|
|
|
|
&__operate {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
|
.el-icon {
|
|
|
|
|
|
font-size: 1.4rem !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-11-13 02:37:16 +00:00
|
|
|
|
</style>
|