feat: add support for uploading other file types and extend file upload settings

--story=1018411 --user=刘瑞斌 【工作流应用】文件上传可以支持其它文件类型 https://www.tapd.cn/57709429/s/1688679
This commit is contained in:
CaptainB 2025-04-21 18:58:46 +08:00 committed by 刘瑞斌
parent 54c9d4e725
commit d32f7d36a6
11 changed files with 185 additions and 27 deletions

View File

@ -40,6 +40,7 @@ class BaseStartStepNode(IStarNode):
self.context['document'] = details.get('document_list')
self.context['image'] = details.get('image_list')
self.context['audio'] = details.get('audio_list')
self.context['other'] = details.get('other_list')
self.status = details.get('status')
self.err_message = details.get('err_message')
for key, value in workflow_variable.items():
@ -59,7 +60,8 @@ class BaseStartStepNode(IStarNode):
'question': question,
'image': self.workflow_manage.image_list,
'document': self.workflow_manage.document_list,
'audio': self.workflow_manage.audio_list
'audio': self.workflow_manage.audio_list,
'other': self.workflow_manage.other_list,
}
return NodeResult(node_variable, workflow_variable)
@ -83,5 +85,6 @@ class BaseStartStepNode(IStarNode):
'image_list': self.context.get('image'),
'document_list': self.context.get('document'),
'audio_list': self.context.get('audio'),
'other_list': self.context.get('other'),
'global_fields': global_fields
}

View File

@ -238,6 +238,7 @@ class WorkflowManage:
base_to_response: BaseToResponse = SystemToResponse(), form_data=None, image_list=None,
document_list=None,
audio_list=None,
other_list=None,
start_node_id=None,
start_node_data=None, chat_record=None, child_node=None):
if form_data is None:
@ -248,12 +249,15 @@ class WorkflowManage:
document_list = []
if audio_list is None:
audio_list = []
if other_list is None:
other_list = []
self.start_node_id = start_node_id
self.start_node = None
self.form_data = form_data
self.image_list = image_list
self.document_list = document_list
self.audio_list = audio_list
self.other_list = other_list
self.params = params
self.flow = flow
self.context = {}

View File

@ -245,6 +245,7 @@ class OpenAIChatSerializer(serializers.Serializer):
'image_list': instance.get('image_list', []),
'document_list': instance.get('document_list', []),
'audio_list': instance.get('audio_list', []),
'other_list': instance.get('other_list', []),
}
).chat(base_to_response=OpenaiToResponse())
@ -274,6 +275,7 @@ class ChatMessageSerializer(serializers.Serializer):
image_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("picture")))
document_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("document")))
audio_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("Audio")))
other_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("Other")))
child_node = serializers.DictField(required=False, allow_null=True,
error_messages=ErrMessage.dict(_("Child Nodes")))
@ -372,6 +374,7 @@ class ChatMessageSerializer(serializers.Serializer):
image_list = self.data.get('image_list')
document_list = self.data.get('document_list')
audio_list = self.data.get('audio_list')
other_list = self.data.get('other_list')
user_id = chat_info.application.user_id
chat_record_id = self.data.get('chat_record_id')
chat_record = None
@ -388,7 +391,7 @@ class ChatMessageSerializer(serializers.Serializer):
'client_id': client_id,
'client_type': client_type,
'user_id': user_id}, WorkFlowPostHandler(chat_info, client_id, client_type),
base_to_response, form_data, image_list, document_list, audio_list,
base_to_response, form_data, image_list, document_list, audio_list, other_list,
self.data.get('runtime_node_id'),
self.data.get('node_data'), chat_record, self.data.get('child_node'))
r = work_flow_manage.run()

View File

@ -144,6 +144,8 @@ class ChatView(APIView):
'document_list') if 'document_list' in request.data else [],
'audio_list': request.data.get(
'audio_list') if 'audio_list' in request.data else [],
'other_list': request.data.get(
'other_list') if 'other_list' in request.data else [],
'client_type': request.auth.client_type,
'node_id': request.data.get('node_id', None),
'runtime_node_id': request.data.get('runtime_node_id', None),

View File

