feat: 工作流增加嵌入应用
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run

--story=1016666 --user=王孝刚 【北区-售前】工作流支持嵌入应用 https://www.tapd.cn/57709429/s/1608362
This commit is contained in:
wxg0103 2024-11-11 18:34:54 +08:00 committed by wxg0103
parent 6e6f564713
commit 72b91bee9a
22 changed files with 549 additions and 38 deletions

View File

@ -7,6 +7,7 @@
@desc:
"""
from .ai_chat_step_node import *
from .application_node import BaseApplicationNode
from .condition_node import *
from .question_node import *
from .search_dataset_node import *
@ -17,7 +18,7 @@ from .function_node import *
from .reranker_node import *
node_list = [BaseStartStepNode, BaseChatNode, BaseSearchDatasetNode, BaseQuestionNode, BaseConditionNode, BaseReplyNode,
BaseFunctionNodeNode, BaseFunctionLibNodeNode, BaseRerankerNode]
BaseFunctionNodeNode, BaseFunctionLibNodeNode, BaseRerankerNode, BaseApplicationNode]
def get_node(node_type):

View File

@ -0,0 +1,2 @@
# coding=utf-8
from .impl import *

View File

@ -0,0 +1,40 @@
# coding=utf-8
from typing import Type
from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage
class ApplicationNodeSerializer(serializers.Serializer):
application_id = serializers.CharField(required=True, error_messages=ErrMessage.char("应用id"))
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("用户输入字段"))
class IApplicationNode(INode):
type = 'application-node'
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
return ApplicationNodeSerializer
def _run(self):
question = self.workflow_manage.get_reference_field(
self.node_params_serializer.data.get('question_reference_address')[0],
self.node_params_serializer.data.get('question_reference_address')[1:])
kwargs = {}
for api_input_field in self.node_params_serializer.data.get('api_input_field_list', []):
kwargs[api_input_field['variable']] = self.workflow_manage.get_reference_field(api_input_field['value'][0],
api_input_field['value'][1:])
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:])
return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data,
message=str(question), **kwargs)
def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat, client_id, client_type,
**kwargs) -> NodeResult:
pass

View File

@ -0,0 +1,2 @@
# coding=utf-8
from .base_application_node import BaseApplicationNode

View File

@ -0,0 +1,124 @@
# coding=utf-8
import json
import time
import uuid
from typing import List, Dict
from application.flow.i_step_node import NodeResult, INode
from application.flow.step_node.application_node.i_application_node import IApplicationNode
from application.models import Chat
from common.handle.impl.response.openai_to_response import OpenaiToResponse
def string_to_uuid(input_str):
return str(uuid.uuid5(uuid.NAMESPACE_DNS, input_str))
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):
result = node_variable.get('result')
node.context['message_tokens'] = result.get('usage', {}).get('prompt_tokens', 0)
node.context['answer_tokens'] = result.get('usage', {}).get('completion_tokens', 0)
node.context['answer'] = answer
node.context['question'] = node_variable['question']
node.context['run_time'] = time.time() - node.context['start_time']
if workflow.is_result(node, NodeResult(node_variable, workflow_variable)):
workflow.answer += answer
def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):
"""
写入上下文数据 (流式)
@param node_variable: 节点数据
@param workflow_variable: 全局数据
@param node: 节点
@param workflow: 工作流管理器
"""
response = node_variable.get('result')
answer = ''
usage = {}
for chunk in response:
# 先把流转成字符串
response_content = chunk.decode('utf-8')[6:]
response_content = json.loads(response_content)
choices = response_content.get('choices')
if choices and isinstance(choices, list) and len(choices) > 0:
content = choices[0].get('delta', {}).get('content', '')
answer += content
yield content
usage = response_content.get('usage', {})
node_variable['result'] = {'usage': usage}
_write_context(node_variable, workflow_variable, node, workflow, answer)
def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):
"""
写入上下文数据
@param node_variable: 节点数据
@param workflow_variable: 全局数据
@param node: 节点实例对象
@param workflow: 工作流管理器
"""
response = node_variable.get('result')['choices'][0]['message']
answer = response.get('content', '') or "抱歉,没有查找到相关内容,请重新描述您的问题或提供更多信息。"
_write_context(node_variable, workflow_variable, node, workflow, answer)
class BaseApplicationNode(IApplicationNode):
def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat, client_id, client_type,
**kwargs) -> NodeResult:
from application.serializers.chat_message_serializers import ChatMessageSerializer
# 生成嵌入应用的chat_id
current_chat_id = string_to_uuid(chat_id + application_id)
Chat.objects.get_or_create(id=current_chat_id, defaults={
'application_id': application_id,
'abstract': message
})
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())
if response.status_code == 200:
if stream:
content_generator = response.streaming_content
return NodeResult({'result': content_generator, 'question': message}, {},
_write_context=write_context_stream)
else:
data = json.loads(response.content)
return NodeResult({'result': data, 'question': message}, {},
_write_context=write_context)
def get_details(self, index: int, **kwargs):
global_fields = []
for api_input_field in self.node_params_serializer.data.get('api_input_field_list', []):
global_fields.append({
'label': api_input_field['variable'],
'key': api_input_field['variable'],
'value': self.workflow_manage.get_reference_field(
api_input_field['value'][0],
api_input_field['value'][1:])
})
for user_input_field in self.node_params_serializer.data.get('user_input_field_list', []):
global_fields.append({
'label': user_input_field['label'],
'key': user_input_field['field'],
'value': self.workflow_manage.get_reference_field(
user_input_field['value'][0],
user_input_field['value'][1:])
})
return {
'name': self.node.properties.get('stepName'),
"index": index,
"info": self.node.properties.get('node_data'),
'run_time': self.context.get('run_time'),
'question': self.context.get('question'),
'answer': self.context.get('answer'),
'type': self.node.type,
'message_tokens': self.context.get('message_tokens'),
'answer_tokens': self.context.get('answer_tokens'),
'status': self.status,
'err_message': self.err_message,
'global_fields': global_fields
}

View File

@ -53,7 +53,7 @@ class Node:
self.__setattr__(keyword, kwargs.get(keyword))
end_nodes = ['ai-chat-node', 'reply-node', 'function-node', 'function-lib-node']
end_nodes = ['ai-chat-node', 'reply-node', 'function-node', 'function-lib-node', 'application-node']
class Flow:

View File

@ -15,14 +15,11 @@ import uuid
from functools import reduce
from typing import Dict, List
from django.conf import settings
from django.contrib.postgres.fields import ArrayField
from django.core import cache, validators
from django.core import signing
from django.core.paginator import Paginator
from django.db import transaction, models
from django.db.models import QuerySet, Q
from django.forms import CharField
from django.http import HttpResponse
from django.template import Template, Context
from rest_framework import serializers
@ -46,10 +43,9 @@ from dataset.models import DataSet, Document, Image
from dataset.serializers.common_serializers import list_paragraph, get_embedding_model_by_dataset_id_list
from embedding.models import SearchMode
from function_lib.serializers.function_lib_serializer import FunctionLibSerializer
from setting.models import AuthOperate, TeamMember, TeamMemberPermission
from setting.models import AuthOperate
from setting.models.model_management import Model
from setting.models_provider import get_model_credential
from setting.models_provider.constants.model_provider_constants import ModelProvideConstants
from setting.models_provider.tools import get_model_instance_by_model_user_id
from setting.serializers.provider_serializers import ModelSerializer
from smartdoc.conf import PROJECT_DIR
@ -979,6 +975,17 @@ class ApplicationSerializer(serializers.Serializer):
model = get_model_instance_by_model_user_id(tts_model_id, application.user_id, **form_data)
return model.text_to_speech(text)
def application_list(self, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
user_id = self.data.get('user_id')
application_id = self.data.get('application_id')
application = Application.objects.filter(user_id=user_id).exclude(id=application_id)
# 把应用的type为WORK_FLOW的应用放到最上面 然后再按名称排序
serialized_data = ApplicationSerializerModel(application, many=True).data
application = sorted(serialized_data, key=lambda x: (x['type'] != 'WORK_FLOW', x['name']))
return list(application)
class ApplicationKeySerializerModel(serializers.ModelSerializer):
class Meta:
model = ApplicationApiKey

View File

@ -305,6 +305,8 @@ class ChatMessageSerializer(serializers.Serializer):
'chat_id': chat_info.chat_id, 'chat_record_id': str(uuid.uuid1()),
'stream': stream,
're_chat': re_chat,
'client_id': client_id,
'client_type': client_type,
'user_id': user_id}, WorkFlowPostHandler(chat_info, client_id, client_type),
base_to_response, form_data)
r = work_flow_manage.run()

View File

@ -22,6 +22,7 @@ urlpatterns = [
path('application/<str:application_id>/function_lib', views.Application.FunctionLib.as_view()),
path('application/<str:application_id>/function_lib/<str:function_lib_id>',
views.Application.FunctionLib.Operate.as_view()),
path('application/<str:application_id>/application', views.Application.Application.as_view()),
path('application/<str:application_id>/model_params_form/<str:model_id>',
views.Application.ModelParamsForm.as_view()),
path('application/<str:application_id>/hit_test', views.Application.HitTest.as_view()),

View File

@ -243,6 +243,24 @@ class Application(APIView):
data={'application_id': application_id,
'user_id': request.user.id}).get_function_lib(function_lib_id))
class Application(APIView):
authentication_classes = [TokenAuth]
@action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary="获取当前人创建的应用列表",
operation_id="获取当前人创建的应用列表",
tags=["应用/会话"])
@has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER],
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE,
dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND))
def get(self, request: Request, application_id: str):
return result.success(
ApplicationSerializer.Operate(
data={'application_id': application_id,
'user_id': request.user.id}).application_list())
class Profile(APIView):
authentication_classes = [TokenAuth]

View File

@ -43,7 +43,7 @@ class ChatView(APIView):
class Export(APIView):
authentication_classes = [TokenAuth]
@action(methods=['GET'], detail=False)
@action(methods=['POST'], detail=False)
@swagger_auto_schema(operation_summary="导出对话",
operation_id="导出对话",
manual_parameters=ChatApi.get_request_params_api(),

View File

@ -309,6 +309,18 @@ const listFunctionLib: (application_id: String, loading?: Ref<boolean>) => Promi
) => {
return get(`${prefix}/${application_id}/function_lib`, undefined, loading)
}
/**
*
* @param application_id id
* @param loading
* @returns
*/
export const getApplicationList: (
application_id: string,
loading?: Ref<boolean>
) => Promise<Result<any>> = (application_id, loading) => {
return get(`${prefix}/${application_id}/application`, undefined, loading)
}
/**
*
* @param application_id
@ -500,5 +512,6 @@ export default {
getWorkFlowVersionDetail,
putWorkFlowVersion,
playDemoText,
getUserList
getUserList,
getApplicationList
}

View File

@ -17,7 +17,12 @@
<el-icon class="mr-8 arrow-icon" :class="current === index ? 'rotate-90' : ''"
><CaretRight
/></el-icon>
<component :is="iconComponent(`${item.type}-icon`)" class="mr-8" :size="24" />
<component
:is="iconComponent(`${item.type}-icon`)"
class="mr-8"
:size="24"
:item="item.info"
/>
<h4>{{ item.name }}</h4>
</div>
<div class="flex align-center">
@ -37,7 +42,11 @@
<div class="mt-12" v-if="current === index">
<template v-if="item.status === 200">
<!-- 开始 -->
<template v-if="item.type === WorkflowType.Start">
<template
v-if="
item.type === WorkflowType.Start || item.type === WorkflowType.Application
"
>
<div class="card-never border-r-4">
<h5 class="p-8-12">参数输入</h5>
<div class="p-8-12 border-t-dashed lighter">
@ -84,15 +93,25 @@
</template>
<!-- AI 对话 / 问题优化-->
<template
v-if="item.type == WorkflowType.AiChat || item.type == WorkflowType.Question"
v-if="
item.type == WorkflowType.AiChat ||
item.type == WorkflowType.Question ||
item.type == WorkflowType.Application
"
>
<div class="card-never border-r-4">
<div
class="card-never border-r-4"
v-if="item.type !== WorkflowType.Application"
>
<h5 class="p-8-12">角色设定 (System)</h5>
<div class="p-8-12 border-t-dashed lighter">
{{ item.system || '-' }}
</div>
</div>
<div class="card-never border-r-4 mt-8">
<div
class="card-never border-r-4 mt-8"
v-if="item.type !== WorkflowType.Application"
>
<h5 class="p-8-12">历史记录</h5>
<div class="p-8-12 border-t-dashed lighter">
<template v-if="item.history_message?.length > 0">
@ -115,7 +134,9 @@
</div>
</div>
<div class="card-never border-r-4 mt-8">
<h5 class="p-8-12">AI 回答</h5>
<h5 class="p-8-12">
{{ item.type == WorkflowType.Application ? '应用回答' : 'AI 回答' }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<MdPreview
v-if="item.answer"
@ -269,7 +290,7 @@ watch(dialogVisible, (bool) => {
const open = (data: any) => {
detail.value = cloneDeep(data)
console.log(detail.value)
dialogVisible.value = true
}
onBeforeUnmount(() => {

View File

@ -8,5 +8,6 @@ export enum WorkflowType {
Reply = 'reply-node',
FunctionLib = 'function-lib-node',
FunctionLibCustom = 'function-node',
RrerankerNode = 'reranker-node'
RrerankerNode = 'reranker-node',
Application = 'application-node'
}

View File

@ -43,7 +43,7 @@
<template v-for="(item, index) in filter_function_lib_list" :key="index">
<div
class="workflow-dropdown-item cursor flex p-8-12"
@click.stop="clickNodes(functionLibNode, item)"
@click.stop="clickNodes(functionLibNode, item, 'function')"
@mousedown.stop="onmousedown(functionLibNode, item)"
>
<component
@ -59,14 +59,45 @@
</template>
</el-scrollbar>
</el-tab-pane>
<el-tab-pane label="应用" name="application">
<el-scrollbar height="400">
<template v-for="(item, index) in filter_application_list" :key="index">
<div
class="workflow-dropdown-item cursor flex p-8-12"
@click.stop="clickNodes(applicationNode, item, 'application')"
@mousedown.stop="onmousedown(applicationNode, item, 'application')"
>
<component
:is="iconComponent(`application-node-icon`)"
class="mr-8 mt-4"
:size="32"
:item="item"
/>
<div class="pre-wrap" style="width: 60%">
<auto-tooltip :content="item.name" style="width: 80%" class="lighter">
{{ item.name }}
</auto-tooltip>
<el-text type="info" size="small" style="width: 80%">{{ item.desc }}</el-text>
</div>
<div class="status-tag" style="margin-left: auto">
<el-tag type="warning" v-if="isWorkFlow(item.type)" style="height: 22px"
>高级编排</el-tag
>
<el-tag class="blue-tag" v-else style="height: 22px">简单配置</el-tag>
</div>
</div>
</template>
</el-scrollbar>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { menuNodes, functionLibNode, functionNode } from '@/workflow/common/data'
import { menuNodes, functionLibNode, functionNode, applicationNode } from '@/workflow/common/data'
import { iconComponent } from '@/workflow/icons/utils'
import applicationApi from '@/api/application'
import { isWorkFlow } from '@/utils/application'
const search_text = ref<string>('')
const props = defineProps({
show: {
@ -91,21 +122,48 @@ const filter_function_lib_list = computed(() => {
item.name.toLocaleLowerCase().includes(search_text.value.toLocaleLowerCase())
)
})
const applicationList = ref<any[]>([])
const filter_application_list = computed(() => {
return applicationList.value.filter((item: any) =>
item.name.toLocaleLowerCase().includes(search_text.value.toLocaleLowerCase())
)
})
const filter_menu_nodes = computed(() => {
return menuNodes.filter((item) =>
item.label.toLocaleLowerCase().includes(search_text.value.toLocaleLowerCase())
)
})
function clickNodes(item: any, data?: any) {
function clickNodes(item: any, data?: any, type?: string) {
if (data) {
item['properties']['stepName'] = data.name
item['properties']['node_data'] = {
...data,
function_lib_id: data.id,
input_field_list: data.input_field_list.map((field: any) => ({
...field,
value: field.source == 'reference' ? [] : ''
}))
if (type == 'function') {
item['properties']['node_data'] = {
...data,
function_lib_id: data.id,
input_field_list: data.input_field_list.map((field: any) => ({
...field,
value: field.source == 'reference' ? [] : ''
}))
}
}
if (type == 'application') {
if (isWorkFlow(data.type)) {
console.log(data.work_flow.nodes[0].properties.api_input_field_list)
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
}
} else {
item['properties']['node_data'] = {
name: data.name,
icon: data.icon,
application_id: data.id
}
}
}
}
props.workflowRef?.addNode(item)
@ -113,16 +171,35 @@ function clickNodes(item: any, data?: any) {
emit('clickNodes', item)
}
function onmousedown(item: any, data?: any) {
function onmousedown(item: any, data?: any, type?: string) {
if (data) {
item['properties']['stepName'] = data.name
item['properties']['node_data'] = {
...data,
function_lib_id: data.id,
input_field_list: data.input_field_list.map((field: any) => ({
...field,
value: field.source == 'reference' ? [] : ''
}))
if (type == 'function') {
item['properties']['node_data'] = {
...data,
function_lib_id: data.id,
input_field_list: data.input_field_list.map((field: any) => ({
...field,
value: field.source == 'reference' ? [] : ''
}))
}
}
if (type == 'application') {
if (isWorkFlow(data.type)) {
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
}
} else {
item['properties']['node_data'] = {
name: data.name,
icon: data.icon,
application_id: data.id
}
}
}
}
props.workflowRef?.onmousedown(item)
@ -133,6 +210,9 @@ function getList() {
applicationApi.listFunctionLib(props.id, loading).then((res: any) => {
functionLibList.value = res.data
})
applicationApi.getApplicationList(props.id, loading).then((res: any) => {
applicationList.value = res.data
})
}
onMounted(() => {

View File

@ -11,7 +11,12 @@
class="flex align-center"
:style="{ maxWidth: node_status == 200 ? 'calc(100% - 55px)' : 'calc(100% - 85px)' }"
>
<component :is="iconComponent(`${nodeModel.type}-icon`)" class="mr-8" :size="24" />
<component
:is="iconComponent(`${nodeModel.type}-icon`)"
class="mr-8"
:size="24"
:item="nodeModel?.properties.node_data"
/>
<h4 v-if="showOperate(nodeModel.type)" style="max-width: 90%">
<ReadWrite
@mousemove.stop

View File

@ -215,6 +215,24 @@ export const functionLibNode = {
}
}
export const applicationNode = {
type: WorkflowType.Application,
text: '应用节点',
label: '应用节点',
height: 260,
properties: {
stepName: '应用节点',
config: {
fields: [
{
label: '结果',
value: 'result'
}
]
}
}
}
export const compareList = [
{ value: 'is_null', label: '为空' },
{ value: 'is_not_null', label: '不为空' },
@ -242,7 +260,8 @@ export const nodeDict: any = {
[WorkflowType.Reply]: replyNode,
[WorkflowType.FunctionLib]: functionLibNode,
[WorkflowType.FunctionLibCustom]: functionNode,
[WorkflowType.RrerankerNode]: rerankerNode
[WorkflowType.RrerankerNode]: rerankerNode,
[WorkflowType.Application]: applicationNode
}
export function isWorkFlow(type: string | undefined) {
return type === 'WORK_FLOW'

View File

@ -4,7 +4,8 @@ const end_nodes: Array<string> = [
WorkflowType.AiChat,
WorkflowType.Reply,
WorkflowType.FunctionLib,
WorkflowType.FunctionLibCustom
WorkflowType.FunctionLibCustom,
WorkflowType.Application
]
export class WorkFlowInstance {
nodes

View File

@ -0,0 +1,29 @@
<template>
<AppAvatar
v-if="isAppIcon(item.icon)"
shape="square"
:size="32"
style="background: none"
class="mr-8"
>
<img :src="item.icon" alt="" />
</AppAvatar>
<AppAvatar
v-else-if="item?.name"
:name="item?.name"
pinyinColor
shape="square"
:size="32"
class="mr-8"
/>
</template>
<script setup lang="ts">
import { isAppIcon } from '@/utils/application'
import { defineProps } from 'vue'
const props = defineProps<{
item: {
name: string
icon: string
}
}>()
</script>

View File

@ -0,0 +1,12 @@
import ChatNodeVue from './index.vue'
import { AppNode, AppNodeModel } from '@/workflow/common/app-node'
class ChatNode extends AppNode {
constructor(props: any) {
super(props, ChatNodeVue)
}
}
export default {
type: 'application-node',
model: AppNodeModel,
view: ChatNode
}

View File

@ -0,0 +1,132 @@
<template>
<NodeContainer :nodeModel="nodeModel">
<h5 class="title-decoration-1 mb-8">节点设置</h5>
<el-card shadow="never" class="card-never">
<el-form
@submit.prevent
:model="form_data"
label-position="top"
require-asterisk-position="right"
label-width="auto"
ref="applicationNodeFormRef"
>
<el-form-item
label="用户问题"
prop="question_reference_address"
:rules="{
message: '请选择检索问题',
trigger: 'blur',
required: true
}"
>
<NodeCascader
ref="nodeCascaderRef"
:nodeModel="nodeModel"
class="w-full"
placeholder="请选择检索问题"
v-model="form_data.question_reference_address"
/>
</el-form-item>
<div v-for="(field, index) in form_data.api_input_field_list" :key="'api-input-' + index">
<el-form-item
:label="field.variable"
:prop="'api_input_field_list.' + index + '.value'"
:rules="[
{ required: field.is_required, message: `请输入${field.variable}`, trigger: 'blur' }
]"
>
<NodeCascader
ref="nodeCascaderRef"
:nodeModel="nodeModel"
class="w-full"
placeholder="请选择检索问题"
v-model="form_data.api_input_field_list[index].value"
/>
</el-form-item>
</div>
<!-- Loop through dynamic fields for user_input_field_list -->
<div v-for="(field, index) in form_data.user_input_field_list" :key="'user-input-' + index">
<el-form-item
:label="field.label"
:prop="'user_input_field_list.' + index + '.value'"
:rules="[
{ required: field.required, message: `请输入${field.label}`, trigger: 'blur' }
]"
>
<NodeCascader
ref="nodeCascaderRef"
:nodeModel="nodeModel"
class="w-full"
placeholder="请选择检索问题"
v-model="form_data.user_input_field_list[index].value"
/>
</el-form-item>
</div>
<el-form-item label="返回内容" @click.prevent>
<template #label>
<div class="flex align-center">
<div class="mr-4">
<span>返回内容<span class="danger">*</span></span>
</div>
<el-tooltip effect="dark" placement="right" popper-class="max-w-200">
<template #content>
关闭后该节点的内容则不输出给用户
如果你想让用户看到该节点的输出内容请打开开关
</template>
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
</el-tooltip>
</div>
</template>
<el-switch size="small" v-model="form_data.is_result" />
</el-form-item>
</el-form>
</el-card>
</NodeContainer>
</template>
<script setup lang="ts">
import { set, groupBy } from 'lodash'
import { app } from '@/main'
import NodeContainer from '@/workflow/common/NodeContainer.vue'
import { ref, computed, onMounted } from 'vue'
import NodeCascader from '@/workflow/common/NodeCascader.vue'
import type { FormInstance } from 'element-plus'
const form = {
question_reference_address: [],
api_input_field_list: [],
user_input_field_list: []
}
const applicationNodeFormRef = ref<FormInstance>()
const form_data = computed({
get: () => {
if (props.nodeModel.properties.node_data) {
return props.nodeModel.properties.node_data
} else {
set(props.nodeModel.properties, 'node_data', form)
}
return props.nodeModel.properties.node_data
},
set: (value) => {
set(props.nodeModel.properties, 'node_data', value)
}
})
const props = defineProps<{ nodeModel: any }>()
const validate = () => {
return applicationNodeFormRef.value?.validate().catch((err) => {
return Promise.reject({ node: props.nodeModel, errMessage: err })
})
}
onMounted(() => {
console.log(applicationNodeFormRef.value)
set(props.nodeModel, 'validate', validate)
})
</script>
<style lang="scss" scoped></style>

View File

@ -198,6 +198,7 @@ function refresh() {
}
const validate = () => {
console.log(DatasetNodeFormRef.value)
return Promise.all([
nodeCascaderRef.value.validate(),
DatasetNodeFormRef.value?.validate()