Merge branch 'main' of https://github.com/maxkb-dev/maxkb
This commit is contained in:
commit
b32bc73e63
@ -39,7 +39,8 @@ docker run -d --name=maxkb --restart=always -p 8080:8080 -v C:/maxkb:/var/lib/po
|
|||||||
|
|
||||||
- 你也可以通过 [1Panel 应用商店](https://apps.fit2cloud.com/1panel) 快速部署 MaxKB;
|
- 你也可以通过 [1Panel 应用商店](https://apps.fit2cloud.com/1panel) 快速部署 MaxKB;
|
||||||
- 如果是内网环境,推荐使用 [离线安装包](https://community.fit2cloud.com/#/products/maxkb/downloads) 进行安装部署;
|
- 如果是内网环境,推荐使用 [离线安装包](https://community.fit2cloud.com/#/products/maxkb/downloads) 进行安装部署;
|
||||||
- MaxKB 产品版本分为社区版和专业版,详情请参见:[MaxKB 产品版本对比](https://maxkb.cn/pricing.html)。
|
- MaxKB 产品版本分为社区版和专业版,详情请参见:[MaxKB 产品版本对比](https://maxkb.cn/pricing.html);
|
||||||
|
- 如果您需要向团队介绍 MaxKB,可以使用这个 [官方 PPT 材料](https://maxkb.cn/download/introduce-maxkb_202411.pdf)。
|
||||||
|
|
||||||
如你有更多问题,可以查看使用手册,或者通过论坛与我们交流。
|
如你有更多问题,可以查看使用手册,或者通过论坛与我们交流。
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,7 @@ def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
|
|||||||
if workflow.is_result(node, NodeResult(step_variable, global_variable)) and 'answer' in step_variable:
|
if workflow.is_result(node, NodeResult(step_variable, global_variable)) and 'answer' in step_variable:
|
||||||
answer = step_variable['answer']
|
answer = step_variable['answer']
|
||||||
yield answer
|
yield answer
|
||||||
workflow.append_answer(answer)
|
node.answer_text = answer
|
||||||
if global_variable is not None:
|
if global_variable is not None:
|
||||||
for key in global_variable:
|
for key in global_variable:
|
||||||
workflow.context[key] = global_variable[key]
|
workflow.context[key] = global_variable[key]
|
||||||
|
|||||||
@ -9,12 +9,7 @@ from common.util.field_message import ErrMessage
|
|||||||
|
|
||||||
|
|
||||||
class DocumentExtractNodeSerializer(serializers.Serializer):
|
class DocumentExtractNodeSerializer(serializers.Serializer):
|
||||||
# 需要查询的数据集id列表
|
document_list = serializers.ListField(required=False, error_messages=ErrMessage.list("文档"))
|
||||||
file_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),
|
|
||||||
error_messages=ErrMessage.list("数据集id列表"))
|
|
||||||
|
|
||||||
def is_valid(self, *, raise_exception=False):
|
|
||||||
super().is_valid(raise_exception=True)
|
|
||||||
|
|
||||||
|
|
||||||
class IDocumentExtractNode(INode):
|
class IDocumentExtractNode(INode):
|
||||||
@ -24,7 +19,9 @@ class IDocumentExtractNode(INode):
|
|||||||
return DocumentExtractNodeSerializer
|
return DocumentExtractNodeSerializer
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
return self.execute(**self.flow_params_serializer.data)
|
res = self.workflow_manage.get_reference_field(self.node_params_serializer.data.get('document_list')[0],
|
||||||
|
self.node_params_serializer.data.get('document_list')[1:])
|
||||||
|
return self.execute(document=res, **self.flow_params_serializer.data)
|
||||||
|
|
||||||
def execute(self, file_list, **kwargs) -> NodeResult:
|
def execute(self, document, **kwargs) -> NodeResult:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@ -1,11 +1,34 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
|
from application.flow.i_step_node import NodeResult
|
||||||
from application.flow.step_node.document_extract_node.i_document_extract_node import IDocumentExtractNode
|
from application.flow.step_node.document_extract_node.i_document_extract_node import IDocumentExtractNode
|
||||||
|
from dataset.models import File
|
||||||
|
|
||||||
|
|
||||||
class BaseDocumentExtractNode(IDocumentExtractNode):
|
class BaseDocumentExtractNode(IDocumentExtractNode):
|
||||||
def execute(self, file_list, **kwargs):
|
def execute(self, document, **kwargs):
|
||||||
pass
|
self.context['document_list'] = document
|
||||||
|
content = ''
|
||||||
|
spliter = '\n-----------------------------------\n'
|
||||||
|
if len(document) > 0:
|
||||||
|
for doc in document:
|
||||||
|
file = QuerySet(File).filter(id=doc['file_id']).first()
|
||||||
|
file_type = doc['name'].split('.')[-1]
|
||||||
|
if file_type.lower() in ['txt', 'md', 'csv', 'html']:
|
||||||
|
content += spliter + doc['name'] + '\n' + file.get_byte().tobytes().decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
return NodeResult({'content': content}, {})
|
||||||
|
|
||||||
def get_details(self, index: int, **kwargs):
|
def get_details(self, index: int, **kwargs):
|
||||||
pass
|
return {
|
||||||
|
'name': self.node.properties.get('stepName'),
|
||||||
|
"index": index,
|
||||||
|
'run_time': self.context.get('run_time'),
|
||||||
|
'type': self.node.type,
|
||||||
|
'content': self.context.get('content'),
|
||||||
|
'status': self.status,
|
||||||
|
'err_message': self.err_message,
|
||||||
|
'document_list': self.context.get('document_list')
|
||||||
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@ class ImageUnderstandNodeSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean('是否返回内容'))
|
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean('是否返回内容'))
|
||||||
|
|
||||||
image_list = serializers.ListField(required=False, error_messages=ErrMessage.list("图片仅1张"))
|
image_list = serializers.ListField(required=False, error_messages=ErrMessage.list("图片"))
|
||||||
|
|
||||||
|
|
||||||
class IImageUnderstandNode(INode):
|
class IImageUnderstandNode(INode):
|
||||||
|
|||||||
@ -25,7 +25,7 @@ def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wo
|
|||||||
node.context['question'] = node_variable['question']
|
node.context['question'] = node_variable['question']
|
||||||
node.context['run_time'] = time.time() - node.context['start_time']
|
node.context['run_time'] = time.time() - node.context['start_time']
|
||||||
if workflow.is_result(node, NodeResult(node_variable, workflow_variable)):
|
if workflow.is_result(node, NodeResult(node_variable, workflow_variable)):
|
||||||
workflow.answer += answer
|
node.answer_text = answer
|
||||||
|
|
||||||
|
|
||||||
def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):
|
def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):
|
||||||
|
|||||||
@ -52,8 +52,12 @@ class BaseStartStepNode(IStarNode):
|
|||||||
"""
|
"""
|
||||||
开始节点 初始化全局变量
|
开始节点 初始化全局变量
|
||||||
"""
|
"""
|
||||||
return NodeResult({'question': question, 'image': self.workflow_manage.image_list},
|
node_variable = {
|
||||||
workflow_variable)
|
'question': question,
|
||||||
|
'image': self.workflow_manage.image_list,
|
||||||
|
'document': self.workflow_manage.document_list
|
||||||
|
}
|
||||||
|
return NodeResult(node_variable, workflow_variable)
|
||||||
|
|
||||||
def get_details(self, index: int, **kwargs):
|
def get_details(self, index: int, **kwargs):
|
||||||
global_fields = []
|
global_fields = []
|
||||||
|
|||||||
@ -240,16 +240,20 @@ class NodeChunk:
|
|||||||
class WorkflowManage:
|
class WorkflowManage:
|
||||||
def __init__(self, flow: Flow, params, work_flow_post_handler: WorkFlowPostHandler,
|
def __init__(self, flow: Flow, params, work_flow_post_handler: WorkFlowPostHandler,
|
||||||
base_to_response: BaseToResponse = SystemToResponse(), form_data=None, image_list=None,
|
base_to_response: BaseToResponse = SystemToResponse(), form_data=None, image_list=None,
|
||||||
|
document_list=None,
|
||||||
start_node_id=None,
|
start_node_id=None,
|
||||||
start_node_data=None, chat_record=None):
|
start_node_data=None, chat_record=None):
|
||||||
if form_data is None:
|
if form_data is None:
|
||||||
form_data = {}
|
form_data = {}
|
||||||
if image_list is None:
|
if image_list is None:
|
||||||
image_list = []
|
image_list = []
|
||||||
|
if document_list is None:
|
||||||
|
document_list = []
|
||||||
self.start_node = None
|
self.start_node = None
|
||||||
self.start_node_result_future = None
|
self.start_node_result_future = None
|
||||||
self.form_data = form_data
|
self.form_data = form_data
|
||||||
self.image_list = image_list
|
self.image_list = image_list
|
||||||
|
self.document_list = document_list
|
||||||
self.params = params
|
self.params = params
|
||||||
self.flow = flow
|
self.flow = flow
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
@ -511,7 +515,7 @@ class WorkflowManage:
|
|||||||
if index == 0:
|
if index == 0:
|
||||||
result.append(answer.get('content'))
|
result.append(answer.get('content'))
|
||||||
continue
|
continue
|
||||||
if answer.get('type') != answer_text_list[index - 1]:
|
if answer.get('type') != answer_text_list[index - 1].get('type'):
|
||||||
result.append(answer.get('content'))
|
result.append(answer.get('content'))
|
||||||
else:
|
else:
|
||||||
result[-1] += answer.get('content')
|
result[-1] += answer.get('content')
|
||||||
|
|||||||
@ -13,6 +13,6 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='application',
|
model_name='application',
|
||||||
name='model_params_setting',
|
name='model_params_setting',
|
||||||
field=models.JSONField(default={}, verbose_name='模型参数相关设置'),
|
field=models.JSONField(default=dict, verbose_name='模型参数相关设置'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -13,6 +13,6 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='application',
|
model_name='application',
|
||||||
name='tts_model_params_setting',
|
name='tts_model_params_setting',
|
||||||
field=models.JSONField(default={}, verbose_name='模型参数相关设置'),
|
field=models.JSONField(default=dict, verbose_name='模型参数相关设置'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -23,7 +23,7 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='application',
|
model_name='application',
|
||||||
name='file_upload_setting',
|
name='file_upload_setting',
|
||||||
field=models.JSONField(default={}, verbose_name='文件上传相关设置'),
|
field=models.JSONField(default=dict, verbose_name='文件上传相关设置'),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='chatrecord',
|
model_name='chatrecord',
|
||||||
|
|||||||
@ -48,8 +48,8 @@ class Application(AppModelMixin):
|
|||||||
model = models.ForeignKey(Model, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True)
|
model = models.ForeignKey(Model, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True)
|
||||||
dataset_setting = models.JSONField(verbose_name="数据集参数设置", default=get_dataset_setting_dict)
|
dataset_setting = models.JSONField(verbose_name="数据集参数设置", default=get_dataset_setting_dict)
|
||||||
model_setting = models.JSONField(verbose_name="模型参数相关设置", default=get_model_setting_dict)
|
model_setting = models.JSONField(verbose_name="模型参数相关设置", default=get_model_setting_dict)
|
||||||
model_params_setting = models.JSONField(verbose_name="模型参数相关设置", default={})
|
model_params_setting = models.JSONField(verbose_name="模型参数相关设置", default=dict)
|
||||||
tts_model_params_setting = models.JSONField(verbose_name="模型参数相关设置", default={})
|
tts_model_params_setting = models.JSONField(verbose_name="模型参数相关设置", default=dict)
|
||||||
problem_optimization = models.BooleanField(verbose_name="问题优化", default=False)
|
problem_optimization = models.BooleanField(verbose_name="问题优化", default=False)
|
||||||
icon = models.CharField(max_length=256, verbose_name="应用icon", default="/ui/favicon.ico")
|
icon = models.CharField(max_length=256, verbose_name="应用icon", default="/ui/favicon.ico")
|
||||||
work_flow = models.JSONField(verbose_name="工作流数据", default=dict)
|
work_flow = models.JSONField(verbose_name="工作流数据", default=dict)
|
||||||
@ -67,7 +67,7 @@ class Application(AppModelMixin):
|
|||||||
tts_type = models.CharField(verbose_name="语音播放类型", max_length=20, default="BROWSER")
|
tts_type = models.CharField(verbose_name="语音播放类型", max_length=20, default="BROWSER")
|
||||||
clean_time = models.IntegerField(verbose_name="清理时间", default=180)
|
clean_time = models.IntegerField(verbose_name="清理时间", default=180)
|
||||||
file_upload_enable = models.BooleanField(verbose_name="文件上传是否启用", default=False)
|
file_upload_enable = models.BooleanField(verbose_name="文件上传是否启用", default=False)
|
||||||
file_upload_setting = models.JSONField(verbose_name="文件上传相关设置", default={})
|
file_upload_setting = models.JSONField(verbose_name="文件上传相关设置", default=dict)
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@ -230,7 +230,8 @@ class ChatMessageSerializer(serializers.Serializer):
|
|||||||
client_id = serializers.CharField(required=True, error_messages=ErrMessage.char("客户端id"))
|
client_id = serializers.CharField(required=True, error_messages=ErrMessage.char("客户端id"))
|
||||||
client_type = serializers.CharField(required=True, error_messages=ErrMessage.char("客户端类型"))
|
client_type = serializers.CharField(required=True, error_messages=ErrMessage.char("客户端类型"))
|
||||||
form_data = serializers.DictField(required=False, error_messages=ErrMessage.char("全局变量"))
|
form_data = serializers.DictField(required=False, error_messages=ErrMessage.char("全局变量"))
|
||||||
image_list = serializers.ListField(required=False, error_messages=ErrMessage.list("图片仅1张"))
|
image_list = serializers.ListField(required=False, error_messages=ErrMessage.list("图片"))
|
||||||
|
document_list = serializers.ListField(required=False, error_messages=ErrMessage.list("文档"))
|
||||||
|
|
||||||
def is_valid_application_workflow(self, *, raise_exception=False):
|
def is_valid_application_workflow(self, *, raise_exception=False):
|
||||||
self.is_valid_intraday_access_num()
|
self.is_valid_intraday_access_num()
|
||||||
@ -322,6 +323,7 @@ class ChatMessageSerializer(serializers.Serializer):
|
|||||||
client_type = self.data.get('client_type')
|
client_type = self.data.get('client_type')
|
||||||
form_data = self.data.get('form_data')
|
form_data = self.data.get('form_data')
|
||||||
image_list = self.data.get('image_list')
|
image_list = self.data.get('image_list')
|
||||||
|
document_list = self.data.get('document_list')
|
||||||
user_id = chat_info.application.user_id
|
user_id = chat_info.application.user_id
|
||||||
chat_record_id = self.data.get('chat_record_id')
|
chat_record_id = self.data.get('chat_record_id')
|
||||||
chat_record = None
|
chat_record = None
|
||||||
@ -336,7 +338,7 @@ class ChatMessageSerializer(serializers.Serializer):
|
|||||||
'client_id': client_id,
|
'client_id': client_id,
|
||||||
'client_type': client_type,
|
'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, image_list, self.data.get('runtime_node_id'),
|
base_to_response, form_data, image_list, document_list, self.data.get('runtime_node_id'),
|
||||||
self.data.get('node_data'), chat_record)
|
self.data.get('node_data'), chat_record)
|
||||||
r = work_flow_manage.run()
|
r = work_flow_manage.run()
|
||||||
return r
|
return r
|
||||||
|
|||||||
@ -132,6 +132,8 @@ class ChatView(APIView):
|
|||||||
|
|
||||||
'image_list': request.data.get(
|
'image_list': request.data.get(
|
||||||
'image_list') if 'image_list' in request.data else [],
|
'image_list') if 'image_list' in request.data else [],
|
||||||
|
'document_list': request.data.get(
|
||||||
|
'document_list') if 'document_list' in request.data else [],
|
||||||
'client_type': request.auth.client_type,
|
'client_type': request.auth.client_type,
|
||||||
'runtime_node_id': request.data.get('runtime_node_id', None),
|
'runtime_node_id': request.data.get('runtime_node_id', None),
|
||||||
'node_data': request.data.get('node_data', {}),
|
'node_data': request.data.get('node_data', {}),
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
"@ctrl/tinycolor": "^4.1.0",
|
"@ctrl/tinycolor": "^4.1.0",
|
||||||
"@logicflow/core": "^1.2.27",
|
"@logicflow/core": "^1.2.27",
|
||||||
"@logicflow/extension": "^1.2.27",
|
"@logicflow/extension": "^1.2.27",
|
||||||
|
"@antv/layout": "^0.3.1",
|
||||||
"@vueuse/core": "^10.9.0",
|
"@vueuse/core": "^10.9.0",
|
||||||
"@wecom/jssdk": "^2.1.0",
|
"@wecom/jssdk": "^2.1.0",
|
||||||
"axios": "^0.28.0",
|
"axios": "^0.28.0",
|
||||||
|
|||||||
@ -39,7 +39,8 @@ interface chatType {
|
|||||||
record_id: string
|
record_id: string
|
||||||
chat_id: string
|
chat_id: string
|
||||||
vote_status: string
|
vote_status: string
|
||||||
status?: number
|
status?: number,
|
||||||
|
execution_details: any[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ChatRecordManage {
|
export class ChatRecordManage {
|
||||||
|
|||||||
@ -1,14 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="item-content mb-16 lighter">
|
<div class="item-content mb-16 lighter">
|
||||||
<div v-for="(answer_text, index) in chatRecord.answer_text_list" :key="index">
|
<template v-for="(answer_text, index) in chatRecord.answer_text_list" :key="index">
|
||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
<img v-if="application.avatar" :src="application.avatar" height="32px" width="32px" />
|
<img v-if="application.avatar" :src="application.avatar" height="32px" width="32px" />
|
||||||
<LogoIcon v-else height="32px" width="32px" />
|
<LogoIcon v-else height="32px" width="32px" />
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<el-card shadow="always" class="dialog-card">
|
<el-card shadow="always" class="dialog-card mb-8">
|
||||||
<MdRenderer
|
<MdRenderer
|
||||||
v-if="answer_text"
|
v-if="
|
||||||
|
(chatRecord.write_ed === undefined || chatRecord.write_ed === true) && !answer_text
|
||||||
|
"
|
||||||
|
source=" 抱歉,没有查找到相关内容,请重新描述您的问题或提供更多信息。"
|
||||||
|
></MdRenderer>
|
||||||
|
<MdRenderer
|
||||||
|
v-else-if="answer_text"
|
||||||
:source="answer_text"
|
:source="answer_text"
|
||||||
:send-message="chatMessage"
|
:send-message="chatMessage"
|
||||||
></MdRenderer>
|
></MdRenderer>
|
||||||
@ -23,6 +29,9 @@
|
|||||||
<KnowledgeSource :data="chatRecord" :type="application.type" />
|
<KnowledgeSource :data="chatRecord" :type="application.type" />
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="content">
|
||||||
<OperationButton
|
<OperationButton
|
||||||
:type="type"
|
:type="type"
|
||||||
:application="application"
|
:application="application"
|
||||||
@ -34,7 +43,6 @@
|
|||||||
></OperationButton>
|
></OperationButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import KnowledgeSource from '@/components/ai-chat/KnowledgeSource.vue'
|
import KnowledgeSource from '@/components/ai-chat/KnowledgeSource.vue'
|
||||||
|
|||||||
@ -20,10 +20,12 @@
|
|||||||
|
|
||||||
<div class="operate flex align-center">
|
<div class="operate flex align-center">
|
||||||
<span v-if="props.applicationDetails.file_upload_enable" class="flex align-center">
|
<span v-if="props.applicationDetails.file_upload_enable" class="flex align-center">
|
||||||
|
<!-- accept="image/jpeg, image/png, image/gif"-->
|
||||||
<el-upload
|
<el-upload
|
||||||
action="#"
|
action="#"
|
||||||
:auto-upload="false"
|
:auto-upload="false"
|
||||||
:show-file-list="false"
|
:show-file-list="false"
|
||||||
|
:accept="[...imageExtensions, ...documentExtensions].map((ext) => '.' + ext).join(',')"
|
||||||
:on-change="(file: any, fileList: any) => uploadFile(file, fileList)"
|
:on-change="(file: any, fileList: any) => uploadFile(file, fileList)"
|
||||||
>
|
>
|
||||||
<el-button text>
|
<el-button text>
|
||||||
@ -126,6 +128,13 @@ const localLoading = computed({
|
|||||||
emit('update:loading', v)
|
emit('update:loading', v)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp']
|
||||||
|
const documentExtensions = ['pdf', 'docx', 'txt', 'xls', 'xlsx', 'md', 'html', 'csv']
|
||||||
|
const videoExtensions = ['mp4', 'avi', 'mov', 'mkv', 'flv']
|
||||||
|
const audioExtensions = ['mp3', 'wav', 'aac', 'flac']
|
||||||
|
|
||||||
const uploadFile = async (file: any, fileList: any) => {
|
const uploadFile = async (file: any, fileList: any) => {
|
||||||
const { maxFiles, fileLimit } = props.applicationDetails.file_upload_setting
|
const { maxFiles, fileLimit } = props.applicationDetails.file_upload_setting
|
||||||
if (fileList.length > maxFiles) {
|
if (fileList.length > maxFiles) {
|
||||||
@ -141,7 +150,18 @@ const uploadFile = async (file: any, fileList: any) => {
|
|||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
for (const file of fileList) {
|
for (const file of fileList) {
|
||||||
formData.append('file', file.raw, file.name)
|
formData.append('file', file.raw, file.name)
|
||||||
uploadFileList.value.push(file)
|
//
|
||||||
|
const extension = file.name.split('.').pop().toLowerCase() // 获取文件后缀名并转为小写
|
||||||
|
|
||||||
|
if (imageExtensions.includes(extension)) {
|
||||||
|
uploadImageList.value.push(file)
|
||||||
|
} else if (documentExtensions.includes(extension)) {
|
||||||
|
uploadDocumentList.value.push(file)
|
||||||
|
} else if (videoExtensions.includes(extension)) {
|
||||||
|
// videos.push(file)
|
||||||
|
} else if (audioExtensions.includes(extension)) {
|
||||||
|
// audios.push(file)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!chatId_context.value) {
|
if (!chatId_context.value) {
|
||||||
@ -158,21 +178,29 @@ const uploadFile = async (file: any, fileList: any) => {
|
|||||||
)
|
)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
fileList.splice(0, fileList.length)
|
fileList.splice(0, fileList.length)
|
||||||
uploadFileList.value.forEach((file: any) => {
|
uploadImageList.value.forEach((file: any) => {
|
||||||
const f = response.data.filter((f: any) => f.name === file.name)
|
const f = response.data.filter((f: any) => f.name === file.name)
|
||||||
if (f.length > 0) {
|
if (f.length > 0) {
|
||||||
file.url = f[0].url
|
file.url = f[0].url
|
||||||
file.file_id = f[0].file_id
|
file.file_id = f[0].file_id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
console.log(uploadFileList.value)
|
uploadDocumentList.value.forEach((file: any) => {
|
||||||
|
const f = response.data.filter((f: any) => f.name === file.name)
|
||||||
|
if (f.length > 0) {
|
||||||
|
file.url = f[0].url
|
||||||
|
file.file_id = f[0].file_id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log(uploadDocumentList.value, uploadImageList.value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const recorderTime = ref(0)
|
const recorderTime = ref(0)
|
||||||
const startRecorderTime = ref(false)
|
const startRecorderTime = ref(false)
|
||||||
const recorderLoading = ref(false)
|
const recorderLoading = ref(false)
|
||||||
const inputValue = ref<string>('')
|
const inputValue = ref<string>('')
|
||||||
const uploadFileList = ref<Array<any>>([])
|
const uploadImageList = ref<Array<any>>([])
|
||||||
|
const uploadDocumentList = ref<Array<any>>([])
|
||||||
const mediaRecorderStatus = ref(true)
|
const mediaRecorderStatus = ref(true)
|
||||||
// 定义响应式引用
|
// 定义响应式引用
|
||||||
const mediaRecorder = ref<any>(null)
|
const mediaRecorder = ref<any>(null)
|
||||||
@ -289,15 +317,20 @@ const handleTimeChange = () => {
|
|||||||
handleTimeChange()
|
handleTimeChange()
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendChatHandle(event: any) {
|
function sendChatHandle(event: any) {
|
||||||
if (!event.ctrlKey) {
|
if (!event.ctrlKey) {
|
||||||
// 如果没有按下组合键ctrl,则会阻止默认事件
|
// 如果没有按下组合键ctrl,则会阻止默认事件
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (!isDisabledChart.value && !props.loading && !event.isComposing) {
|
if (!isDisabledChart.value && !props.loading && !event.isComposing) {
|
||||||
if (inputValue.value.trim()) {
|
if (inputValue.value.trim()) {
|
||||||
props.sendMessage(inputValue.value, { image_list: uploadFileList.value })
|
props.sendMessage(inputValue.value, {
|
||||||
|
image_list: uploadImageList.value,
|
||||||
|
document_list: uploadDocumentList.value
|
||||||
|
})
|
||||||
inputValue.value = ''
|
inputValue.value = ''
|
||||||
uploadFileList.value = []
|
uploadImageList.value = []
|
||||||
|
uploadDocumentList.value = []
|
||||||
quickInputRef.value.textareaStyle.height = '45px'
|
quickInputRef.value.textareaStyle.height = '45px'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,10 +22,19 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type chatType } from '@/api/type/application'
|
import { type chatType } from '@/api/type/application'
|
||||||
defineProps<{
|
import { onMounted } from 'vue'
|
||||||
|
const props = defineProps<{
|
||||||
application: any
|
application: any
|
||||||
chatRecord: chatType
|
chatRecord: chatType
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.chatRecord.execution_details?.length > 0) {
|
||||||
|
props.chatRecord.execution_details[0].image_list.forEach((image: any) => {
|
||||||
|
console.log('image', image.name, image.url)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -370,7 +370,7 @@ function chatMessage(chat?: any, problem?: string, re_chat?: boolean, other_para
|
|||||||
*/
|
*/
|
||||||
function getSourceDetail(row: any) {
|
function getSourceDetail(row: any) {
|
||||||
logApi.getRecordDetail(id || props.appId, row.chat_id, row.record_id, loading).then((res) => {
|
logApi.getRecordDetail(id || props.appId, row.chat_id, row.record_id, loading).then((res) => {
|
||||||
const exclude_keys = ['answer_text', 'id']
|
const exclude_keys = ['answer_text', 'id', 'answer_text_list']
|
||||||
Object.keys(res.data).forEach((key) => {
|
Object.keys(res.data).forEach((key) => {
|
||||||
if (!exclude_keys.includes(key)) {
|
if (!exclude_keys.includes(key)) {
|
||||||
row[key] = res.data[key]
|
row[key] = res.data[key]
|
||||||
|
|||||||
@ -72,7 +72,7 @@ const props = defineProps<{
|
|||||||
modelValue?: Array<any>
|
modelValue?: Array<any>
|
||||||
}>()
|
}>()
|
||||||
const rowTemp = ref<any>()
|
const rowTemp = ref<any>()
|
||||||
const evalF = (text: string, row: any) => {
|
const evalF: (text: string, row: any) => string = (text: string, row: any) => {
|
||||||
rowTemp.value = row
|
rowTemp.value = row
|
||||||
return eval(text)
|
return eval(text)
|
||||||
}
|
}
|
||||||
@ -167,8 +167,8 @@ watch(
|
|||||||
|
|
||||||
const activeText = computed(() => {
|
const activeText = computed(() => {
|
||||||
if (props.modelValue) {
|
if (props.modelValue) {
|
||||||
const rows = option_list.value.filter(
|
const rows = option_list.value.filter((f: any) =>
|
||||||
(f: any) => props.modelValue?.includes(f[valueField.value])
|
props.modelValue?.includes(f[valueField.value])
|
||||||
)
|
)
|
||||||
if (rows) {
|
if (rows) {
|
||||||
if (rows.length > 3) {
|
if (rows.length > 3) {
|
||||||
|
|||||||
@ -1228,6 +1228,28 @@ export const iconMap: any = {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'app-beautify': {
|
||||||
|
iconReader: () => {
|
||||||
|
return h('i', [
|
||||||
|
h(
|
||||||
|
'svg',
|
||||||
|
{
|
||||||
|
style: { height: '100%', width: '100%' },
|
||||||
|
viewBox: '0 0 1024 1024',
|
||||||
|
version: '1.1',
|
||||||
|
xmlns: 'http://www.w3.org/2000/svg'
|
||||||
|
},
|
||||||
|
[
|
||||||
|
h('path', {
|
||||||
|
d: 'M739.6864 689.92l4.2496 3.584 136.4992 135.936a34.1504 34.1504 0 0 1-43.9296 51.968l-4.1984-3.584-136.5504-135.936a34.1504 34.1504 0 0 1 43.9296-51.968zM663.4496 151.552a34.1504 34.1504 0 0 1 51.2512 30.464l-5.9392 216.6272 156.4672 146.1248a34.1504 34.1504 0 0 1-8.6528 55.808l-4.8128 1.792-202.8032 61.0816-87.4496 197.12a34.1504 34.1504 0 0 1-56.32 9.216l-3.2768-4.096-119.5008-178.432-209.9712-24.064a34.1504 34.1504 0 0 1-26.1632-50.176l2.7648-4.3008 129.28-171.7248-42.5472-212.3776a34.1504 34.1504 0 0 1 40.448-40.1408l4.6592 1.3312 198.912 72.3456z m-18.6368 89.7536l-144.5376 83.968a34.1504 34.1504 0 0 1-28.8256 2.56L314.5728 270.592l33.792 167.8848c1.4848 7.68 0.3584 15.5136-3.1744 22.3232l-3.072 4.9152-102.656 136.2944 166.4 19.1488c8.2944 0.9216 15.872 4.864 21.4016 10.9568l3.072 3.9424 93.8496 140.032 68.7104-154.7776a34.1504 34.1504 0 0 1 16.7936-17.0496l4.608-1.792 160.9216-48.4864-124.2624-116.0192a34.1504 34.1504 0 0 1-10.4448-20.0704l-0.3584-5.7856 4.6592-170.9056z',
|
||||||
|
fill: 'currentColor'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
// 'app-history-outlined': {
|
// 'app-history-outlined': {
|
||||||
// iconReader: () => {
|
// iconReader: () => {
|
||||||
// return h('i', [
|
// return h('i', [
|
||||||
|
|||||||
@ -7,12 +7,7 @@
|
|||||||
import { onMounted, nextTick, watch, onBeforeUnmount, ref } from 'vue'
|
import { onMounted, nextTick, watch, onBeforeUnmount, ref } from 'vue'
|
||||||
import * as echarts from 'echarts'
|
import * as echarts from 'echarts'
|
||||||
const tmp = ref()
|
const tmp = ref()
|
||||||
const props = defineProps({
|
const props = defineProps<{ option: string }>()
|
||||||
option: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const chartsRef = ref()
|
const chartsRef = ref()
|
||||||
|
|
||||||
const style = ref({
|
const style = ref({
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import { computed, ref } from 'vue'
|
|||||||
import DynamicsForm from '@/components/dynamics-form/index.vue'
|
import DynamicsForm from '@/components/dynamics-form/index.vue'
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
form_setting: string
|
form_setting: string
|
||||||
sendMessage: (question: string, type: 'old' | 'new', other_params_data?: any) => void
|
sendMessage?: (question: string, type: 'old' | 'new', other_params_data?: any) => void
|
||||||
}>()
|
}>()
|
||||||
const form_setting_data = computed(() => {
|
const form_setting_data = computed(() => {
|
||||||
if (props.form_setting) {
|
if (props.form_setting) {
|
||||||
@ -68,11 +68,13 @@ const submit = () => {
|
|||||||
dynamicsFormRef.value?.validate().then(() => {
|
dynamicsFormRef.value?.validate().then(() => {
|
||||||
_submit.value = true
|
_submit.value = true
|
||||||
const setting = JSON.parse(props.form_setting)
|
const setting = JSON.parse(props.form_setting)
|
||||||
|
if (props.sendMessage) {
|
||||||
props.sendMessage('', 'old', {
|
props.sendMessage('', 'old', {
|
||||||
runtime_node_id: setting.runtime_node_id,
|
runtime_node_id: setting.runtime_node_id,
|
||||||
chat_record_id: setting.chat_record_id,
|
chat_record_id: setting.chat_record_id,
|
||||||
node_data: form_data.value
|
node_data: form_data.value
|
||||||
})
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
:option="item.content"
|
:option="item.content"
|
||||||
></EchartsRander>
|
></EchartsRander>
|
||||||
<FormRander
|
<FormRander
|
||||||
:sendMessage="sendMessage"
|
:send-message="sendMessage"
|
||||||
v-else-if="item.type === 'form_rander'"
|
v-else-if="item.type === 'form_rander'"
|
||||||
:form_setting="item.content"
|
:form_setting="item.content"
|
||||||
></FormRander>
|
></FormRander>
|
||||||
|
|||||||
@ -51,7 +51,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-collapse-transition>
|
<el-collapse-transition>
|
||||||
<div @mousedown.stop @keydown.stop @click.stop v-if="showNode" class="mt-16">
|
<div @mousedown.stop @keydown.stop @click.stop v-show="showNode" class="mt-16">
|
||||||
<el-alert
|
<el-alert
|
||||||
v-if="node_status != 200"
|
v-if="node_status != 200"
|
||||||
class="mb-16"
|
class="mb-16"
|
||||||
|
|||||||
@ -11,6 +11,9 @@
|
|||||||
<el-button link @click="fitView">
|
<el-button link @click="fitView">
|
||||||
<AppIcon iconName="app-fitview" title="适应"></AppIcon>
|
<AppIcon iconName="app-fitview" title="适应"></AppIcon>
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button link @click="layout">
|
||||||
|
<AppIcon iconName="app-beautify" title="美化"></AppIcon>
|
||||||
|
</el-button>
|
||||||
</el-card>
|
</el-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -30,5 +33,8 @@ function fitView() {
|
|||||||
props.lf?.resetTranslate()
|
props.lf?.resetTranslate()
|
||||||
props.lf?.fitView()
|
props.lf?.fitView()
|
||||||
}
|
}
|
||||||
|
const layout = () => {
|
||||||
|
props.lf?.extension.dagre.layout()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|||||||
@ -137,6 +137,26 @@ class AppNode extends HtmlResize.view {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AppNodeModel extends HtmlResize.model {
|
class AppNodeModel extends HtmlResize.model {
|
||||||
|
refreshDeges() {
|
||||||
|
// 更新节点连接边的path
|
||||||
|
this.incoming.edges.forEach((edge: any) => {
|
||||||
|
// 调用自定义的更新方案
|
||||||
|
edge.updatePathByAnchor()
|
||||||
|
})
|
||||||
|
this.outgoing.edges.forEach((edge: any) => {
|
||||||
|
edge.updatePathByAnchor()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
set_position(position: { x?: number; y?: number }) {
|
||||||
|
const { x, y } = position
|
||||||
|
if (x) {
|
||||||
|
this.x = x
|
||||||
|
}
|
||||||
|
if (y) {
|
||||||
|
this.y = y
|
||||||
|
}
|
||||||
|
this.refreshDeges()
|
||||||
|
}
|
||||||
getResizeOutlineStyle() {
|
getResizeOutlineStyle() {
|
||||||
const style = super.getResizeOutlineStyle()
|
const style = super.getResizeOutlineStyle()
|
||||||
style.stroke = 'none'
|
style.stroke = 'none'
|
||||||
|
|||||||
@ -5,13 +5,14 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import LogicFlow from '@logicflow/core'
|
import LogicFlow from '@logicflow/core'
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed, nextTick } from 'vue'
|
||||||
import AppEdge from './common/edge'
|
import AppEdge from './common/edge'
|
||||||
import Control from './common/NodeControl.vue'
|
import Control from './common/NodeControl.vue'
|
||||||
import { baseNodes } from '@/workflow/common/data'
|
import { baseNodes } from '@/workflow/common/data'
|
||||||
import '@logicflow/extension/lib/style/index.css'
|
import '@logicflow/extension/lib/style/index.css'
|
||||||
import '@logicflow/core/dist/style/index.css'
|
import '@logicflow/core/dist/style/index.css'
|
||||||
import { initDefaultShortcut } from '@/workflow/common/shortcut'
|
import { initDefaultShortcut } from '@/workflow/common/shortcut'
|
||||||
|
import Dagre from '@/workflow/plugins/dagre'
|
||||||
const nodes: any = import.meta.glob('./nodes/**/index.ts', { eager: true })
|
const nodes: any = import.meta.glob('./nodes/**/index.ts', { eager: true })
|
||||||
|
|
||||||
defineOptions({ name: 'WorkFlow' })
|
defineOptions({ name: 'WorkFlow' })
|
||||||
@ -61,6 +62,7 @@ const renderGraphData = (data?: any) => {
|
|||||||
const container: any = document.querySelector('#container')
|
const container: any = document.querySelector('#container')
|
||||||
if (container) {
|
if (container) {
|
||||||
lf.value = new LogicFlow({
|
lf.value = new LogicFlow({
|
||||||
|
plugins: [Dagre],
|
||||||
textEdit: false,
|
textEdit: false,
|
||||||
adjustEdge: false,
|
adjustEdge: false,
|
||||||
adjustEdgeStartAndEnd: false,
|
adjustEdgeStartAndEnd: false,
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
label-width="auto"
|
label-width="auto"
|
||||||
ref="DatasetNodeFormRef"
|
ref="DatasetNodeFormRef"
|
||||||
>
|
>
|
||||||
<el-form-item label="选择文件" :rules="{
|
<el-form-item label="选择文档" :rules="{
|
||||||
type: 'array',
|
type: 'array',
|
||||||
required: true,
|
required: true,
|
||||||
message: '请选择文件',
|
message: '请选择文件',
|
||||||
@ -21,8 +21,8 @@
|
|||||||
ref="nodeCascaderRef"
|
ref="nodeCascaderRef"
|
||||||
:nodeModel="nodeModel"
|
:nodeModel="nodeModel"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
placeholder="请选择文件"
|
placeholder="请选择文档"
|
||||||
v-model="form.file_list"
|
v-model="form.document_list"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
@ -39,7 +39,7 @@ import NodeCascader from '@/workflow/common/NodeCascader.vue'
|
|||||||
const props = defineProps<{ nodeModel: any }>()
|
const props = defineProps<{ nodeModel: any }>()
|
||||||
|
|
||||||
const form = {
|
const form = {
|
||||||
file_list: []
|
document_list: ["start-node", "document"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
69
ui/src/workflow/plugins/dagre.ts
Normal file
69
ui/src/workflow/plugins/dagre.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { DagreLayout, type DagreLayoutOptions } from '@antv/layout'
|
||||||
|
|
||||||
|
export default class Dagre {
|
||||||
|
static pluginName = 'dagre'
|
||||||
|
lf: any
|
||||||
|
option: DagreLayoutOptions | any
|
||||||
|
render(lf: any) {
|
||||||
|
this.lf = lf
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* option: {
|
||||||
|
* rankdir: "TB", // layout 方向, 可选 TB, BT, LR, RL
|
||||||
|
* align: undefined, // 节点对齐方式,可选 UL, UR, DL, DR
|
||||||
|
* nodeSize: undefined, // 节点大小
|
||||||
|
* nodesepFunc: undefined, // 节点水平间距(px)
|
||||||
|
* ranksepFunc: undefined, // 每一层节点之间间距
|
||||||
|
* nodesep: 40, // 节点水平间距(px) 注意:如果有grid,需要保证nodesep为grid的偶数倍
|
||||||
|
* ranksep: 40, // 每一层节点之间间距 注意:如果有grid,需要保证ranksep为grid的偶数倍
|
||||||
|
* controlPoints: false, // 是否保留布局连线的控制点
|
||||||
|
* radial: false, // 是否基于 dagre 进行辐射布局
|
||||||
|
* focusNode: null, // radial 为 true 时生效,关注的节点
|
||||||
|
* };
|
||||||
|
*/
|
||||||
|
layout(option = {}) {
|
||||||
|
const { nodes, edges, gridSize } = this.lf.graphModel
|
||||||
|
// 为了保证生成的节点在girdSize上,需要处理一下。
|
||||||
|
let nodesep = 40
|
||||||
|
let ranksep = 40
|
||||||
|
if (gridSize > 20) {
|
||||||
|
nodesep = gridSize * 2
|
||||||
|
ranksep = gridSize * 2
|
||||||
|
}
|
||||||
|
this.option = {
|
||||||
|
type: 'dagre',
|
||||||
|
rankdir: 'LR',
|
||||||
|
// align: 'UL',
|
||||||
|
// align: 'UR',
|
||||||
|
align: 'DR',
|
||||||
|
nodesep,
|
||||||
|
ranksep,
|
||||||
|
begin: [120, 120],
|
||||||
|
...option
|
||||||
|
}
|
||||||
|
const layoutInstance = new DagreLayout(this.option)
|
||||||
|
const layoutData = layoutInstance.layout({
|
||||||
|
nodes: nodes.map((node: any) => ({
|
||||||
|
id: node.id,
|
||||||
|
size: {
|
||||||
|
width: node.width,
|
||||||
|
height: node.height
|
||||||
|
},
|
||||||
|
model: node
|
||||||
|
})),
|
||||||
|
edges: edges.map((edge: any) => ({
|
||||||
|
source: edge.sourceNodeId,
|
||||||
|
target: edge.targetNodeId,
|
||||||
|
model: edge
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
layoutData.nodes?.forEach((node: any) => {
|
||||||
|
// @ts-ignore: pass node data
|
||||||
|
const { model } = node
|
||||||
|
model.set_position({ x: node.x, y: node.y })
|
||||||
|
})
|
||||||
|
this.lf.fitView()
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user