mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 01:33:05 +00:00
feat: 工作流增加嵌入应用
--story=1016666 --user=王孝刚 【北区-售前】工作流支持嵌入应用 https://www.tapd.cn/57709429/s/1608362
This commit is contained in:
parent
6e6f564713
commit
72b91bee9a
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
# coding=utf-8
|
||||
from .impl import *
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
# coding=utf-8
|
||||
from .base_application_node import BaseApplicationNode
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -198,6 +198,7 @@ function refresh() {
|
|||
}
|
||||
|
||||
const validate = () => {
|
||||
console.log(DatasetNodeFormRef.value)
|
||||
return Promise.all([
|
||||
nodeCascaderRef.value.validate(),
|
||||
DatasetNodeFormRef.value?.validate()
|
||||
|
|
|
|||
Loading…
Reference in New Issue