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:
|
@desc:
|
||||||
"""
|
"""
|
||||||
from .ai_chat_step_node import *
|
from .ai_chat_step_node import *
|
||||||
|
from .application_node import BaseApplicationNode
|
||||||
from .condition_node import *
|
from .condition_node import *
|
||||||
from .question_node import *
|
from .question_node import *
|
||||||
from .search_dataset_node import *
|
from .search_dataset_node import *
|
||||||
@ -17,7 +18,7 @@ from .function_node import *
|
|||||||
from .reranker_node import *
|
from .reranker_node import *
|
||||||
|
|
||||||
node_list = [BaseStartStepNode, BaseChatNode, BaseSearchDatasetNode, BaseQuestionNode, BaseConditionNode, BaseReplyNode,
|
node_list = [BaseStartStepNode, BaseChatNode, BaseSearchDatasetNode, BaseQuestionNode, BaseConditionNode, BaseReplyNode,
|
||||||
BaseFunctionNodeNode, BaseFunctionLibNodeNode, BaseRerankerNode]
|
BaseFunctionNodeNode, BaseFunctionLibNodeNode, BaseRerankerNode, BaseApplicationNode]
|
||||||
|
|
||||||
|
|
||||||
def get_node(node_type):
|
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))
|
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:
|
class Flow:
|
||||||
|
|||||||
@ -15,14 +15,11 @@ import uuid
|
|||||||
from functools import reduce
|
from functools import reduce
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.core import cache, validators
|
from django.core import cache, validators
|
||||||
from django.core import signing
|
from django.core import signing
|
||||||
from django.core.paginator import Paginator
|
|
||||||
from django.db import transaction, models
|
from django.db import transaction, models
|
||||||
from django.db.models import QuerySet, Q
|
from django.db.models import QuerySet, Q
|
||||||
from django.forms import CharField
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.template import Template, Context
|
from django.template import Template, Context
|
||||||
from rest_framework import serializers
|
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 dataset.serializers.common_serializers import list_paragraph, get_embedding_model_by_dataset_id_list
|
||||||
from embedding.models import SearchMode
|
from embedding.models import SearchMode
|
||||||
from function_lib.serializers.function_lib_serializer import FunctionLibSerializer
|
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.model_management import Model
|
||||||
from setting.models_provider import get_model_credential
|
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.models_provider.tools import get_model_instance_by_model_user_id
|
||||||
from setting.serializers.provider_serializers import ModelSerializer
|
from setting.serializers.provider_serializers import ModelSerializer
|
||||||
from smartdoc.conf import PROJECT_DIR
|
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)
|
model = get_model_instance_by_model_user_id(tts_model_id, application.user_id, **form_data)
|
||||||
return model.text_to_speech(text)
|
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 ApplicationKeySerializerModel(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ApplicationApiKey
|
model = ApplicationApiKey
|
||||||
|
|||||||
@ -305,6 +305,8 @@ class ChatMessageSerializer(serializers.Serializer):
|
|||||||
'chat_id': chat_info.chat_id, 'chat_record_id': str(uuid.uuid1()),
|
'chat_id': chat_info.chat_id, 'chat_record_id': str(uuid.uuid1()),
|
||||||
'stream': stream,
|
'stream': stream,
|
||||||
're_chat': re_chat,
|
're_chat': re_chat,
|
||||||
|
'client_id': client_id,
|
||||||
|
'client_type': client_type,
|
||||||
'user_id': user_id}, WorkFlowPostHandler(chat_info, client_id, client_type),
|
'user_id': user_id}, WorkFlowPostHandler(chat_info, client_id, client_type),
|
||||||
base_to_response, form_data)
|
base_to_response, form_data)
|
||||||
r = work_flow_manage.run()
|
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', views.Application.FunctionLib.as_view()),
|
||||||
path('application/<str:application_id>/function_lib/<str:function_lib_id>',
|
path('application/<str:application_id>/function_lib/<str:function_lib_id>',
|
||||||
views.Application.FunctionLib.Operate.as_view()),
|
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>',
|
path('application/<str:application_id>/model_params_form/<str:model_id>',
|
||||||
views.Application.ModelParamsForm.as_view()),
|
views.Application.ModelParamsForm.as_view()),
|
||||||
path('application/<str:application_id>/hit_test', views.Application.HitTest.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,
|
data={'application_id': application_id,
|
||||||
'user_id': request.user.id}).get_function_lib(function_lib_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):
|
class Profile(APIView):
|
||||||
authentication_classes = [TokenAuth]
|
authentication_classes = [TokenAuth]
|
||||||
|
|
||||||
|
|||||||
@ -43,7 +43,7 @@ class ChatView(APIView):
|
|||||||
class Export(APIView):
|
class Export(APIView):
|
||||||
authentication_classes = [TokenAuth]
|
authentication_classes = [TokenAuth]
|
||||||
|
|
||||||
@action(methods=['GET'], detail=False)
|
@action(methods=['POST'], detail=False)
|
||||||
@swagger_auto_schema(operation_summary="导出对话",
|
@swagger_auto_schema(operation_summary="导出对话",
|
||||||
operation_id="导出对话",
|
operation_id="导出对话",
|
||||||
manual_parameters=ChatApi.get_request_params_api(),
|
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)
|
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
|
* @param application_id
|
||||||
@ -500,5 +512,6 @@ export default {
|
|||||||
getWorkFlowVersionDetail,
|
getWorkFlowVersionDetail,
|
||||||
putWorkFlowVersion,
|
putWorkFlowVersion,
|
||||||
playDemoText,
|
playDemoText,
|
||||||
getUserList
|
getUserList,
|
||||||
|
getApplicationList
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,12 @@
|
|||||||
<el-icon class="mr-8 arrow-icon" :class="current === index ? 'rotate-90' : ''"
|
<el-icon class="mr-8 arrow-icon" :class="current === index ? 'rotate-90' : ''"
|
||||||
><CaretRight
|
><CaretRight
|
||||||
/></el-icon>
|
/></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>
|
<h4>{{ item.name }}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex align-center">
|
<div class="flex align-center">
|
||||||
@ -37,7 +42,11 @@
|
|||||||
<div class="mt-12" v-if="current === index">
|
<div class="mt-12" v-if="current === index">
|
||||||
<template v-if="item.status === 200">
|
<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">
|
<div class="card-never border-r-4">
|
||||||
<h5 class="p-8-12">参数输入</h5>
|
<h5 class="p-8-12">参数输入</h5>
|
||||||
<div class="p-8-12 border-t-dashed lighter">
|
<div class="p-8-12 border-t-dashed lighter">
|
||||||
@ -84,15 +93,25 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- AI 对话 / 问题优化-->
|
<!-- AI 对话 / 问题优化-->
|
||||||
<template
|
<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"
|
||||||
|
v-if="item.type !== WorkflowType.Application"
|
||||||
>
|
>
|
||||||
<div class="card-never border-r-4">
|
|
||||||
<h5 class="p-8-12">角色设定 (System)</h5>
|
<h5 class="p-8-12">角色设定 (System)</h5>
|
||||||
<div class="p-8-12 border-t-dashed lighter">
|
<div class="p-8-12 border-t-dashed lighter">
|
||||||
{{ item.system || '-' }}
|
{{ item.system || '-' }}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<h5 class="p-8-12">历史记录</h5>
|
||||||
<div class="p-8-12 border-t-dashed lighter">
|
<div class="p-8-12 border-t-dashed lighter">
|
||||||
<template v-if="item.history_message?.length > 0">
|
<template v-if="item.history_message?.length > 0">
|
||||||
@ -115,7 +134,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-never border-r-4 mt-8">
|
<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">
|
<div class="p-8-12 border-t-dashed lighter">
|
||||||
<MdPreview
|
<MdPreview
|
||||||
v-if="item.answer"
|
v-if="item.answer"
|
||||||
@ -269,7 +290,7 @@ watch(dialogVisible, (bool) => {
|
|||||||
|
|
||||||
const open = (data: any) => {
|
const open = (data: any) => {
|
||||||
detail.value = cloneDeep(data)
|
detail.value = cloneDeep(data)
|
||||||
|
console.log(detail.value)
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
|||||||
@ -8,5 +8,6 @@ export enum WorkflowType {
|
|||||||
Reply = 'reply-node',
|
Reply = 'reply-node',
|
||||||
FunctionLib = 'function-lib-node',
|
FunctionLib = 'function-lib-node',
|
||||||
FunctionLibCustom = 'function-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">
|
<template v-for="(item, index) in filter_function_lib_list" :key="index">
|
||||||
<div
|
<div
|
||||||
class="workflow-dropdown-item cursor flex p-8-12"
|
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)"
|
@mousedown.stop="onmousedown(functionLibNode, item)"
|
||||||
>
|
>
|
||||||
<component
|
<component
|
||||||
@ -59,14 +59,45 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</el-tab-pane>
|
</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>
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed } from 'vue'
|
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 { iconComponent } from '@/workflow/icons/utils'
|
||||||
import applicationApi from '@/api/application'
|
import applicationApi from '@/api/application'
|
||||||
|
import { isWorkFlow } from '@/utils/application'
|
||||||
const search_text = ref<string>('')
|
const search_text = ref<string>('')
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
show: {
|
show: {
|
||||||
@ -91,14 +122,22 @@ const filter_function_lib_list = computed(() => {
|
|||||||
item.name.toLocaleLowerCase().includes(search_text.value.toLocaleLowerCase())
|
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(() => {
|
const filter_menu_nodes = computed(() => {
|
||||||
return menuNodes.filter((item) =>
|
return menuNodes.filter((item) =>
|
||||||
item.label.toLocaleLowerCase().includes(search_text.value.toLocaleLowerCase())
|
item.label.toLocaleLowerCase().includes(search_text.value.toLocaleLowerCase())
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
function clickNodes(item: any, data?: any) {
|
function clickNodes(item: any, data?: any, type?: string) {
|
||||||
if (data) {
|
if (data) {
|
||||||
item['properties']['stepName'] = data.name
|
item['properties']['stepName'] = data.name
|
||||||
|
if (type == 'function') {
|
||||||
item['properties']['node_data'] = {
|
item['properties']['node_data'] = {
|
||||||
...data,
|
...data,
|
||||||
function_lib_id: data.id,
|
function_lib_id: data.id,
|
||||||
@ -108,14 +147,34 @@ function clickNodes(item: any, data?: any) {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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)
|
props.workflowRef?.addNode(item)
|
||||||
|
|
||||||
emit('clickNodes', item)
|
emit('clickNodes', item)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onmousedown(item: any, data?: any) {
|
function onmousedown(item: any, data?: any, type?: string) {
|
||||||
if (data) {
|
if (data) {
|
||||||
item['properties']['stepName'] = data.name
|
item['properties']['stepName'] = data.name
|
||||||
|
if (type == 'function') {
|
||||||
item['properties']['node_data'] = {
|
item['properties']['node_data'] = {
|
||||||
...data,
|
...data,
|
||||||
function_lib_id: data.id,
|
function_lib_id: data.id,
|
||||||
@ -125,6 +184,24 @@ function onmousedown(item: any, data?: any) {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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)
|
props.workflowRef?.onmousedown(item)
|
||||||
emit('onmousedown', item)
|
emit('onmousedown', item)
|
||||||
}
|
}
|
||||||
@ -133,6 +210,9 @@ function getList() {
|
|||||||
applicationApi.listFunctionLib(props.id, loading).then((res: any) => {
|
applicationApi.listFunctionLib(props.id, loading).then((res: any) => {
|
||||||
functionLibList.value = res.data
|
functionLibList.value = res.data
|
||||||
})
|
})
|
||||||
|
applicationApi.getApplicationList(props.id, loading).then((res: any) => {
|
||||||
|
applicationList.value = res.data
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@ -11,7 +11,12 @@
|
|||||||
class="flex align-center"
|
class="flex align-center"
|
||||||
:style="{ maxWidth: node_status == 200 ? 'calc(100% - 55px)' : 'calc(100% - 85px)' }"
|
: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%">
|
<h4 v-if="showOperate(nodeModel.type)" style="max-width: 90%">
|
||||||
<ReadWrite
|
<ReadWrite
|
||||||
@mousemove.stop
|
@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 = [
|
export const compareList = [
|
||||||
{ value: 'is_null', label: '为空' },
|
{ value: 'is_null', label: '为空' },
|
||||||
{ value: 'is_not_null', label: '不为空' },
|
{ value: 'is_not_null', label: '不为空' },
|
||||||
@ -242,7 +260,8 @@ export const nodeDict: any = {
|
|||||||
[WorkflowType.Reply]: replyNode,
|
[WorkflowType.Reply]: replyNode,
|
||||||
[WorkflowType.FunctionLib]: functionLibNode,
|
[WorkflowType.FunctionLib]: functionLibNode,
|
||||||
[WorkflowType.FunctionLibCustom]: functionNode,
|
[WorkflowType.FunctionLibCustom]: functionNode,
|
||||||
[WorkflowType.RrerankerNode]: rerankerNode
|
[WorkflowType.RrerankerNode]: rerankerNode,
|
||||||
|
[WorkflowType.Application]: applicationNode
|
||||||
}
|
}
|
||||||
export function isWorkFlow(type: string | undefined) {
|
export function isWorkFlow(type: string | undefined) {
|
||||||
return type === 'WORK_FLOW'
|
return type === 'WORK_FLOW'
|
||||||
|
|||||||
@ -4,7 +4,8 @@ const end_nodes: Array<string> = [
|
|||||||
WorkflowType.AiChat,
|
WorkflowType.AiChat,
|
||||||
WorkflowType.Reply,
|
WorkflowType.Reply,
|
||||||
WorkflowType.FunctionLib,
|
WorkflowType.FunctionLib,
|
||||||
WorkflowType.FunctionLibCustom
|
WorkflowType.FunctionLibCustom,
|
||||||
|
WorkflowType.Application
|
||||||
]
|
]
|
||||||
export class WorkFlowInstance {
|
export class WorkFlowInstance {
|
||||||
nodes
|
nodes
|
||||||
|
|||||||
29
ui/src/workflow/icons/application-node-icon.vue
Normal file
29
ui/src/workflow/icons/application-node-icon.vue
Normal 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>
|
||||||
12
ui/src/workflow/nodes/application-node/index.ts
Normal file
12
ui/src/workflow/nodes/application-node/index.ts
Normal 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
|
||||||
|
}
|
||||||
132
ui/src/workflow/nodes/application-node/index.vue
Normal file
132
ui/src/workflow/nodes/application-node/index.vue
Normal 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>
|
||||||
@ -198,6 +198,7 @@ function refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const validate = () => {
|
const validate = () => {
|
||||||
|
console.log(DatasetNodeFormRef.value)
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
nodeCascaderRef.value.validate(),
|
nodeCascaderRef.value.validate(),
|
||||||
DatasetNodeFormRef.value?.validate()
|
DatasetNodeFormRef.value?.validate()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user