diff --git a/apps/application/flow/step_node/application_node/i_application_node.py b/apps/application/flow/step_node/application_node/i_application_node.py index b11fa0023..d0184d4a3 100644 --- a/apps/application/flow/step_node/application_node/i_application_node.py +++ b/apps/application/flow/step_node/application_node/i_application_node.py @@ -12,6 +12,8 @@ class ApplicationNodeSerializer(serializers.Serializer): question_reference_address = serializers.ListField(required=True, error_messages=ErrMessage.list("用户问题")) api_input_field_list = serializers.ListField(required=False, error_messages=ErrMessage.list("api输入字段")) user_input_field_list = serializers.ListField(required=False, error_messages=ErrMessage.uuid("用户输入字段")) + image_list = serializers.ListField(required=False, error_messages=ErrMessage.list("图片")) + document_list = serializers.ListField(required=False, error_messages=ErrMessage.list("文档")) class IApplicationNode(INode): @@ -31,10 +33,27 @@ class IApplicationNode(INode): for user_input_field in self.node_params_serializer.data.get('user_input_field_list', []): kwargs[user_input_field['field']] = self.workflow_manage.get_reference_field(user_input_field['value'][0], user_input_field['value'][1:]) - + # 判断是否包含这个属性 + app_document_list = self.node_params_serializer.data.get('document_list', []) + if app_document_list and len(app_document_list) > 0: + app_document_list = self.workflow_manage.get_reference_field( + app_document_list[0], + app_document_list[1:]) + for document in app_document_list: + if 'file_id' not in document: + raise ValueError("参数值错误: 上传的文档中缺少file_id") + app_image_list = self.node_params_serializer.data.get('image_list', []) + if app_image_list and len(app_image_list) > 0: + app_image_list = self.workflow_manage.get_reference_field( + app_image_list[0], + app_image_list[1:]) + for image in app_image_list: + if 'file_id' not in image: + raise ValueError("参数值错误: 上传的图片中缺少file_id") return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data, + app_document_list=app_document_list, app_image_list=app_image_list, message=str(question), **kwargs) def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat, client_id, client_type, - **kwargs) -> NodeResult: + app_document_list=None, app_image_list=None, **kwargs) -> NodeResult: pass diff --git a/apps/application/flow/step_node/application_node/impl/base_application_node.py b/apps/application/flow/step_node/application_node/impl/base_application_node.py index f1abca40d..999e700d4 100644 --- a/apps/application/flow/step_node/application_node/impl/base_application_node.py +++ b/apps/application/flow/step_node/application_node/impl/base_application_node.py @@ -71,6 +71,7 @@ class BaseApplicationNode(IApplicationNode): self.answer_text = details.get('answer') def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat, client_id, client_type, + app_document_list=None, app_image_list=None, **kwargs) -> NodeResult: from application.serializers.chat_message_serializers import ChatMessageSerializer # 生成嵌入应用的chat_id @@ -79,13 +80,20 @@ class BaseApplicationNode(IApplicationNode): 'application_id': application_id, 'abstract': message }) + if app_document_list is None: + app_document_list = [] + if app_image_list is None: + app_image_list = [] response = ChatMessageSerializer( data={'chat_id': current_chat_id, 'message': message, 're_chat': re_chat, 'stream': stream, 'application_id': application_id, 'client_id': client_id, - 'client_type': client_type, 'form_data': kwargs}).chat(base_to_response=OpenaiToResponse()) + 'client_type': client_type, + 'document_list': app_document_list, + 'image_list': app_image_list, + 'form_data': kwargs}).chat(base_to_response=OpenaiToResponse()) if response.status_code == 200: if stream: content_generator = response.streaming_content diff --git a/apps/application/flow/step_node/image_understand_step_node/impl/base_image_understand_node.py b/apps/application/flow/step_node/image_understand_step_node/impl/base_image_understand_node.py index 7f0394fe5..16aafb628 100644 --- a/apps/application/flow/step_node/image_understand_step_node/impl/base_image_understand_node.py +++ b/apps/application/flow/step_node/image_understand_step_node/impl/base_image_understand_node.py @@ -116,13 +116,17 @@ class BaseImageUnderstandNode(IImageUnderstandNode): def generate_message_list(self, image_model, system: str, prompt: str, history_message, image): if image is not None and len(image) > 0: - file_id = image[0]['file_id'] - file = QuerySet(File).filter(id=file_id).first() - base64_image = base64.b64encode(file.get_byte()).decode("utf-8") + # 处理多张图片 + images = [] + for img in image: + file_id = img['file_id'] + file = QuerySet(File).filter(id=file_id).first() + base64_image = base64.b64encode(file.get_byte()).decode("utf-8") + images.append({'type': 'image_url', 'image_url': {'url': f'data:image/jpeg;base64,{base64_image}'}}) messages = [HumanMessage( content=[ {'type': 'text', 'text': self.workflow_manage.generate_prompt(prompt)}, - {'type': 'image_url', 'image_url': {'url': f'data:image/jpeg;base64,{base64_image}'}}, + *images ])] else: messages = [HumanMessage(self.workflow_manage.generate_prompt(prompt))] diff --git a/apps/application/flow/workflow_manage.py b/apps/application/flow/workflow_manage.py index d36a58b1f..b05bdea83 100644 --- a/apps/application/flow/workflow_manage.py +++ b/apps/application/flow/workflow_manage.py @@ -52,7 +52,7 @@ class Node: self.__setattr__(keyword, kwargs.get(keyword)) -end_nodes = ['ai-chat-node', 'reply-node', 'function-node', 'function-lib-node', 'application-node'] +end_nodes = ['ai-chat-node', 'reply-node', 'function-node', 'function-lib-node', 'application-node', 'image-understand-node'] class Flow: diff --git a/apps/setting/models_provider/impl/openai_model_provider/credential/image.py b/apps/setting/models_provider/impl/openai_model_provider/credential/image.py index c400fcb65..e6063695a 100644 --- a/apps/setting/models_provider/impl/openai_model_provider/credential/image.py +++ b/apps/setting/models_provider/impl/openai_model_provider/credential/image.py @@ -29,7 +29,9 @@ class OpenAIImageModelCredential(BaseForm, BaseModelCredential): return False try: model = provider.get_model(model_type, model_name, model_credential) - model.stream([HumanMessage(content=[{"type": "text", "text": "你好"}])]) + res = model.stream([HumanMessage(content=[{"type": "text", "text": "你好"}])]) + for chunk in res: + print(chunk) except Exception as e: if isinstance(e, AppApiException): raise e diff --git a/apps/setting/models_provider/impl/qwen_model_provider/credential/image.py b/apps/setting/models_provider/impl/qwen_model_provider/credential/image.py index 6f8b2f702..e77d4dfdf 100644 --- a/apps/setting/models_provider/impl/qwen_model_provider/credential/image.py +++ b/apps/setting/models_provider/impl/qwen_model_provider/credential/image.py @@ -50,7 +50,9 @@ class QwenVLModelCredential(BaseForm, BaseModelCredential): return False try: model = provider.get_model(model_type, model_name, model_credential) - model.stream([HumanMessage(content=[{"type": "text", "text": "你好"}])]) + res = model.stream([HumanMessage(content=[{"type": "text", "text": "你好"}])]) + for chunk in res: + print(chunk) except Exception as e: if isinstance(e, AppApiException): raise e diff --git a/apps/setting/models_provider/impl/tencent_model_provider/credential/image.py b/apps/setting/models_provider/impl/tencent_model_provider/credential/image.py index f2224546d..ae534d41b 100644 --- a/apps/setting/models_provider/impl/tencent_model_provider/credential/image.py +++ b/apps/setting/models_provider/impl/tencent_model_provider/credential/image.py @@ -50,7 +50,9 @@ class TencentVisionModelCredential(BaseForm, BaseModelCredential): return False try: model = provider.get_model(model_type, model_name, model_credential) - model.stream([HumanMessage(content=[{"type": "text", "text": "你好"}])]) + res = model.stream([HumanMessage(content=[{"type": "text", "text": "你好"}])]) + for chunk in res: + print(chunk) except Exception as e: if isinstance(e, AppApiException): raise e diff --git a/ui/src/api/type/application.ts b/ui/src/api/type/application.ts index 3762256e0..be8eeae90 100644 --- a/ui/src/api/type/application.ts +++ b/ui/src/api/type/application.ts @@ -39,9 +39,12 @@ interface chatType { record_id: string chat_id: string vote_status: string - status?: number, + status?: number execution_details: any[] - upload_meta?: any[] + upload_meta?: { + document_list: Array + image_list: Array + } } export class ChatRecordManage { diff --git a/ui/src/components/ai-chat/component/chat-input-operate/index.vue b/ui/src/components/ai-chat/component/chat-input-operate/index.vue index 0174c1612..d4524fde2 100644 --- a/ui/src/components/ai-chat/component/chat-input-operate/index.vue +++ b/ui/src/components/ai-chat/component/chat-input-operate/index.vue @@ -80,9 +80,7 @@ action="#" :auto-upload="false" :show-file-list="false" - :accept=" - [...imageExtensions, ...documentExtensions].map((ext) => '.' + ext).join(',') - " + :accept="getAcceptList()" :on-change="(file: any, fileList: any) => uploadFile(file, fileList)" > @@ -191,6 +189,24 @@ const documentExtensions = ['pdf', 'docx', 'txt', 'xls', 'xlsx', 'md', 'html', ' const videoExtensions = ['mp4', 'avi', 'mov', 'mkv', 'flv'] const audioExtensions = ['mp3', 'wav', 'aac', 'flac'] +const getAcceptList = () => { + const { image, document, audio, video } = props.applicationDetails.file_upload_setting + let accepts = '' + if (image) { + accepts += imageExtensions.map((ext) => '.' + ext).join(',') + } + if (document) { + accepts += documentExtensions.map((ext) => '.' + ext).join(',') + } + if (audio) { + accepts += audioExtensions.map((ext) => '.' + ext).join(',') + } + if (video) { + accepts += videoExtensions.map((ext) => '.' + ext).join(',') + } + return accepts +} + const uploadFile = async (file: any, fileList: any) => { const { maxFiles, fileLimit } = props.applicationDetails.file_upload_setting if (fileList.length > maxFiles) { diff --git a/ui/src/components/dynamics-form/constructor/data.ts b/ui/src/components/dynamics-form/constructor/data.ts index ff100fa06..4478558b1 100644 --- a/ui/src/components/dynamics-form/constructor/data.ts +++ b/ui/src/components/dynamics-form/constructor/data.ts @@ -15,6 +15,10 @@ const input_type_list = [ label: '单选框', value: 'SingleSelect' }, + { + label: '多选框', + value: 'MultiSelect' + }, { label: '日期', value: 'DatePicker' @@ -22,6 +26,14 @@ const input_type_list = [ { label: 'JSON文本框', value: 'JsonInput' + }, + { + label: '选项卡', + value: 'RadioCard' + }, + { + label: '单行选项卡', + value: 'RadioRow' } ] export { input_type_list } diff --git a/ui/src/components/dynamics-form/constructor/items/MultiSelectConstructor.vue b/ui/src/components/dynamics-form/constructor/items/MultiSelectConstructor.vue new file mode 100644 index 000000000..253ab160a --- /dev/null +++ b/ui/src/components/dynamics-form/constructor/items/MultiSelectConstructor.vue @@ -0,0 +1,129 @@ + + + diff --git a/ui/src/components/dynamics-form/constructor/items/RadioCardConstructor.vue b/ui/src/components/dynamics-form/constructor/items/RadioCardConstructor.vue new file mode 100644 index 000000000..6f6699d81 --- /dev/null +++ b/ui/src/components/dynamics-form/constructor/items/RadioCardConstructor.vue @@ -0,0 +1,122 @@ + + + diff --git a/ui/src/components/dynamics-form/constructor/items/RadioRowConstructor.vue b/ui/src/components/dynamics-form/constructor/items/RadioRowConstructor.vue new file mode 100644 index 000000000..00009918b --- /dev/null +++ b/ui/src/components/dynamics-form/constructor/items/RadioRowConstructor.vue @@ -0,0 +1,123 @@ + + + diff --git a/ui/src/components/dynamics-form/constructor/items/SingleSelectConstructor.vue b/ui/src/components/dynamics-form/constructor/items/SingleSelectConstructor.vue index 4e4f2a065..077401268 100644 --- a/ui/src/components/dynamics-form/constructor/items/SingleSelectConstructor.vue +++ b/ui/src/components/dynamics-form/constructor/items/SingleSelectConstructor.vue @@ -12,18 +12,38 @@ -
+
+ 标签 +
+ 选项值 + + - - - - - - -
+
+ +
+ +
+ + + + + { - formValue.value.option_list.push({ value: '' }) + formValue.value.option_list.push({ value: '', label: '' }) } const delOption = (index: number) => { @@ -74,8 +94,8 @@ const getData = () => { input_type: 'SingleSelect', attrs: {}, default_value: formValue.value.default_value, - text_field: 'value', - value_field: 'value', + textField: 'label', + valueField: 'value', option_list: formValue.value.option_list } } diff --git a/ui/src/components/dynamics-form/items/radio/RadioCard.vue b/ui/src/components/dynamics-form/items/radio/RadioCard.vue index 492cff408..68a43d522 100644 --- a/ui/src/components/dynamics-form/items/radio/RadioCard.vue +++ b/ui/src/components/dynamics-form/items/radio/RadioCard.vue @@ -1,18 +1,19 @@ diff --git a/ui/src/components/dynamics-form/items/radio/RadioRow.vue b/ui/src/components/dynamics-form/items/radio/RadioRow.vue new file mode 100644 index 000000000..f0c00bf6f --- /dev/null +++ b/ui/src/components/dynamics-form/items/radio/RadioRow.vue @@ -0,0 +1,79 @@ + + + diff --git a/ui/src/views/application-workflow/component/DropdownMenu.vue b/ui/src/views/application-workflow/component/DropdownMenu.vue index 333815064..5f91072ff 100644 --- a/ui/src/views/application-workflow/component/DropdownMenu.vue +++ b/ui/src/views/application-workflow/component/DropdownMenu.vue @@ -149,13 +149,20 @@ function clickNodes(item: any, data?: any, type?: string) { } if (type == 'application') { if (isWorkFlow(data.type)) { - console.log(data.work_flow.nodes[0].properties.api_input_field_list) + const nodeData = data.work_flow.nodes[0].properties.node_data + const fileUploadSetting = nodeData.file_upload_setting item['properties']['node_data'] = { name: data.name, icon: data.icon, application_id: data.id, api_input_field_list: data.work_flow.nodes[0].properties.api_input_field_list, - user_input_field_list: data.work_flow.nodes[0].properties.user_input_field_list + user_input_field_list: data.work_flow.nodes[0].properties.user_input_field_list, + ...(!fileUploadSetting + ? {} + : { + ...(fileUploadSetting.document ? { document_list: [] } : {}), + ...(fileUploadSetting.image ? { image_list: [] } : {}) + }) } } else { item['properties']['node_data'] = { @@ -186,12 +193,20 @@ function onmousedown(item: any, data?: any, type?: string) { } if (type == 'application') { if (isWorkFlow(data.type)) { + const nodeData = data.work_flow.nodes[0].properties.node_data + const fileUploadSetting = nodeData.file_upload_setting item['properties']['node_data'] = { name: data.name, icon: data.icon, application_id: data.id, api_input_field_list: data.work_flow.nodes[0].properties.api_input_field_list, - user_input_field_list: data.work_flow.nodes[0].properties.user_input_field_list + user_input_field_list: data.work_flow.nodes[0].properties.user_input_field_list, + ...(!fileUploadSetting + ? {} + : { + ...(fileUploadSetting.document ? { document_list: [] } : {}), + ...(fileUploadSetting.image ? { image_list: [] } : {}) + }) } } else { item['properties']['node_data'] = { diff --git a/ui/src/views/application/index.vue b/ui/src/views/application/index.vue index 849fbf8e6..643ae8a8b 100644 --- a/ui/src/views/application/index.vue +++ b/ui/src/views/application/index.vue @@ -3,7 +3,12 @@

{{ $t('views.application.applicationList.title') }}

- +
diff --git a/ui/src/views/dataset/index.vue b/ui/src/views/dataset/index.vue index 03de2ea81..9075316b2 100644 --- a/ui/src/views/dataset/index.vue +++ b/ui/src/views/dataset/index.vue @@ -3,7 +3,12 @@

知识库

- +
diff --git a/ui/src/views/function-lib/index.vue b/ui/src/views/function-lib/index.vue index e5d7f8cfc..6ead54f04 100644 --- a/ui/src/views/function-lib/index.vue +++ b/ui/src/views/function-lib/index.vue @@ -3,7 +3,12 @@

函数库

- +
diff --git a/ui/src/views/log/component/EditContentDialog.vue b/ui/src/views/log/component/EditContentDialog.vue index d97316cd9..c92905441 100644 --- a/ui/src/views/log/component/EditContentDialog.vue +++ b/ui/src/views/log/component/EditContentDialog.vue @@ -216,33 +216,36 @@ const onUploadImg = async (files: any, callback: any) => { callback(res.map((item) => item.data)) } -function changeDataset(id: string) { - if (user.userInfo) { - localStorage.setItem(user.userInfo.id + 'chat_dataset_id', id) - } +function changeDataset(dataset_id: string) { + localStorage.setItem(id + 'chat_dataset_id', dataset_id) form.value.document_id = '' - getDocument(id) + getDocument(dataset_id) } -function changeDocument(id: string) { - if (user.userInfo) { - localStorage.setItem(user.userInfo.id + 'chat_document_id', id) - } +function changeDocument(document_id: string) { + localStorage.setItem(id + 'chat_document_id', document_id) } -function getDocument(id: string) { - document.asyncGetAllDocument(id, loading).then((res: any) => { +function getDocument(dataset_id: string) { + document.asyncGetAllDocument(dataset_id, loading).then((res: any) => { documentList.value = res.data + if (localStorage.getItem(id + 'chat_document_id')) { + form.value.document_id = localStorage.getItem(id + 'chat_document_id') as string + } + if (!documentList.value.find((v) => v.id === form.value.document_id)) { + form.value.document_id = '' + } }) } function getDataset() { application.asyncGetApplicationDataset(id, loading).then((res: any) => { datasetList.value = res.data - if (localStorage.getItem(user.userInfo?.id + 'chat_dataset_id')) { - form.value.dataset_id = localStorage.getItem(user.userInfo?.id + 'chat_dataset_id') as string + if (localStorage.getItem(id + 'chat_dataset_id')) { + form.value.dataset_id = localStorage.getItem(id + 'chat_dataset_id') as string if (!datasetList.value.find((v) => v.id === form.value.dataset_id)) { form.value.dataset_id = '' + form.value.document_id = '' } else { getDocument(form.value.dataset_id) } @@ -252,9 +255,6 @@ function getDataset() { const open = (data: any) => { getDataset() - if (localStorage.getItem(user.userInfo?.id + 'chat_document_id')) { - form.value.document_id = localStorage.getItem(user.userInfo?.id + 'chat_document_id') as string - } form.value.chat_id = data.chat_id form.value.record_id = data.id form.value.problem_text = data.problem_text ? data.problem_text.substring(0, 256) : '' diff --git a/ui/src/views/log/index.vue b/ui/src/views/log/index.vue index eb1b0c3aa..bdf5af51e 100644 --- a/ui/src/views/log/index.vue +++ b/ui/src/views/log/index.vue @@ -33,8 +33,8 @@ 清除策略 导出 添加至知识库 + >添加至知识库 +
@@ -171,9 +171,9 @@ 天之前的对话记录