@ -10,7 +10,8 @@
uploadDocumentList.length ||
uploadImageList.length ||
uploadAudioList.length ||
uploadVideoList.length
uploadVideoList.length ||
uploadOtherList.length
"
>
<el-row :gutter="10">
@ -50,6 +51,42 @@
</div>
</el-card>
</el-col>
<el-col
v-for="(item, index) in uploadOtherList"
:key="index"
:xs="24"
: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"
class="mb-8"
>
<el-card
shadow="never"
style="--el-card-padding: 8px; max-width: 100%"
class="file cursor"
>
<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"
>
<el-icon>
<CircleCloseFilled />
</el-icon>
</div>
<img :src="getImgUrl(item && item?.name)" alt="" width="24" />
<div class="ml-4 ellipsis-1" :title="item && item?.name">
{{ item && item?.name }}
</div>
</div>
</el-card>
</el-col>
<el-col
:xs="24"
@ -310,9 +347,10 @@ const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp']
const documentExtensions = ['pdf', 'docx', 'txt', 'xls', 'xlsx', 'md', 'html', 'csv']
const videoExtensions = ['mp4', 'avi', 'mov', 'mkv', 'flv']
const audioExtensions = ['mp3', 'wav', 'ogg', 'aac', 'm4a']
let otherExtensions = ['ppt', 'doc']
const getAcceptList = () => {
const { image, document, audio, video } = props.applicationDetails.file_upload_setting
const { image, document, audio, video, other } = props.applicationDetails.file_upload_setting
let accepts: any = []
if (image) {
accepts = [...imageExtensions]
@ -326,6 +364,11 @@ const getAcceptList = () => {
if (video) {
accepts = [...accepts, ...videoExtensions]
}
if (other) {
//
otherExtensions = props.applicationDetails.file_upload_setting.otherExtensions
accepts = [...accepts, ...otherExtensions]
}
if (accepts.length === 0) {
return `.${t('chat.uploadFile.tipMessage')}`
@ -339,7 +382,8 @@ const checkMaxFilesLimit = () => {
uploadImageList.value.length +
uploadDocumentList.value.length +
uploadAudioList.value.length +
uploadVideoList.value.length
uploadVideoList.value.length +
uploadOtherList.value.length
)
}
@ -350,7 +394,8 @@ const uploadFile = async (file: any, fileList: any) => {
uploadImageList.value.length +
uploadDocumentList.value.length +
uploadAudioList.value.length +
uploadVideoList.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)
@ -376,6 +421,8 @@ const uploadFile = async (file: any, fileList: any) => {
uploadVideoList.value.push(file)
} else if (audioExtensions.includes(extension)) {
uploadAudioList.value.push(file)
} else if (otherExtensions.includes(extension)) {
uploadOtherList.value.push(file)
}
if (!chatId_context.value) {
@ -434,6 +481,15 @@ const uploadFile = async (file: any, fileList: any) => {
file.file_id = f[0].file_id
}
})
uploadOtherList.value.forEach((file: any) => {
const f = response.data.filter(
(f: any) => f.name.replaceAll(' ', '') === file.name.replaceAll(' ', '')
)
if (f.length > 0) {
file.url = f[0].url
file.file_id = f[0].file_id
}
})
if (!inputValue.value && uploadImageList.value.length > 0) {
inputValue.value = t('chat.uploadFile.imageMessage')
}
@ -499,6 +555,7 @@ const uploadImageList = ref<Array<any>>([])
const uploadDocumentList = ref<Array<any>>([])
const uploadVideoList = ref<Array<any>>([])
const uploadAudioList = ref<Array<any>>([])
const uploadOtherList = ref<Array<any>>([])
const showDelete = ref('')
@ -709,13 +766,15 @@ function autoSendMessage() {
image_list: uploadImageList.value,
document_list: uploadDocumentList.value,
audio_list: uploadAudioList.value,
video_list: uploadVideoList.value
video_list: uploadVideoList.value,
other_list: uploadOtherList.value,
})
inputValue.value = ''
uploadImageList.value = []
uploadDocumentList.value = []
uploadAudioList.value = []
uploadVideoList.value = []
uploadOtherList.value = []
if (quickInputRef.value) {
quickInputRef.value.textareaStyle.height = '45px'
}
@ -771,6 +830,8 @@ function deleteFile(index: number, val: string) {
uploadVideoList.value.splice(index, 1)
} else if (val === 'audio') {
uploadAudioList.value.splice(index, 1)
} else if (val === 'other') {
uploadOtherList.value.splice(index, 1)
}
}

View File

