mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-28 14:52:58 +00:00
feat: knowledge workflow
This commit is contained in:
parent
58446c896e
commit
6b61dac7bf
|
|
@ -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]:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class IDataSourceWebNode(INode):
|
|||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def get_form_class():
|
||||
def get_form_list(node):
|
||||
pass
|
||||
|
||||
def _run(self):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 调用工具数据源的函数获取表单列表
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
//1、判断文件大小是否合法,文件限制不能大于100M
|
||||
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>
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
Loading…
Reference in New Issue