feat: knowledge workflow
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled

This commit is contained in:
shaohuzhang1 2025-11-12 17:00:39 +08:00
parent 58446c896e
commit 6b61dac7bf
13 changed files with 220 additions and 42 deletions

View File

@ -16,9 +16,9 @@ from application.flow.i_step_node import INode, NodeResult
class DataSourceLocalNodeParamsSerializer(serializers.Serializer):
file_format = serializers.ListField(child=serializers.CharField(label=('')), label='')
max_file_number = serializers.IntegerField(required=True, label=_("Number of uploaded files"))
file_max_size = serializers.IntegerField(required=True, label=_("Upload file size"))
file_type_list = serializers.ListField(child=serializers.CharField(label=('')), label='')
file_size_limit = serializers.IntegerField(required=True, label=_("Number of uploaded files"))
file_count_limit = serializers.IntegerField(required=True, label=_("Upload file size"))
class IDataSourceLocalNode(INode):
@ -26,7 +26,7 @@ class IDataSourceLocalNode(INode):
@staticmethod
@abstractmethod
def get_form_class():
def get_form_list(node):
pass
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:

View File

@ -21,8 +21,18 @@ class BaseDataSourceLocalNode(IDataSourceLocalNode):
pass
@staticmethod
def get_form_class():
return BaseDataSourceLocalNodeForm
def get_form_list(node):
node_data = node.get('properties').get('node_data')
return [{
'field': 'file_list',
'input_type': 'LocalFileUpload',
'attrs': {
'file_count_limit': node_data.get('file_count_limit') or 10,
'file_size_limit': node_data.get('file_size_limit') or 100,
'file_type_list': node_data.get('file_type_list'),
},
'label': '',
}]
def execute(self, file_format, max_file_number, file_max_size, **kwargs) -> NodeResult:
pass

View File

@ -18,7 +18,7 @@ class IDataSourceWebNode(INode):
@staticmethod
@abstractmethod
def get_form_class():
def get_form_list(node):
pass
def _run(self):

View File

@ -22,8 +22,8 @@ class BaseDataSourceWebNode(IDataSourceWebNode):
pass
@staticmethod
def get_form_class():
return BaseDataSourceWebNodeForm
def get_form_list(node):
return BaseDataSourceWebNodeForm().to_form_list()
def execute(self, **kwargs) -> NodeResult:
pass
pass

View File

@ -31,7 +31,7 @@ from common.utils.fork import Fork, ChildLink
from common.utils.logger import maxkb_logger
from common.utils.split_model import get_split_model
from knowledge.models import Knowledge, KnowledgeScope, KnowledgeType, Document, Paragraph, Problem, \
ProblemParagraphMapping, TaskType, State, SearchMode, KnowledgeFolder, File, Tag
ProblemParagraphMapping, TaskType, State, SearchMode, KnowledgeFolder, File, Tag, KnowledgeWorkflow
from knowledge.serializers.common import ProblemParagraphManage, drop_knowledge_index, \
get_embedding_model_id_by_knowledge_id, MetaSerializer, \
GenerateRelatedSerializer, get_embedding_model_by_knowledge_id, list_paragraph, write_image, zip_dir
@ -350,7 +350,7 @@ class KnowledgeSerializer(serializers.Serializer):
workflow = k.work_flow
return {
**knowledge_dict,
'workflow': workflow,
'work_flow': workflow,
'meta': json.loads(knowledge_dict.get('meta', '{}')),
'application_id_list': list(filter(
lambda application_id: all_application_list.__contains__(application_id),
@ -413,7 +413,15 @@ class KnowledgeSerializer(serializers.Serializer):
application_id=application_id, knowledge_id=self.data.get('knowledge_id')
) for application_id in application_id_list
]) if len(application_id_list) > 0 else None
if instance.get("work_flow"):
QuerySet(KnowledgeWorkflow).update_or_create(knowledge_id=self.data.get("knowledge_id"),
create_defaults={'id': uuid.uuid7(),
'knowledge_id': self.data.get("knowledge_id"),
"workspace_id": self.data.get('workspace_id'),
'work_flow': instance.get('work_flow', {}), },
defaults={
'work_flow': instance.get('work_flow')
})
knowledge.save()
if select_one:
return self.one()

View File

@ -27,12 +27,13 @@ class KnowledgeWorkflowSerializer(serializers.Serializer):
class Form(serializers.Serializer):
type = serializers.CharField(required=True, label=_('type'))
id = serializers.CharField(required=True, label=_('type'))
node = serializers.DictField(required=True, label="")
def get_form_list(self):
self.is_valid(raise_exception=True)
if self.data.get('type') == 'local':
node = get_node(self.data.get('id'))
return node.get_form_class()().to_form_list()
return node.get_form_list(self.data.get("node"))
elif self.data.get('type') == 'tool':
tool = QuerySet(Tool).filter(id=self.data.get("id")).first()
# todo 调用工具数据源的函数获取表单列表