@ -45,7 +45,9 @@ export default {
document: 'Documents',
image: 'Image',
audio: 'Audio',
video: 'Video'
video: 'Video',
other: 'Other file',
addExtensions: 'Add file extensions',
},
status: {
label: 'Status',
@ -55,7 +57,7 @@ export default {
param: {
outputParam: 'Output Parameters',
inputParam: 'Input Parameters',
initParam: 'Startup Parameters',
initParam: 'Startup Parameters'
},
inputPlaceholder: 'Please input',

View File

@ -45,7 +45,9 @@ export default {
document: '文档',
image: '图片',
audio: '音频',
video: '视频'
video: '视频',
other: '其他文件',
addExtensions: '添加文件扩展名',
},
status: {
label: '状态',

View File

@ -45,7 +45,9 @@ export default {
document: '文檔',
image: '圖片',
audio: '音頻',
video: '視頻'
video: '視頻',
other: '其他文件',
addExtensions: '添加文件擴展名'
},
status: {
label: '狀態',

View File

@ -57,10 +57,11 @@
{{ $t('common.fileUpload.document') }}TXTMDDOCXHTMLCSVXLSXXLSPDF
</p>
<el-text class="color-secondary">{{
$t(
'views.applicationWorkflow.nodes.baseNode.FileUploadSetting.fileUploadType.documentText'
)
}}</el-text>
$t(
'views.applicationWorkflow.nodes.baseNode.FileUploadSetting.fileUploadType.documentText'
)
}}
</el-text>
</div>
</div>
<el-checkbox
@ -84,10 +85,11 @@
{{ $t('common.fileUpload.image') }}JPGJPEGPNGGIF
</p>
<el-text class="color-secondary">{{
$t(
'views.applicationWorkflow.nodes.baseNode.FileUploadSetting.fileUploadType.imageText'
)
}}</el-text>
$t(
'views.applicationWorkflow.nodes.baseNode.FileUploadSetting.fileUploadType.imageText'
)
}}
</el-text>
</div>
</div>
<el-checkbox v-model="form_data.image" @change="form_data.image = !form_data.image" />
@ -109,15 +111,58 @@
{{ $t('common.fileUpload.audio') }}MP3WAVOGGACCM4A
</p>
<el-text class="color-secondary">{{
$t(
'views.applicationWorkflow.nodes.baseNode.FileUploadSetting.fileUploadType.audioText'
)
}}</el-text>
$t(
'views.applicationWorkflow.nodes.baseNode.FileUploadSetting.fileUploadType.audioText'
)
}}
</el-text>
</div>
</div>
<el-checkbox v-model="form_data.audio" @change="form_data.audio = !form_data.audio" />
</div>
</el-card>
<el-card
shadow="hover"
class="card-checkbox cursor w-full mb-8"
:class="form_data.other ? 'active' : ''"
style="--el-card-padding: 8px 16px"
@click.stop="form_data.other = !form_data.other"
>
<div class="flex-between">
<div class="flex align-center">
<img class="mr-12" src="@/assets/icon_file-doc.svg" alt="" />
<div>
<p class="line-height-22 mt-4">
{{ $t('common.fileUpload.other') }}
</p>
<div class="flex">
<el-tag
v-for="tag in form_data.otherExtensions"
:key="tag"
closable
:disable-transitions="false"
@close="handleClose(tag)"
>
{{ tag }}
</el-tag>
<el-input
v-if="inputVisible"
ref="InputRef"
v-model="inputValue"
class="w-20"
size="small"
@keyup.enter="handleInputConfirm"
@blur="handleInputConfirm"
/>
<el-button v-else class="button-new-tag" size="small" @click.stop="showInput">
+ {{ $t('common.fileUpload.addExtensions') }}
</el-button>
</div>
</div>
</div>
<el-checkbox v-model="form_data.other" @change="form_data.other = !form_data.other" />
</div>
</el-card>
</el-form-item>
</el-form>
<template #footer>
@ -133,20 +178,28 @@
<script setup lang="ts">
import { nextTick, ref } from 'vue'
import type { InputInstance } from 'element-plus'
import { cloneDeep } from 'lodash'
const emit = defineEmits(['refresh'])
const props = defineProps<{ nodeModel: any }>()
const dialogVisible = ref(false)
const inputVisible = ref(false)
const inputValue = ref('')
const loading = ref(false)
const fieldFormRef = ref()
const InputRef = ref<InputInstance>()
const form_data = ref({
maxFiles: 3,
fileLimit: 50,
document: true,
image: false,
audio: false,
video: false
video: false,
other: false,
otherExtensions: ['ppt', 'doc']
})
function open(data: any) {
@ -160,11 +213,31 @@ function close() {
dialogVisible.value = false
}
const handleClose = (tag: string) => {
form_data.value.otherExtensions = form_data.value.otherExtensions.filter(item => item !== tag)
}
const showInput = () => {
inputVisible.value = true
nextTick(() => {
InputRef.value!.input!.focus()
})
}
const handleInputConfirm = () => {
if (inputValue.value) {
form_data.value.otherExtensions.push(inputValue.value)
}
inputVisible.value = false
inputValue.value = ''
}
async function submit() {
const formEl = fieldFormRef.value
if (!formEl) return
await formEl.validate().then(() => {
emit('refresh', form_data.value)
const formattedData = cloneDeep(form_data.value)
emit('refresh', formattedData)
// emit('refresh', form_data.value)
props.nodeModel.graphModel.eventCenter.emit('refreshFileUploadConfig')
dialogVisible.value = false
})

View File

@ -314,7 +314,9 @@ const switchFileUpload = () => {
document: true,
image: false,
audio: false,
video: false
video: false,
other: false,
otherExtensions: ['ppt', 'doc']
}
if (form_data.value.file_upload_enable) {

View File

@ -78,7 +78,8 @@ const refreshFileUploadConfig = () => {
item.value !== 'image' &&
item.value !== 'document' &&
item.value !== 'audio' &&
item.value !== 'video'
item.value !== 'video' &&
item.value !== 'other'
)
if (form_data.length === 0) {
@ -98,6 +99,9 @@ const refreshFileUploadConfig = () => {
if (form_data[0].video) {
fileUploadFields.push({ label: t('common.fileUpload.video'), value: 'video' })
}
if (form_data[0].other) {
fileUploadFields.push({ label: t('common.fileUpload.other'), value: 'other' })
}
set(props.nodeModel.properties.config, 'fields', [...fields, ...fileUploadFields])
}