feat: add local and URL upload options with validation messages

This commit is contained in:
wxg0103 2025-11-20 10:23:20 +08:00
parent 8c0836627a
commit 56a8795b33
11 changed files with 474 additions and 110 deletions

View File

@ -1,5 +1,5 @@
import { Result } from '@/request/Result'
import { get, post, del, put } from '@/request/index'
import {Result} from '@/request/Result'
import {get, post, del, put} from '@/request/index'
const prefix = '/oss/file'
/**
@ -10,6 +10,10 @@ const postImage: (data: any) => Promise<Result<any>> = (data) => {
return post(`${prefix}`, data)
}
const getFile: (params: any) => Promise<Result<any>> = (params) => {
return get(`/oss/get_url` , params)
}
export default {
postImage,
getFile
}

View File

@ -42,7 +42,7 @@
@mouseleave.stop="mouseleave()"
>
<div class="flex align-center">
<img :src="getImgUrl(item && item?.name)" alt="" width="24" />
<img :src="getImgUrl(item && item?.name)" alt="" width="24"/>
<div class="ml-4 ellipsis-1" :title="item && item?.name">
{{ item && item?.name }}
</div>
@ -53,7 +53,7 @@
v-if="showDelete === item.url"
>
<el-icon style="font-size: 16px; top: 2px">
<CircleCloseFilled />
<CircleCloseFilled/>
</el-icon>
</div>
</div>
@ -80,7 +80,7 @@
@mouseleave.stop="mouseleave()"
>
<div class="flex align-center">
<img :src="getImgUrl(item && item?.name)" alt="" width="24" />
<img :src="getImgUrl(item && item?.name)" alt="" width="24"/>
<div class="ml-4 ellipsis-1" :title="item && item?.name">
{{ item && item?.name }}
</div>
@ -91,7 +91,7 @@
v-if="showDelete === item.url"
>
<el-icon style="font-size: 16px; top: 2px">
<CircleCloseFilled />
<CircleCloseFilled/>
</el-icon>
</div>
</div>
@ -115,7 +115,7 @@
@mouseleave.stop="mouseleave()"
>
<div class="flex align-center">
<img :src="getImgUrl(item && item?.name)" alt="" width="24" />
<img :src="getImgUrl(item && item?.name)" alt="" width="24"/>
<div class="ml-4 ellipsis-1" :title="item && item?.name">
{{ item && item?.name }}
</div>
@ -126,7 +126,7 @@
v-if="showDelete === item.url"
>
<el-icon style="font-size: 16px; top: 2px">
<CircleCloseFilled />
<CircleCloseFilled/>
</el-icon>
</div>
</div>
@ -146,7 +146,7 @@
v-if="showDelete === item.url"
>
<el-icon style="font-size: 16px; top: 2px">
<CircleCloseFilled />
<CircleCloseFilled/>
</el-icon>
</div>
<el-image
@ -173,7 +173,7 @@
v-if="showDelete === item.url"
>
<el-icon style="font-size: 16px; top: 2px">
<CircleCloseFilled />
<CircleCloseFilled/>
</el-icon>
</div>
<video
@ -213,7 +213,7 @@
<div class="operate flex-between">
<div>
<slot name="userInput" />
<slot name="userInput"/>
</div>
<div class="flex align-center">
<template v-if="props.applicationDetails.stt_model_enable">
@ -223,7 +223,7 @@
<AppIcon v-if="isMicrophone" iconName="app-keyboard"></AppIcon>
<el-icon v-else>
<!-- 录音 -->
<Microphone />
<Microphone/>
</el-icon>
</el-button>
</span>
@ -235,13 +235,13 @@
v-if="recorderStatus === 'STOP'"
>
<el-icon>
<Microphone />
<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
>00:{{ recorderTime < 10 ? `0${recorderTime}` : recorderTime }}</el-text
>
<el-button
text
@ -256,48 +256,60 @@
</template>
<template v-if="recorderStatus === 'STOP' || mode === 'mobile'">
<span v-if="props.applicationDetails.file_upload_enable" class="flex align-center ml-4">
<span
v-if="props.applicationDetails.file_upload_enable"
class="flex align-center ml-4">
<!-- 如果URL地址 -->
<!-- <el-button
text
:disabled="checkMaxFilesLimit() || loading"
class="mt-4"
@click="showURLSetting = true"
>
<el-icon><Paperclip /></el-icon>
</el-button> -->
<el-button
v-if="props.applicationDetails.file_upload_setting.url_upload"
text
:disabled="checkMaxFilesLimit() || loading"
class="mt-4"
@click="openUrlSetting"
>
<el-icon><Paperclip/></el-icon>
</el-button>
<!-- 没有URL地址 -->
<el-upload
action="#"
multiple
:auto-upload="false"
:show-file-list="false"
:accept="getAcceptList()"
:on-change="(file: any, fileList: any) => uploadFile(file, fileList)"
v-model:file-list="fileAllList"
ref="upload"
>
<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() || loading" class="mt-4">
<el-icon><Paperclip /></el-icon>
</el-button>
</el-tooltip>
</el-upload>
<el-upload
v-else
action="#"
multiple
:auto-upload="false"
:show-file-list="false"
:accept="getAcceptList()"
:on-change="(file: any, fileList: any) => uploadFile(file, fileList)"
v-model:file-list="fileAllList"
ref="upload"
>
<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() || loading"
class="mt-4">
<el-icon><Paperclip/></el-icon>
</el-button>
</el-tooltip>
</el-upload>
</span>
<el-divider
direction="vertical"
@ -317,7 +329,7 @@
src="@/assets/icon_send.svg"
alt=""
/>
<SendIcon v-show="!isDisabledChat && !loading && !uploadLoading" />
<SendIcon v-show="!isDisabledChat && !loading && !uploadLoading"/>
</el-button>
</template>
</div>
@ -334,26 +346,32 @@
<!-- 弹出URL设置框 -->
<div class="popperURLSetting" v-if="showURLSetting">
<el-card shadow="always" class="border-r-8" style="--el-card-padding: 16px">
<el-card shadow="always" class="border-r-8" style="--el-card-padding: 16px"
v-if="props.applicationDetails.file_upload_setting.url_upload">
<el-form label-position="top" ref="urlFormRef" :model="urlForm">
<el-form-item>
<template #label>
<div class="flex-between">
<span>URL 地址</span>
<span>{{ $t('chat.uploadFile.urlTitle') }}</span>
<el-select
:teleported="false"
v-model="urlForm.type"
size="small"
style="width: 85px"
>
<el-option :label="$t('common.fileUpload.image')" value="image" />
<el-option :label="$t('common.fileUpload.audio')" value="audio" />
<el-option
v-for="option in fileUploadOptions"
:key="option.value"
:label="option.label"
:value="option.value"
v-show="option.visible"
/>
</el-select>
</div>
</template>
<el-input
v-model="urlForm.source_url"
placeholder="请输入URL地址每行一个地址"
:placeholder="$t('chat.uploadFile.urlPlaceholder')"
:rows="5"
type="textarea"
/>
@ -361,12 +379,14 @@
</el-form>
<div class="text-right">
<el-button @click="showURLSetting = false">{{ $t('common.cancel') }}</el-button>
<el-button type="primary" @click="showURLSetting = false">{{
$t('common.confirm')
}}</el-button>
<el-button type="primary" @click="saveUrl">{{
$t('common.confirm')
}}
</el-button>
</div>
<el-divider style="margin: 16px 0" />
<el-divider style="margin: 16px 0"/>
<el-upload
v-if="props.applicationDetails.file_upload_setting.local_upload"
action="#"
multiple
:auto-upload="false"
@ -377,32 +397,35 @@
ref="upload"
class="import-button"
>
<el-button class="w-full url-upload-button">本地上传</el-button>
<el-button class="w-full url-upload-button">{{
$t('chat.uploadFile.localUpload')
}}
</el-button>
</el-upload>
</el-card>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, nextTick, reactive, type Ref } from 'vue'
import { t } from '@/locales'
import {computed, nextTick, onMounted, reactive, ref, type Ref} from 'vue'
import {t} from '@/locales'
import Recorder from 'recorder-core'
import TouchChat from './TouchChat.vue'
import applicationApi from '@/api/application/application'
import { MsgAlert } from '@/utils/message'
import { type chatType } from '@/api/type/application'
import { useRoute, useRouter } from 'vue-router'
import { getImgUrl } from '@/utils/common'
import {MsgAlert, MsgWarning} from '@/utils/message'
import {type chatType} from '@/api/type/application'
import {useRoute, useRouter} from 'vue-router'
import {getImgUrl} from '@/utils/common'
import bus from '@/bus'
import 'recorder-core/src/engine/mp3'
import 'recorder-core/src/engine/mp3-engine'
import { MsgWarning } from '@/utils/message'
import chatAPI from '@/api/chat/chat'
import imageApi from '@/api/image'
const router = useRouter()
const route = useRoute()
const {
query: { mode, question },
query: {mode, question},
} = route as any
const quickInputRef = ref()
const props = withDefaults(
@ -451,6 +474,7 @@ const urlForm = reactive({
type: '',
})
const uploadLoading = computed(() => {
return Object.values(filePromisionDict.value).length > 0
})
@ -467,12 +491,12 @@ const upload = ref()
const imageExtensions = ['JPG', 'JPEG', 'PNG', 'GIF', 'BMP']
const documentExtensions = ['PDF', 'DOCX', 'TXT', 'XLS', 'XLSX', 'MD', 'HTML', 'CSV']
const videoExtensions: any = ['MP4', 'AVI', 'MKV', 'MOV', 'FLV', 'WMV']
const videoExtensions = ['MP4', 'AVI', 'MKV', 'MOV', 'FLV', 'WMV']
const audioExtensions = ['MP3', 'WAV', 'OGG', 'AAC', 'M4A']
const otherExtensions = ref(['PPT', 'DOC'])
const getAcceptList = () => {
const { image, document, audio, video, other } = props.applicationDetails.file_upload_setting
const {image, document, audio, video, other} = props.applicationDetails.file_upload_setting
let accepts: any = []
if (image) {
accepts = [...imageExtensions]
@ -502,15 +526,15 @@ const checkMaxFilesLimit = () => {
return (
props.applicationDetails.file_upload_setting.maxFiles <=
uploadImageList.value.length +
uploadDocumentList.value.length +
uploadAudioList.value.length +
uploadVideoList.value.length +
uploadOtherList.value.length
uploadDocumentList.value.length +
uploadAudioList.value.length +
uploadVideoList.value.length +
uploadOtherList.value.length
)
}
const filePromisionDict: any = ref<any>({})
const uploadFile = async (file: any, fileList: any) => {
const { maxFiles, fileLimit } = props.applicationDetails.file_upload_setting
const {maxFiles, fileLimit} = props.applicationDetails.file_upload_setting
//
const file_limit_once =
uploadImageList.value.length +
@ -518,7 +542,6 @@ const uploadFile = async (file: any, fileList: any) => {
uploadAudioList.value.length +
uploadVideoList.value.length +
uploadOtherList.value.length
if (file_limit_once >= maxFiles) {
MsgWarning(t('chat.uploadFile.limitMessage1') + maxFiles + t('chat.uploadFile.limitMessage2'))
fileList.splice(0, fileList.length, ...fileList.slice(0, maxFiles))
@ -546,8 +569,7 @@ const uploadFile = async (file: any, fileList: any) => {
const inner = reactive(file)
fileAllList.value.push(inner)
if (!chatId_context.value) {
const res = await props.openChatId()
chatId_context.value = res
chatId_context.value = await props.openChatId()
}
const api =
props.type === 'debug-ai-chat'
@ -676,7 +698,8 @@ const TouchEnd = (bool?: boolean) => {
}
}
//
Recorder.CLog = function () {}
Recorder.CLog = function () {
}
class RecorderManage {
recorder?: any
@ -982,16 +1005,16 @@ onMounted(() => {
//
const route = router.currentRoute.value
// query
const query = { ...route.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('&')
'?' +
Object.entries(query)
.map(([key, value]) => `${key}=${value}`)
.join('&')
: route.path
history.pushState(null, '', '/chat' + newRoute)
@ -1003,6 +1026,300 @@ onMounted(() => {
})
}, 800)
})
const mime_types = {
"html": "text/html",
"htm": "text/html",
"shtml": "text/html",
"css": "text/css",
"xml": "text/xml",
"gif": "image/gif",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"js": "application/javascript",
"atom": "application/atom+xml",
"rss": "application/rss+xml",
"mml": "text/mathml",
"txt": "text/plain",
"jad": "text/vnd.sun.j2me.app-descriptor",
"wml": "text/vnd.wap.wml",
"htc": "text/x-component",
"avif": "image/avif",
"png": "image/png",
"svg": "image/svg+xml",
"svgz": "image/svg+xml",
"tif": "image/tiff",
"tiff": "image/tiff",
"wbmp": "image/vnd.wap.wbmp",
"webp": "image/webp",
"ico": "image/x-icon",
"jng": "image/x-jng",
"bmp": "image/x-ms-bmp",
"woff": "font/woff",
"woff2": "font/woff2",
"jar": "application/java-archive",
"war": "application/java-archive",
"ear": "application/java-archive",
"json": "application/json",
"hqx": "application/mac-binhex40",
"doc": "application/msword",
"pdf": "application/pdf",
"ps": "application/postscript",
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
"eps": "application/postscript",
"ai": "application/postscript",
"rtf": "application/rtf",
"m3u8": "application/vnd.apple.mpegurl",
"kml": "application/vnd.google-earth.kml+xml",
"kmz": "application/vnd.google-earth.kmz",
"xls": "application/vnd.ms-excel",
"eot": "application/vnd.ms-fontobject",
"ppt": "application/vnd.ms-powerpoint",
"odg": "application/vnd.oasis.opendocument.graphics",
"odp": "application/vnd.oasis.opendocument.presentation",
"ods": "application/vnd.oasis.opendocument.spreadsheet",
"odt": "application/vnd.oasis.opendocument.text",
"wmlc": "application/vnd.wap.wmlc",
"wasm": "application/wasm",
"7z": "application/x-7z-compressed",
"cco": "application/x-cocoa",
"jardiff": "application/x-java-archive-diff",
"jnlp": "application/x-java-jnlp-file",
"run": "application/x-makeself",
"pl": "application/x-perl",
"pm": "application/x-perl",
"prc": "application/x-pilot",
"pdb": "application/x-pilot",
"rar": "application/x-rar-compressed",
"rpm": "application/x-redhat-package-manager",
"sea": "application/x-sea",
"swf": "application/x-shockwave-flash",
"sit": "application/x-stuffit",
"tcl": "application/x-tcl",
"tk": "application/x-tcl",
"der": "application/x-x509-ca-cert",
"pem": "application/x-x509-ca-cert",
"crt": "application/x-x509-ca-cert",
"xpi": "application/x-xpinstall",
"xhtml": "application/xhtml+xml",
"xspf": "application/xspf+xml",
"zip": "application/zip",
"bin": "application/octet-stream",
"exe": "application/octet-stream",
"dll": "application/octet-stream",
"deb": "application/octet-stream",
"dmg": "application/octet-stream",
"iso": "application/octet-stream",
"img": "application/octet-stream",
"msi": "application/octet-stream",
"msp": "application/octet-stream",
"msm": "application/octet-stream",
"mid": "audio/midi",
"midi": "audio/midi",
"kar": "audio/midi",
"mp3": "audio/mp3",
"ogg": "audio/ogg",
"m4a": "audio/x-m4a",
"ra": "audio/x-realaudio",
"3gpp": "video/3gpp",
"3gp": "video/3gpp",
"ts": "video/mp2t",
"mp4": "video/mp4",
"mpeg": "video/mpeg",
"mpg": "video/mpeg",
"mov": "video/quicktime",
"webm": "video/webm",
"flv": "video/x-flv",
"m4v": "video/x-m4v",
"mng": "video/x-mng",
"asx": "video/x-ms-asf",
"asf": "video/x-ms-asf",
"wmv": "video/x-ms-wmv",
"avi": "video/x-msvideo",
"wav": "audio/wav",
"flac": "audio/flac",
"aac": "audio/aac",
"opus": "audio/opus",
"csv": "text/csv",
"tsv": "text/tab-separated-values",
"ics": "text/calendar",
}
function getExtensionsByMime(mime: string): string[] {
return Object.entries(mime_types)
.filter(([key, value]) => value === mime)
.map(([key]) => key);
}
const fileUploadOptions = computed(() => [
{
label: t('common.fileUpload.image'),
value: 'image',
visible: props.applicationDetails.file_upload_setting.image
},
{
label: t('common.fileUpload.document'),
value: 'document',
visible: props.applicationDetails.file_upload_setting.document
},
{
label: t('common.fileUpload.video'),
value: 'video',
visible: props.applicationDetails.file_upload_setting.video
},
{
label: t('common.fileUpload.audio'),
value: 'audio',
visible: props.applicationDetails.file_upload_setting.audio
},
{
label: t('common.fileUpload.other'),
value: 'other',
visible: props.applicationDetails.file_upload_setting.other
}
])
function openUrlSetting() {
showURLSetting.value = true
const visibleOptions = fileUploadOptions.value.filter(option => option.visible)
if (visibleOptions.length > 0) {
urlForm.type = visibleOptions[0].value
}
}
async function saveUrl() {
const urls = urlForm.source_url.split('\n')
if (urls.length === 0) {
MsgWarning(t('chat.uploadFile.invalidUrl'))
return
}
// MIME
const allowedTypes: Record<string, string[]> = {
image: imageExtensions
.map(ext => mime_types[ext.toLowerCase() as keyof typeof mime_types])
.filter(Boolean) as string[],
document: documentExtensions
.map(ext => mime_types[ext.toLowerCase() as keyof typeof mime_types])
.filter(Boolean) as string[],
audio: audioExtensions
.map(ext => mime_types[ext.toLowerCase() as keyof typeof mime_types])
.filter(Boolean) as string[],
video: videoExtensions
.map(ext => mime_types[ext.toLowerCase() as keyof typeof mime_types])
.filter(Boolean) as string[],
other: otherExtensions.value
.map(ext => mime_types[ext.toLowerCase() as keyof typeof mime_types])
.filter(Boolean) as string[]
};
// URL
const validUrls = urls.map(u => u.trim()).filter(u => {
try {
new URL(u);
return u !== '';
} catch {
return false;
}
});
if (validUrls.length === 0) {
MsgWarning(t('chat.uploadFile.invalidUrl'));
return;
}
const type = urlForm.type
const expectedTypes = allowedTypes[type] || [];
const validFiles: any[] = [];
// URL
async function processUrl(url: string) {
try {
const res = await imageApi.getFile({url});
if (!res.data) {
MsgWarning(url + ' ' + t('chat.uploadFile.invalidUrl'));
return;
}
const contentType = res.data['Content-Type'] || '';
const contentLength = res.data['Content-Length'];
const fileSize = contentLength ? parseInt(contentLength, 10) : 0;
//
if (expectedTypes.length > 0 && !expectedTypes.some(type => contentType.includes(type))) {
MsgWarning(url + ' ' + t('chat.uploadFile.urlErrorMessage'));
return;
}
//
const {fileLimit} = props.applicationDetails.file_upload_setting;
if (fileSize > fileLimit * 1024 * 1024) {
MsgWarning(url + ' ' + t('chat.uploadFile.sizeLimit') + fileLimit + 'MB')
return;
}
//
let fileName = url.substring(url.lastIndexOf('/') + 1);
if (!fileName) fileName = `file_${Date.now()}`;
if (!fileName.includes('.') && getExtensionsByMime(contentType)) {
fileName += '.' + getExtensionsByMime(contentType)[0];
}
const fileItem = {
uid: `${Date.now()}_${Math.random()}`,
name: fileName,
url: url,
type: contentType,
size: fileSize,
status: 'success'
};
// /
if (type === 'document' || type === 'audio' || type === 'other') {
const base64Data = res.data.content;
const byteString = atob(base64Data.split(',')[1] || base64Data);
const mimeString = base64Data.split(',')[0]?.split(':')[1]?.split(';')[0] || contentType;
const ab = new ArrayBuffer(byteString.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < byteString.length; i++) ia[i] = byteString.charCodeAt(i);
const fileBlob = new Blob([ab], {type: mimeString});
const fileObj = new File([fileBlob], fileName, {type: mimeString});
const uploadFileItem = {
uid: fileItem.uid,
name: fileName,
size: fileSize,
raw: fileObj,
status: 'ready',
percentage: 0
};
await uploadFile(uploadFileItem, [uploadFileItem]);
} else {
validFiles.push(reactive(fileItem));
}
} catch (e) {
console.error(e);
MsgWarning(`${url} 无法访问`);
}
}
// URL
await Promise.all(validUrls.map(url => processUrl(url)));
if (validFiles.length > 0) {
fileAllList.value.push(...validFiles);
}
showURLSetting.value = false;
urlForm.source_url = '';
urlForm.type = ''
}
</script>
<style lang="scss" scoped>
.ai-chat {
@ -1101,6 +1418,7 @@ onMounted(() => {
bottom: 65px;
width: calc(100% - 50px);
max-width: 320px;
.url-upload-button {
border-color: var(--el-color-primary);
color: var(--el-color-primary);

View File

@ -81,6 +81,11 @@ export default {
errorMessage: 'Upload Failed',
fileMessage: 'Please process the file content',
fileRepeat: 'File already exists',
invalidUrl: 'Invalid URL',
localUpload: 'Local Upload',
urlPlaceholder: 'Please enter URL addresses, one per line',
urlTitle: 'URL Address',
urlErrorMessage: 'File type does not meet requirements'
},
executionDetails: {
title: 'Execution Details',

View File

@ -61,6 +61,8 @@ export default {
other: 'Other',
addExtensions: 'Add Extensions',
existingExtensionsTip: 'The following extensions already exist',
localUpload: 'Local Files',
urlUpload: 'URL',
},
status: {
label: 'Status',

View File

@ -79,6 +79,11 @@ export default {
errorMessage: '上传失败',
fileMessage: '请解析文件内容',
fileRepeat: '文件已存在',
invalidUrl: '无效的URL',
localUpload: '本地上传',
urlPlaceholder: '请输入 URL 地址,每行一个地址',
urlTitle: 'URL 地址',
urlErrorMessage: '文件类型不符合要求'
},
executionDetails: {
title: '执行详情',

View File

@ -62,6 +62,8 @@ export default {
other: '其他文件',
addExtensions: '添加后缀名',
existingExtensionsTip: '文件后缀已存在',
localUpload: '本地文件',
urlUpload: 'URL 地址',
},
status: {
label: '状态',

View File

@ -79,6 +79,11 @@ export default {
fileMessage: '請解析文件內容',
errorMessage: '上傳失敗',
fileRepeat: '文件已存在',
invalidUrl: '无效的URL',
localUpload: '本地上傳',
urlPlaceholder: '請輸入 URL 地址,每行一個地址',
urlTitle: 'URL 地址',
urlErrorMessage: '文件类型不符合要求'
},
executionDetails: {
title: '執行詳細',

View File

@ -61,6 +61,8 @@ export default {
other: '其他文件',
addExtensions: '添加後綴名',
existingExtensionsTip: '文件後綴已存在',
localUpload: '本地文件',
urlUpload: 'URL 地址',
},
status: {
label: '狀態',

View File

@ -199,7 +199,7 @@ const statisticsType = computed(() => [
},
])
const topOptions = [{ label: 'TOP 10', value: 10 }]
const topOptions = [{ label: 'TOP 10', value: 10 }, { label: 'TOP 20', value: 20}, { label: 'TOP 50', value: 50}, { label: 'TOP 100', value: 100}]
const tokenUsageCount = ref(10)
const topQuestionsCount = ref(10)
const tokenUsageOption = computed(() => {

View File

@ -55,12 +55,12 @@
>
<div class="flex-between">
<div class="flex align-center">
<img class="mr-12" src="@/assets/workflow/icon_file-doc.svg" alt="" />
<img class="mr-12" src="@/assets/workflow/icon_file-doc.svg" alt=""/>
<div>
<p class="line-height-22 mt-4">
{{ $t('common.fileUpload.document') }}
<el-text class="color-secondary"
>{{
>{{
$t(
'views.applicationWorkflow.nodes.baseNode.FileUploadSetting.fileUploadType.documentText',
)
@ -85,12 +85,12 @@
>
<div class="flex-between">
<div class="flex align-center">
<img class="mr-12" src="@/assets/workflow/icon_file-image.svg" alt="" />
<img class="mr-12" src="@/assets/workflow/icon_file-image.svg" alt=""/>
<div>
<p class="line-height-22 mt-4">
{{ $t('common.fileUpload.image') }}
<el-text class="color-secondary"
>{{
>{{
$t(
'views.applicationWorkflow.nodes.baseNode.FileUploadSetting.fileUploadType.imageText',
)
@ -116,12 +116,12 @@
>
<div class="flex-between">
<div class="flex align-center">
<img class="mr-12" src="@/assets/workflow/icon_file-audio.svg" alt="" />
<img class="mr-12" src="@/assets/workflow/icon_file-audio.svg" alt=""/>
<div>
<p class="line-height-22 mt-4">
{{ $t('common.fileUpload.audio') }}
<el-text class="color-secondary"
>{{
>{{
$t(
'views.applicationWorkflow.nodes.baseNode.FileUploadSetting.fileUploadType.audioText',
)
@ -156,7 +156,7 @@
<p class="line-height-22 mt-4">
{{ $t('common.fileUpload.video') }}
<el-text class="color-secondary"
>{{
>{{
$t(
'views.applicationWorkflow.nodes.baseNode.FileUploadSetting.fileUploadType.videoText',
)
@ -181,12 +181,12 @@
>
<div class="flex-between">
<div class="flex align-center">
<img class="mr-12" :width="32" src="@/assets/fileType/unknown-icon.svg" alt="" />
<img class="mr-12" :width="32" src="@/assets/fileType/unknown-icon.svg" alt=""/>
<div>
<p class="line-height-22 mt-4">
{{ $t('common.fileUpload.other') }}
<el-text class="color-secondary"
>{{
>{{
$t(
'views.applicationWorkflow.nodes.baseNode.FileUploadSetting.fileUploadType.otherText',
)
@ -230,6 +230,21 @@
/>
</div>
</el-card>
<el-form-item>
<div class="flex align-center">
<el-checkbox
v-model="form_data.local_upload"
class="mr-16"
>
{{ $t('common.fileUpload.localUpload') }}
</el-checkbox>
<el-checkbox
v-model="form_data.url_upload"
>
{{ $t('common.fileUpload.urlUpload') }}
</el-checkbox>
</div>
</el-form-item>
</el-form-item>
</el-form>
</div>
@ -246,11 +261,11 @@
</template>
<script setup lang="ts">
import { nextTick, ref } from 'vue'
import type { InputInstance } from 'element-plus'
import { cloneDeep } from 'lodash'
import { MsgWarning } from '@/utils/message'
import { t } from '@/locales'
import {nextTick, ref} from 'vue'
import type {InputInstance} from 'element-plus'
import {cloneDeep} from 'lodash'
import {MsgWarning} from '@/utils/message'
import {t} from '@/locales'
const emit = defineEmits(['refresh'])
const props = defineProps<{ nodeModel: any }>()
@ -276,12 +291,14 @@ const form_data = ref({
video: false,
other: false,
otherExtensions: ['PPT', 'DOC'],
local_upload: true,
url_upload: false,
})
function open(data: any) {
dialogVisible.value = true
nextTick(() => {
form_data.value = { ...form_data.value, ...data }
form_data.value = {...form_data.value, ...data}
})
}

View File

@ -1,11 +1,11 @@
import { fileURLToPath, URL } from 'node:url'
import type { ProxyOptions } from 'vite'
import { defineConfig, loadEnv } from 'vite'
import {fileURLToPath, URL} from 'node:url'
import type {ProxyOptions} from 'vite'
import {defineConfig, loadEnv} from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import DefineOptions from 'unplugin-vue-define-options/vite'
import path from 'path'
import { createHtmlPlugin } from 'vite-plugin-html'
import {createHtmlPlugin} from 'vite-plugin-html'
import fs from 'fs'
// import vueDevTools from 'vite-plugin-vue-devtools'
const envDir = './env'
@ -69,6 +69,10 @@ export default defineConfig((conf: any) => {
target: `http://127.0.0.1:8080`,
changeOrigin: true,
}
proxyConf[`^${ENV.VITE_BASE_PATH}oss\/get_url\/.*$`] = {
target: `http://127.0.0.1:8080`,
changeOrigin: true,
}
// 前端静态资源转发到本身
proxyConf[ENV.VITE_BASE_PATH] = {
target: `http://127.0.0.1:${ENV.VITE_APP_PORT}`,
@ -85,7 +89,7 @@ export default defineConfig((conf: any) => {
vue(),
vueJsx(),
DefineOptions(),
createHtmlPlugin({ template: ENV.VITE_ENTRY }),
createHtmlPlugin({template: ENV.VITE_ENTRY}),
renameHtmlPlugin(`dist${ENV.VITE_BASE_PATH}`, ENV.VITE_ENTRY),
],
server: {