View File

@ -18,8 +18,9 @@ from knowledge.serializers.knowledge_workflow import KnowledgeWorkflowSerializer
class KnowledgeWorkflowFormView(APIView):
authentication_classes = [TokenAuth]
def get(self, request: Request, workspace_id: str, knowledge_id: str, type: str, id: str):
return result.success(KnowledgeWorkflowSerializer.Form(data={'type': type, 'id': id}).get_form_list())
def post(self, request: Request, workspace_id: str, knowledge_id: str, type: str, id: str):
return result.success(KnowledgeWorkflowSerializer.Form(
data={'type': type, 'id': id, 'node': request.data.get('node')}).get_form_list())
class KnowledgeWorkflowView(APIView):

View File

@ -319,9 +319,16 @@ const getKnowledgeWorkflowFormList: (
knowledge_id: string,
type: 'loacl' | 'tool',
id: string,
node: any,
loading?: Ref<boolean>,
) => Promise<Result<any>> = (knowledge_id: string, type: 'loacl' | 'tool', id: string, loading) => {
return get(`${prefix.value}/${knowledge_id}/form_list/${type}/${id}`, null, loading)
) => Promise<Result<any>> = (
knowledge_id: string,
type: 'loacl' | 'tool',
id: string,
node,
loading,
) => {
return post(`${prefix.value}/${knowledge_id}/form_list/${type}/${id}`, { node }, {}, loading)
}
export default {

View File

@ -22,6 +22,16 @@ import { ref } from 'vue'
import type { Dict } from '@/api/type/common'
const damo_data: Array<FormField> = [
{
field: 'aa',
input_type: 'LocalFileUpload',
attrs: {
file_count_limit: 10,
file_size_limit: 10,
file_type_list: ['TXT'],
},
label: '',
},
{
field: 'name',
input_type: 'PasswordInput',

View File

@ -0,0 +1,149 @@
<template>
<div>
<div class="update-info flex p-8-12 border-r-6 mb-16 w-full">
<div class="mt-4">
<AppIcon iconName="app-warning-colorful" style="font-size: 16px"></AppIcon>
</div>
<div class="ml-16 lighter">
<p>{{ $t('views.document.fileType.txt.tip1') }}</p>
<p>
2. {{ $t('views.document.tip.fileLimitCountTip1') }} {{ file_count_limit }}
{{ $t('views.document.tip.fileLimitCountTip2') }},
{{ $t('views.document.tip.fileLimitSizeTip1') }} {{ file_size_limit }} MB
</p>
</div>
</div>
<el-upload
:webkitdirectory="false"
class="w-full"
drag
multiple
v-bind:file-list="modelValue"
action="#"
:auto-upload="false"
:show-file-list="false"
:accept="accept"
:limit="attrs.limit"
:on-exceed="onExceed"
:on-change="fileHandleChange"
@click.prevent="handlePreview(false)"
>
<img src="@/assets/upload-icon.svg" alt="" />
<div class="el-upload__text">
<p>
{{ $t('views.document.upload.uploadMessage') }}
<em class="hover" @click.prevent="handlePreview(false)">
{{ $t('views.document.upload.selectFile') }}
</em>
<em class="hover ml-4" @click.prevent="handlePreview(true)">
{{ $t('views.document.upload.selectFiles') }}
</em>
</p>
<div class="upload__decoration">
<p>{{ $t('views.document.upload.formats') }}{{ formats }}</p>
</div>
</div>
</el-upload>
<el-row :gutter="8" v-if="modelValue?.length">
<template v-for="(item, index) in modelValue" :key="index">
<el-col :span="12" class="mb-8">
<el-card shadow="never" class="file-List-card">
<div class="flex-between">
<div class="flex">
<img :src="getImgUrl(item && item?.name)" alt="" width="40" />
<div class="ml-8">
<p>{{ item && item?.name }}</p>
<el-text type="info" size="small">{{
filesize(item && item?.size) || '0K'
}}</el-text>
</div>
</div>
<el-button text @click="deleteFile(index)">
<AppIcon iconName="app-delete"></AppIcon>
</el-button>
</div>
</el-card>
</el-col>
</template>
</el-row>
</div>
</template>
<script setup lang="ts">
import { computed, useAttrs, nextTick } from 'vue'
import type { FormField } from '@/components/dynamics-form/type'
import { MsgError } from '@/utils/message'
import type { UploadFiles } from 'element-plus'
import { filesize, getImgUrl, isRightType } from '@/utils/common'
import { t } from '@/locales'
const attrs = useAttrs() as any
const props = withDefaults(defineProps<{ modelValue?: any; formField: FormField }>(), {
modelValue: () => [],
})
const onExceed = () => {
MsgError(
t('views.document.tip.fileLimitCountTip1') +
attrs.limit +
t('views.document.tip.fileLimitCountTip2'),
)
}
const emit = defineEmits(['update:modelValue'])
// on-change
const fileHandleChange = (file: any, fileList: UploadFiles) => {
//1100M
const isLimit = file?.size / 1024 / 1024 < file_size_limit.value
if (!isLimit) {
MsgError(t('views.document.tip.fileLimitSizeTip1') + file_size_limit.value + 'MB')
fileList.splice(-1, 1) //
return false
}
if (file?.size === 0) {
MsgError(t('views.document.upload.errorMessage3'))
fileList.splice(-1, 1)
return false
}
emit('update:modelValue', fileList)
}
function deleteFile(index: number) {
emit(
'update:modelValue',
props.modelValue.filter((item: any, i: number) => index != i),
)
}
const handlePreview = (bool: boolean) => {
let inputDom: any = null
nextTick(() => {
if (document.querySelector('.el-upload__input') != null) {
inputDom = document.querySelector('.el-upload__input')
inputDom.webkitdirectory = bool
}
})
}
const accept = computed(() => {
return (attrs.file_type_list || []).map((item: any) => '.' + item.toLowerCase()).join(',')
})
const formats = computed(() => {
return (attrs.file_type_list || []).map((item: any) => item.toUpperCase()).join('、')
})
const file_size_limit = computed(() => {
return attrs.file_size_limit || 50
})
const file_count_limit = computed(() => {
return attrs.file_count_limit || 100
})
</script>
<style lang="scss" scoped>
.upload__decoration {
font-size: 12px;
line-height: 20px;
color: var(--el-text-color-secondary);
}
.el-upload__text {
.hover:hover {
color: var(--el-color-primary-light-5);
}
}
</style>

View File

@ -1,7 +1,8 @@
<template>
<el-drawer
v-model="drawer"
title="I am the title"
title="调试"
size="500"
direction="rtl"
destroy-on-close
:before-close="close"
@ -20,7 +21,6 @@ const close = () => {
drawer.value = false
}
const open = (workflow: any) => {
console.log('ok')
drawer.value = true
_workflow.value = workflow
}

View File

@ -71,8 +71,8 @@ const sourceChange = (node_id: string) => {
const n = source_node_list.value.find((n: any) => n.id == node_id)
node_id = n ? ([WorkflowType.DataSourceLocalNode,WorkflowType.DataSourceWebNode].includes(n.type) ? n.type : node_id) : node_id
loadSharedApi({ type: 'knowledge', systemType: apiType.value })
.getKnowledgeWorkflowFormList(id, 'local', node_id)
.then((ok) => {
.getKnowledgeWorkflowFormList(id, 'local', node_id, n)
.then((ok: any) => {
dynamicsFormRef.value?.render(ok.data)
})
}

View File

@ -27,7 +27,7 @@
}"
>
<el-select
v-model="form_data.file_format"
v-model="form_data.file_type_list"
:placeholder="
$t(
'views.applicationWorkflow.nodes.dataSourceLocalNode.fileFormat.placeholder',
@ -42,10 +42,10 @@
<span>{{ label }} </span>
</template>
<el-option
v-for="item in file_format_list"
:key="item.value"
:label="item.label"
:value="item.value"
v-for="item in file_type_list_options"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
@ -66,7 +66,7 @@
trigger: 'change',
}"
>
<el-slider v-model="form_data.max_file_number" show-input />
<el-slider v-model="form_data.file_size_limit" show-input />
</el-form-item>
<el-form-item
:label="
@ -85,7 +85,7 @@
trigger: 'change',
}"
>
<el-slider v-model="form_data.file_max_size" show-input />
<el-slider v-model="form_data.file_count_limit" show-input />
</el-form-item>
</el-form>
</el-card>
@ -98,20 +98,12 @@ import { computed } from 'vue'
import { set } from 'lodash'
const props = defineProps<{ nodeModel: any }>()
const file_format_list = [
{ label: 'TXT', value: '.txt' },
{ label: 'DOCX', value: '.docx' },
{ label: 'PDF', value: '.pdf' },
{ label: 'HTML', value: '.html' },
{ label: 'XLS', value: '.xls' },
{ label: 'XLSX', value: '.xlsx' },
{ label: 'ZIP', value: '.zip' },
{ label: 'CSV', value: '.csv' },
]
const file_type_list_options = ['TXT', 'DOCX', 'PDF', 'HTML', 'XLS', 'XLSX', 'ZIP', 'CSV']
const form = {
file_format: [],
max_file_number: 50,
file_max_size: 100,
file_type_list: [],
file_size_limit: 50,
file_count_limit: 100,
}
const form_data = computed({