This commit is contained in:
liqiang-fit2cloud 2024-12-04 14:18:56 +08:00
commit 1e1e98e155
61 changed files with 674 additions and 329 deletions

View File

@ -40,6 +40,10 @@ def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
node.context['run_time'] = time.time() - node.context['start_time'] node.context['run_time'] = time.time() - node.context['start_time']
def is_interrupt(node, step_variable: Dict, global_variable: Dict):
return node.type == 'form-node' and not node.context.get('is_submit', False)
class WorkFlowPostHandler: class WorkFlowPostHandler:
def __init__(self, chat_info, client_id, client_type): def __init__(self, chat_info, client_id, client_type):
self.chat_info = chat_info self.chat_info = chat_info
@ -57,7 +61,7 @@ class WorkFlowPostHandler:
answer_tokens = sum([row.get('answer_tokens') for row in details.values() if answer_tokens = sum([row.get('answer_tokens') for row in details.values() if
'answer_tokens' in row and row.get('answer_tokens') is not None]) 'answer_tokens' in row and row.get('answer_tokens') is not None])
answer_text_list = workflow.get_answer_text_list() answer_text_list = workflow.get_answer_text_list()
answer_text = '\n\n'.join(answer_text_list) answer_text = '\n\n'.join(answer['content'] for answer in answer_text_list)
if workflow.chat_record is not None: if workflow.chat_record is not None:
chat_record = workflow.chat_record chat_record = workflow.chat_record
chat_record.answer_text = answer_text chat_record.answer_text = answer_text
@ -91,10 +95,11 @@ class WorkFlowPostHandler:
class NodeResult: class NodeResult:
def __init__(self, node_variable: Dict, workflow_variable: Dict, def __init__(self, node_variable: Dict, workflow_variable: Dict,
_write_context=write_context): _write_context=write_context, _is_interrupt=is_interrupt):
self._write_context = _write_context self._write_context = _write_context
self.node_variable = node_variable self.node_variable = node_variable
self.workflow_variable = workflow_variable self.workflow_variable = workflow_variable
self._is_interrupt = _is_interrupt
def write_context(self, node, workflow): def write_context(self, node, workflow):
return self._write_context(self.node_variable, self.workflow_variable, node, workflow) return self._write_context(self.node_variable, self.workflow_variable, node, workflow)
@ -102,6 +107,14 @@ class NodeResult:
def is_assertion_result(self): def is_assertion_result(self):
return 'branch_id' in self.node_variable return 'branch_id' in self.node_variable
def is_interrupt_exec(self, current_node):
"""
是否中断执行
@param current_node:
@return:
"""
return self._is_interrupt(current_node, self.node_variable, self.workflow_variable)
class ReferenceAddressSerializer(serializers.Serializer): class ReferenceAddressSerializer(serializers.Serializer):
node_id = serializers.CharField(required=True, error_messages=ErrMessage.char("节点id")) node_id = serializers.CharField(required=True, error_messages=ErrMessage.char("节点id"))
@ -139,14 +152,18 @@ class INode:
pass pass
def get_answer_text(self): def get_answer_text(self):
return self.answer_text if self.answer_text is None:
return None
return {'content': self.answer_text, 'runtime_node_id': self.runtime_node_id,
'chat_record_id': self.workflow_params['chat_record_id']}
def __init__(self, node, workflow_params, workflow_manage, up_node_id_list=None): def __init__(self, node, workflow_params, workflow_manage, up_node_id_list=None,
get_node_params=lambda node: node.properties.get('node_data')):
# 当前步骤上下文,用于存储当前步骤信息 # 当前步骤上下文,用于存储当前步骤信息
self.status = 200 self.status = 200
self.err_message = '' self.err_message = ''
self.node = node self.node = node
self.node_params = node.properties.get('node_data') self.node_params = get_node_params(node)
self.workflow_params = workflow_params self.workflow_params = workflow_params
self.workflow_manage = workflow_manage self.workflow_manage = workflow_manage
self.node_params_serializer = None self.node_params_serializer = None

View File

@ -14,6 +14,8 @@ class ApplicationNodeSerializer(serializers.Serializer):
user_input_field_list = serializers.ListField(required=False, error_messages=ErrMessage.uuid("用户输入字段")) user_input_field_list = serializers.ListField(required=False, error_messages=ErrMessage.uuid("用户输入字段"))
image_list = serializers.ListField(required=False, error_messages=ErrMessage.list("图片")) image_list = serializers.ListField(required=False, error_messages=ErrMessage.list("图片"))
document_list = serializers.ListField(required=False, error_messages=ErrMessage.list("文档")) document_list = serializers.ListField(required=False, error_messages=ErrMessage.list("文档"))
child_node = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.dict("子节点"))
node_data = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.dict("表单数据"))
class IApplicationNode(INode): class IApplicationNode(INode):
@ -55,5 +57,5 @@ class IApplicationNode(INode):
message=str(question), **kwargs) message=str(question), **kwargs)
def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat, client_id, client_type, def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat, client_id, client_type,
app_document_list=None, app_image_list=None, **kwargs) -> NodeResult: app_document_list=None, app_image_list=None, child_node=None, node_data=None, **kwargs) -> NodeResult:
pass pass

View File

@ -2,19 +2,25 @@
import json import json
import time import time
import uuid import uuid
from typing import List, Dict from typing import Dict
from application.flow.i_step_node import NodeResult, INode from application.flow.i_step_node import NodeResult, INode
from application.flow.step_node.application_node.i_application_node import IApplicationNode from application.flow.step_node.application_node.i_application_node import IApplicationNode
from application.models import Chat from application.models import Chat
from common.handle.impl.response.openai_to_response import OpenaiToResponse
def string_to_uuid(input_str): def string_to_uuid(input_str):
return str(uuid.uuid5(uuid.NAMESPACE_DNS, input_str)) return str(uuid.uuid5(uuid.NAMESPACE_DNS, input_str))
def _is_interrupt_exec(node, node_variable: Dict, workflow_variable: Dict):
return node_variable.get('is_interrupt_exec', False)
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str): def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):
result = node_variable.get('result') result = node_variable.get('result')
node.context['child_node'] = node_variable['child_node']
node.context['is_interrupt_exec'] = node_variable['is_interrupt_exec']
node.context['message_tokens'] = result.get('usage', {}).get('prompt_tokens', 0) 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_tokens'] = result.get('usage', {}).get('completion_tokens', 0)
node.context['answer'] = answer node.context['answer'] = answer
@ -36,17 +42,34 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
response = node_variable.get('result') response = node_variable.get('result')
answer = '' answer = ''
usage = {} usage = {}
node_child_node = {}
is_interrupt_exec = False
for chunk in response: for chunk in response:
# 先把流转成字符串 # 先把流转成字符串
response_content = chunk.decode('utf-8')[6:] response_content = chunk.decode('utf-8')[6:]
response_content = json.loads(response_content) response_content = json.loads(response_content)
choices = response_content.get('choices') content = response_content.get('content', '')
if choices and isinstance(choices, list) and len(choices) > 0: runtime_node_id = response_content.get('runtime_node_id', '')
content = choices[0].get('delta', {}).get('content', '') chat_record_id = response_content.get('chat_record_id', '')
answer += content child_node = response_content.get('child_node')
yield content node_type = response_content.get('node_type')
real_node_id = response_content.get('real_node_id')
node_is_end = response_content.get('node_is_end', False)
if node_type == 'form-node':
is_interrupt_exec = True
answer += content
node_child_node = {'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id,
'child_node': child_node}
yield {'content': content,
'node_type': node_type,
'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id,
'child_node': child_node,
'real_node_id': real_node_id,
'node_is_end': node_is_end}
usage = response_content.get('usage', {}) usage = response_content.get('usage', {})
node_variable['result'] = {'usage': usage} node_variable['result'] = {'usage': usage}
node_variable['is_interrupt_exec'] = is_interrupt_exec
node_variable['child_node'] = node_child_node
_write_context(node_variable, workflow_variable, node, workflow, answer) _write_context(node_variable, workflow_variable, node, workflow, answer)
@ -64,6 +87,11 @@ def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wor
class BaseApplicationNode(IApplicationNode): class BaseApplicationNode(IApplicationNode):
def get_answer_text(self):
if self.answer_text is None:
return None
return {'content': self.answer_text, 'runtime_node_id': self.runtime_node_id,
'chat_record_id': self.workflow_params['chat_record_id'], 'child_node': self.context.get('child_node')}
def save_context(self, details, workflow_manage): def save_context(self, details, workflow_manage):
self.context['answer'] = details.get('answer') self.context['answer'] = details.get('answer')
@ -72,7 +100,7 @@ class BaseApplicationNode(IApplicationNode):
self.answer_text = details.get('answer') self.answer_text = details.get('answer')
def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat, client_id, client_type, def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat, client_id, client_type,
app_document_list=None, app_image_list=None, app_document_list=None, app_image_list=None, child_node=None, node_data=None,
**kwargs) -> NodeResult: **kwargs) -> NodeResult:
from application.serializers.chat_message_serializers import ChatMessageSerializer from application.serializers.chat_message_serializers import ChatMessageSerializer
# 生成嵌入应用的chat_id # 生成嵌入应用的chat_id
@ -85,6 +113,14 @@ class BaseApplicationNode(IApplicationNode):
app_document_list = [] app_document_list = []
if app_image_list is None: if app_image_list is None:
app_image_list = [] app_image_list = []
runtime_node_id = None
record_id = None
child_node_value = None
if child_node is not None:
runtime_node_id = child_node.get('runtime_node_id')
record_id = child_node.get('chat_record_id')
child_node_value = child_node.get('child_node')
response = ChatMessageSerializer( response = ChatMessageSerializer(
data={'chat_id': current_chat_id, 'message': message, data={'chat_id': current_chat_id, 'message': message,
're_chat': re_chat, 're_chat': re_chat,
@ -94,16 +130,20 @@ class BaseApplicationNode(IApplicationNode):
'client_type': client_type, 'client_type': client_type,
'document_list': app_document_list, 'document_list': app_document_list,
'image_list': app_image_list, 'image_list': app_image_list,
'form_data': kwargs}).chat(base_to_response=OpenaiToResponse()) 'runtime_node_id': runtime_node_id,
'chat_record_id': record_id,
'child_node': child_node_value,
'node_data': node_data,
'form_data': kwargs}).chat()
if response.status_code == 200: if response.status_code == 200:
if stream: if stream:
content_generator = response.streaming_content content_generator = response.streaming_content
return NodeResult({'result': content_generator, 'question': message}, {}, return NodeResult({'result': content_generator, 'question': message}, {},
_write_context=write_context_stream) _write_context=write_context_stream, _is_interrupt=_is_interrupt_exec)
else: else:
data = json.loads(response.content) data = json.loads(response.content)
return NodeResult({'result': data, 'question': message}, {}, return NodeResult({'result': data, 'question': message}, {},
_write_context=write_context) _write_context=write_context, _is_interrupt=_is_interrupt_exec)
def get_details(self, index: int, **kwargs): def get_details(self, index: int, **kwargs):
global_fields = [] global_fields = []

View File

@ -17,6 +17,7 @@ from common.util.field_message import ErrMessage
class FormNodeParamsSerializer(serializers.Serializer): class FormNodeParamsSerializer(serializers.Serializer):
form_field_list = serializers.ListField(required=True, error_messages=ErrMessage.list("表单配置")) form_field_list = serializers.ListField(required=True, error_messages=ErrMessage.list("表单配置"))
form_content_format = serializers.CharField(required=True, error_messages=ErrMessage.char('表单输出内容')) form_content_format = serializers.CharField(required=True, error_messages=ErrMessage.char('表单输出内容'))
form_data = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.dict("表单数据"))
class IFormNode(INode): class IFormNode(INode):
@ -29,5 +30,5 @@ class IFormNode(INode):
def _run(self): def _run(self):
return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data) return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)
def execute(self, form_field_list, form_content_format, **kwargs) -> NodeResult: def execute(self, form_field_list, form_content_format, form_data, **kwargs) -> NodeResult:
pass pass

View File

@ -42,7 +42,12 @@ class BaseFormNode(IFormNode):
for key in form_data: for key in form_data:
self.context[key] = form_data[key] self.context[key] = form_data[key]
def execute(self, form_field_list, form_content_format, **kwargs) -> NodeResult: def execute(self, form_field_list, form_content_format, form_data, **kwargs) -> NodeResult:
if form_data is not None:
self.context['is_submit'] = True
self.context['form_data'] = form_data
else:
self.context['is_submit'] = False
form_setting = {"form_field_list": form_field_list, "runtime_node_id": self.runtime_node_id, form_setting = {"form_field_list": form_field_list, "runtime_node_id": self.runtime_node_id,
"chat_record_id": self.flow_params_serializer.data.get("chat_record_id"), "chat_record_id": self.flow_params_serializer.data.get("chat_record_id"),
"is_submit": self.context.get("is_submit", False)} "is_submit": self.context.get("is_submit", False)}
@ -63,7 +68,8 @@ class BaseFormNode(IFormNode):
form = f'<form_rander>{json.dumps(form_setting)}</form_rander>' form = f'<form_rander>{json.dumps(form_setting)}</form_rander>'
prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2') prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2')
value = prompt_template.format(form=form) value = prompt_template.format(form=form)
return value return {'content': value, 'runtime_node_id': self.runtime_node_id,
'chat_record_id': self.workflow_params['chat_record_id']}
def get_details(self, index: int, **kwargs): def get_details(self, index: int, **kwargs):
form_content_format = self.context.get('form_content_format') form_content_format = self.context.get('form_content_format')

View File

@ -244,15 +244,15 @@ class WorkflowManage:
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, 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, child_node=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: if document_list is None:
document_list = [] document_list = []
self.start_node_id = start_node_id
self.start_node = None self.start_node = 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.document_list = document_list
@ -270,6 +270,7 @@ class WorkflowManage:
self.base_to_response = base_to_response self.base_to_response = base_to_response
self.chat_record = chat_record self.chat_record = chat_record
self.await_future_map = {} self.await_future_map = {}
self.child_node = child_node
if start_node_id is not None: if start_node_id is not None:
self.load_node(chat_record, start_node_id, start_node_data) self.load_node(chat_record, start_node_id, start_node_data)
else: else:
@ -290,11 +291,17 @@ class WorkflowManage:
for node_details in sorted(chat_record.details.values(), key=lambda d: d.get('index')): for node_details in sorted(chat_record.details.values(), key=lambda d: d.get('index')):
node_id = node_details.get('node_id') node_id = node_details.get('node_id')
if node_details.get('runtime_node_id') == start_node_id: if node_details.get('runtime_node_id') == start_node_id:
self.start_node = self.get_node_cls_by_id(node_id, node_details.get('up_node_id_list')) def get_node_params(n):
self.start_node.valid_args(self.start_node.node_params, self.start_node.workflow_params) is_result = False
self.start_node.save_context(node_details, self) if n.type == 'application-node':
node_result = NodeResult({**start_node_data, 'form_data': start_node_data, 'is_submit': True}, {}) is_result = True
self.start_node_result_future = NodeResultFuture(node_result, None) return {**n.properties.get('node_data'), 'form_data': start_node_data, 'node_data': start_node_data,
'child_node': self.child_node, 'is_result': is_result}
self.start_node = self.get_node_cls_by_id(node_id, node_details.get('up_node_id_list'),
get_node_params=get_node_params)
self.start_node.valid_args(
{**self.start_node.node_params, 'form_data': start_node_data}, self.start_node.workflow_params)
self.node_context.append(self.start_node) self.node_context.append(self.start_node)
continue continue
@ -306,7 +313,7 @@ class WorkflowManage:
def run(self): def run(self):
if self.params.get('stream'): if self.params.get('stream'):
return self.run_stream(self.start_node, self.start_node_result_future) return self.run_stream(self.start_node, None)
return self.run_block() return self.run_block()
def run_block(self): def run_block(self):
@ -352,9 +359,19 @@ class WorkflowManage:
break break
yield chunk yield chunk
finally: finally:
details = self.get_runtime_details()
message_tokens = sum([row.get('message_tokens') for row in details.values() if
'message_tokens' in row and row.get('message_tokens') is not None])
answer_tokens = sum([row.get('answer_tokens') for row in details.values() if
'answer_tokens' in row and row.get('answer_tokens') is not None])
self.work_flow_post_handler.handler(self.params['chat_id'], self.params['chat_record_id'], self.work_flow_post_handler.handler(self.params['chat_id'], self.params['chat_record_id'],
self.answer, self.answer,
self) self)
yield self.base_to_response.to_stream_chunk_response(self.params['chat_id'],
self.params['chat_record_id'],
'',
[],
'', True, message_tokens, answer_tokens, {})
def run_chain_async(self, current_node, node_result_future): def run_chain_async(self, current_node, node_result_future):
future = executor.submit(self.run_chain, current_node, node_result_future) future = executor.submit(self.run_chain, current_node, node_result_future)
@ -423,6 +440,8 @@ class WorkflowManage:
def hand_event_node_result(self, current_node, node_result_future): def hand_event_node_result(self, current_node, node_result_future):
node_chunk = NodeChunk() node_chunk = NodeChunk()
real_node_id = current_node.runtime_node_id
child_node = {}
try: try:
current_result = node_result_future.result() current_result = node_result_future.result()
result = current_result.write_context(current_node, self) result = current_result.write_context(current_node, self)
@ -430,21 +449,38 @@ class WorkflowManage:
if self.is_result(current_node, current_result): if self.is_result(current_node, current_result):
self.node_chunk_manage.add_node_chunk(node_chunk) self.node_chunk_manage.add_node_chunk(node_chunk)
for r in result: for r in result:
content = r
child_node = {}
node_is_end = False
if isinstance(r, dict):
content = r.get('content')
child_node = {'runtime_node_id': r.get('runtime_node_id'),
'chat_record_id': r.get('chat_record_id')
, 'child_node': r.get('child_node')}
real_node_id = r.get('real_node_id')
node_is_end = r.get('node_is_end')
chunk = self.base_to_response.to_stream_chunk_response(self.params['chat_id'], chunk = self.base_to_response.to_stream_chunk_response(self.params['chat_id'],
self.params['chat_record_id'], self.params['chat_record_id'],
current_node.id, current_node.id,
current_node.up_node_id_list, current_node.up_node_id_list,
r, False, 0, 0, content, False, 0, 0,
{'node_type': current_node.type, {'node_type': current_node.type,
'view_type': current_node.view_type}) 'runtime_node_id': current_node.runtime_node_id,
'view_type': current_node.view_type,
'child_node': child_node,
'node_is_end': node_is_end,
'real_node_id': real_node_id})
node_chunk.add_chunk(chunk) node_chunk.add_chunk(chunk)
chunk = self.base_to_response.to_stream_chunk_response(self.params['chat_id'], chunk = self.base_to_response.to_stream_chunk_response(self.params['chat_id'],
self.params['chat_record_id'], self.params['chat_record_id'],
current_node.id, current_node.id,
current_node.up_node_id_list, current_node.up_node_id_list,
'', False, 0, 0, {'node_is_end': True, '', False, 0, 0, {'node_is_end': True,
'runtime_node_id': current_node.runtime_node_id,
'node_type': current_node.type, 'node_type': current_node.type,
'view_type': current_node.view_type}) 'view_type': current_node.view_type,
'child_node': child_node,
'real_node_id': real_node_id})
node_chunk.end(chunk) node_chunk.end(chunk)
else: else:
list(result) list(result)
@ -461,8 +497,12 @@ class WorkflowManage:
current_node.id, current_node.id,
current_node.up_node_id_list, current_node.up_node_id_list,
str(e), False, 0, 0, str(e), False, 0, 0,
{'node_is_end': True, 'node_type': current_node.type, {'node_is_end': True,
'view_type': current_node.view_type}) 'runtime_node_id': current_node.runtime_node_id,
'node_type': current_node.type,
'view_type': current_node.view_type,
'child_node': {},
'real_node_id': real_node_id})
if not self.node_chunk_manage.contains(node_chunk): if not self.node_chunk_manage.contains(node_chunk):
self.node_chunk_manage.add_node_chunk(node_chunk) self.node_chunk_manage.add_node_chunk(node_chunk)
node_chunk.end(chunk) node_chunk.end(chunk)
@ -554,9 +594,9 @@ class WorkflowManage:
else: else:
if len(result) > 0: if len(result) > 0:
exec_index = len(result) - 1 exec_index = len(result) - 1
content = result[exec_index] content = result[exec_index]['content']
result[exec_index] += answer_text if len( result[exec_index]['content'] += answer_text['content'] if len(
content) == 0 else ('\n\n' + answer_text) content) == 0 else ('\n\n' + answer_text['content'])
else: else:
answer_text = node.get_answer_text() answer_text = node.get_answer_text()
result.insert(0, answer_text) result.insert(0, answer_text)
@ -613,8 +653,8 @@ class WorkflowManage:
@param current_node_result: 当前可执行节点结果 @param current_node_result: 当前可执行节点结果
@return: 可执行节点列表 @return: 可执行节点列表
""" """
# 判断是否中断执行
if current_node.type == 'form-node' and 'form_data' not in current_node_result.node_variable: if current_node_result.is_interrupt_exec(current_node):
return [] return []
node_list = [] node_list = []
if current_node_result is not None and current_node_result.is_assertion_result(): if current_node_result is not None and current_node_result.is_assertion_result():
@ -689,11 +729,12 @@ class WorkflowManage:
base_node_list = [node for node in self.flow.nodes if node.type == 'base-node'] base_node_list = [node for node in self.flow.nodes if node.type == 'base-node']
return base_node_list[0] return base_node_list[0]
def get_node_cls_by_id(self, node_id, up_node_id_list=None): def get_node_cls_by_id(self, node_id, up_node_id_list=None,
get_node_params=lambda node: node.properties.get('node_data')):
for node in self.flow.nodes: for node in self.flow.nodes:
if node.id == node_id: if node.id == node_id:
node_instance = get_node(node.type)(node, node_instance = get_node(node.type)(node,
self.params, self, up_node_id_list) self.params, self, up_node_id_list, get_node_params)
return node_instance return node_instance
return None return None

View File

@ -4,8 +4,8 @@ import django.contrib.postgres.fields
from django.db import migrations, models from django.db import migrations, models
sql = """ sql = """
UPDATE "public".application_chat_record UPDATE application_chat_record
SET "answer_text_list" = ARRAY[answer_text]; SET answer_text_list=ARRAY[jsonb_build_object('content',answer_text)]
""" """
@ -28,8 +28,7 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='chatrecord', model_name='chatrecord',
name='answer_text_list', name='answer_text_list',
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=40960), default=list, field=django.contrib.postgres.fields.ArrayField(base_field=models.JSONField(), default=list, size=None, verbose_name='改进标注列表')
size=None, verbose_name='改进标注列表'),
), ),
migrations.RunSQL(sql) migrations.RunSQL(sql)
] ]

View File

@ -69,7 +69,6 @@ class Application(AppModelMixin):
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=dict) file_upload_setting = models.JSONField(verbose_name="文件上传相关设置", default=dict)
@staticmethod @staticmethod
def get_default_model_prompt(): def get_default_model_prompt():
return ('已知信息:' return ('已知信息:'
@ -148,7 +147,7 @@ class ChatRecord(AppModelMixin):
problem_text = models.CharField(max_length=10240, verbose_name="问题") problem_text = models.CharField(max_length=10240, verbose_name="问题")
answer_text = models.CharField(max_length=40960, verbose_name="答案") answer_text = models.CharField(max_length=40960, verbose_name="答案")
answer_text_list = ArrayField(verbose_name="改进标注列表", answer_text_list = ArrayField(verbose_name="改进标注列表",
base_field=models.CharField(max_length=40960) base_field=models.JSONField()
, default=list) , default=list)
message_tokens = models.IntegerField(verbose_name="请求token数量", default=0) message_tokens = models.IntegerField(verbose_name="请求token数量", default=0)
answer_tokens = models.IntegerField(verbose_name="响应token数量", default=0) answer_tokens = models.IntegerField(verbose_name="响应token数量", default=0)

View File

@ -238,13 +238,14 @@ class ChatMessageSerializer(serializers.Serializer):
runtime_node_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, runtime_node_id = serializers.CharField(required=False, allow_null=True, allow_blank=True,
error_messages=ErrMessage.char("运行时节点id")) error_messages=ErrMessage.char("运行时节点id"))
node_data = serializers.DictField(required=False, error_messages=ErrMessage.char("节点参数")) node_data = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.char("节点参数"))
application_id = serializers.UUIDField(required=False, allow_null=True, error_messages=ErrMessage.uuid("应用id")) application_id = serializers.UUIDField(required=False, allow_null=True, error_messages=ErrMessage.uuid("应用id"))
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("图片")) image_list = serializers.ListField(required=False, error_messages=ErrMessage.list("图片"))
document_list = serializers.ListField(required=False, error_messages=ErrMessage.list("文档")) document_list = serializers.ListField(required=False, error_messages=ErrMessage.list("文档"))
child_node = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.dict("子节点"))
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()
@ -353,7 +354,7 @@ class ChatMessageSerializer(serializers.Serializer):
'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, document_list, base_to_response, form_data, image_list, document_list,
self.data.get('runtime_node_id'), self.data.get('runtime_node_id'),
self.data.get('node_data'), chat_record) self.data.get('node_data'), chat_record, self.data.get('child_node'))
r = work_flow_manage.run() r = work_flow_manage.run()
return r return r

View File

@ -169,7 +169,7 @@ function initMaxkbStyle(root){
position: absolute; position: absolute;
{{x_type}}: {{x_value}}px; {{x_type}}: {{x_value}}px;
{{y_type}}: {{y_value}}px; {{y_type}}: {{y_value}}px;
z-index: 1000; z-index: 10001;
} }
#maxkb .maxkb-tips { #maxkb .maxkb-tips {
position: fixed; position: fixed;
@ -180,7 +180,7 @@ function initMaxkbStyle(root){
color: #ffffff; color: #ffffff;
font-size: 14px; font-size: 14px;
background: #3370FF; background: #3370FF;
z-index: 1000; z-index: 10001;
} }
#maxkb .maxkb-tips .maxkb-arrow { #maxkb .maxkb-tips .maxkb-arrow {
position: absolute; position: absolute;

View File

@ -138,7 +138,8 @@ class ChatView(APIView):
'node_id': request.data.get('node_id', None), 'node_id': request.data.get('node_id', None),
'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', {}),
'chat_record_id': request.data.get('chat_record_id')} 'chat_record_id': request.data.get('chat_record_id'),
'child_node': request.data.get('child_node')}
).chat() ).chat()
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)

View File

@ -10,8 +10,14 @@ import setting.models
from setting.models import Model from setting.models import Model
from .listener_manage import * from .listener_manage import *
update_document_status_sql = """
UPDATE "public"."document"
SET status ="replace"("replace"("replace"(status, '1', '3'), '0', '3'), '4', '3')
"""
def run(): def run():
# QuerySet(Document).filter(status__in=[Status.embedding, Status.queue_up]).update(**{'status': Status.error}) # QuerySet(Document).filter(status__in=[Status.embedding, Status.queue_up]).update(**{'status': Status.error})
QuerySet(Model).filter(status=setting.models.Status.DOWNLOAD).update(status=setting.models.Status.ERROR, QuerySet(Model).filter(status=setting.models.Status.DOWNLOAD).update(status=setting.models.Status.ERROR,
meta={'message': "下载程序被中断,请重试"}) meta={'message': "下载程序被中断,请重试"})
update_execute(update_document_status_sql, [])

View File

@ -35,7 +35,7 @@ class OpenaiToResponse(BaseToResponse):
def to_stream_chunk_response(self, chat_id, chat_record_id, node_id, up_node_id_list, content, is_end, completion_tokens, def to_stream_chunk_response(self, chat_id, chat_record_id, node_id, up_node_id_list, content, is_end, completion_tokens,
prompt_tokens, other_params: dict = None): prompt_tokens, other_params: dict = None):
chunk = ChatCompletionChunk(id=chat_record_id, model='', object='chat.completion.chunk', chunk = ChatCompletionChunk(id=chat_record_id, model='', object='chat.completion.chunk',
created=datetime.datetime.now().second, choices=[ created=datetime.datetime.now().second,choices=[
Choice(delta=ChoiceDelta(content=content, chat_id=chat_id), finish_reason='stop' if is_end else None, Choice(delta=ChoiceDelta(content=content, chat_id=chat_id), finish_reason='stop' if is_end else None,
index=0)], index=0)],
usage=CompletionUsage(completion_tokens=completion_tokens, usage=CompletionUsage(completion_tokens=completion_tokens,

View File

@ -28,7 +28,7 @@ class SystemToResponse(BaseToResponse):
prompt_tokens, other_params: dict = None): prompt_tokens, other_params: dict = None):
if other_params is None: if other_params is None:
other_params = {} other_params = {}
chunk = json.dumps({'chat_id': str(chat_id), 'id': str(chat_record_id), 'operate': True, chunk = json.dumps({'chat_id': str(chat_id), 'chat_record_id': str(chat_record_id), 'operate': True,
'content': content, 'node_id': node_id, 'up_node_id_list': up_node_id_list, 'is_end': is_end, 'content': content, 'node_id': node_id, 'up_node_id_list': up_node_id_list, 'is_end': is_end,
'usage': {'completion_tokens': completion_tokens, 'usage': {'completion_tokens': completion_tokens,
'prompt_tokens': prompt_tokens, 'prompt_tokens': prompt_tokens,

View File

@ -142,7 +142,10 @@ class Fork:
if len(charset_list) > 0: if len(charset_list) > 0:
charset = charset_list[0] charset = charset_list[0]
if charset != encoding: if charset != encoding:
html_content = response.content.decode(charset) try:
html_content = response.content.decode(charset)
except Exception as e:
logging.getLogger("max_kb").error(f'{e}')
return BeautifulSoup(html_content, "html.parser") return BeautifulSoup(html_content, "html.parser")
return beautiful_soup return beautiful_soup

View File

@ -18,7 +18,7 @@ import openpyxl
from celery_once import AlreadyQueued from celery_once import AlreadyQueued
from django.core import validators from django.core import validators
from django.db import transaction from django.db import transaction
from django.db.models import QuerySet from django.db.models import QuerySet, Count
from django.db.models.functions import Substr, Reverse from django.db.models.functions import Substr, Reverse
from django.http import HttpResponse from django.http import HttpResponse
from drf_yasg import openapi from drf_yasg import openapi
@ -56,6 +56,7 @@ from embedding.task.embedding import embedding_by_document, delete_embedding_by_
delete_embedding_by_document, update_embedding_dataset_id, delete_embedding_by_paragraph_ids, \ delete_embedding_by_document, update_embedding_dataset_id, delete_embedding_by_paragraph_ids, \
embedding_by_document_list embedding_by_document_list
from smartdoc.conf import PROJECT_DIR from smartdoc.conf import PROJECT_DIR
from django.db import models
parse_qa_handle_list = [XlsParseQAHandle(), CsvParseQAHandle(), XlsxParseQAHandle()] parse_qa_handle_list = [XlsParseQAHandle(), CsvParseQAHandle(), XlsxParseQAHandle()]
parse_table_handle_list = [CsvSplitHandle(), XlsSplitHandle(), XlsxSplitHandle()] parse_table_handle_list = [CsvSplitHandle(), XlsSplitHandle(), XlsxSplitHandle()]
@ -442,6 +443,7 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
QuerySet(model=Paragraph).filter(document_id=document_id).delete() QuerySet(model=Paragraph).filter(document_id=document_id).delete()
# 删除问题 # 删除问题
QuerySet(model=ProblemParagraphMapping).filter(document_id=document_id).delete() QuerySet(model=ProblemParagraphMapping).filter(document_id=document_id).delete()
delete_problems_and_mappings([document_id])
# 删除向量库 # 删除向量库
delete_embedding_by_document(document_id) delete_embedding_by_document(document_id)
paragraphs = get_split_model('web.md').parse(result.content) paragraphs = get_split_model('web.md').parse(result.content)
@ -660,7 +662,7 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
# 删除段落 # 删除段落
QuerySet(model=Paragraph).filter(document_id=document_id).delete() QuerySet(model=Paragraph).filter(document_id=document_id).delete()
# 删除问题 # 删除问题
QuerySet(model=ProblemParagraphMapping).filter(document_id=document_id).delete() delete_problems_and_mappings([document_id])
# 删除向量库 # 删除向量库
delete_embedding_by_document(document_id) delete_embedding_by_document(document_id)
return True return True
@ -987,7 +989,7 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
document_id_list = instance.get("id_list") document_id_list = instance.get("id_list")
QuerySet(Document).filter(id__in=document_id_list).delete() QuerySet(Document).filter(id__in=document_id_list).delete()
QuerySet(Paragraph).filter(document_id__in=document_id_list).delete() QuerySet(Paragraph).filter(document_id__in=document_id_list).delete()
QuerySet(ProblemParagraphMapping).filter(document_id__in=document_id_list).delete() delete_problems_and_mappings(document_id_list)
# 删除向量库 # 删除向量库
delete_embedding_by_document_list(document_id_list) delete_embedding_by_document_list(document_id_list)
return True return True
@ -1086,3 +1088,18 @@ def file_to_paragraph(file, pattern_list: List, with_filter: bool, limit: int):
if split_handle.support(file, get_buffer): if split_handle.support(file, get_buffer):
return split_handle.handle(file, pattern_list, with_filter, limit, get_buffer, save_image) return split_handle.handle(file, pattern_list, with_filter, limit, get_buffer, save_image)
return default_split_handle.handle(file, pattern_list, with_filter, limit, get_buffer, save_image) return default_split_handle.handle(file, pattern_list, with_filter, limit, get_buffer, save_image)
def delete_problems_and_mappings(document_ids):
problem_paragraph_mappings = ProblemParagraphMapping.objects.filter(document_id__in=document_ids)
problem_ids = set(problem_paragraph_mappings.values_list('problem_id', flat=True))
if problem_ids:
problem_paragraph_mappings.delete()
remaining_problem_counts = ProblemParagraphMapping.objects.filter(problem_id__in=problem_ids).values(
'problem_id').annotate(count=Count('problem_id'))
remaining_problem_ids = {pc['problem_id'] for pc in remaining_problem_counts}
problem_ids_to_delete = problem_ids - remaining_problem_ids
Problem.objects.filter(id__in=problem_ids_to_delete).delete()
else:
problem_paragraph_mappings.delete()

View File

@ -11,7 +11,7 @@ from typing import Dict
from celery_once import AlreadyQueued from celery_once import AlreadyQueued
from django.db import transaction from django.db import transaction
from django.db.models import QuerySet from django.db.models import QuerySet, Count
from drf_yasg import openapi from drf_yasg import openapi
from rest_framework import serializers from rest_framework import serializers
@ -291,7 +291,7 @@ class ParagraphSerializers(ApiMixin, serializers.Serializer):
self.is_valid(raise_exception=True) self.is_valid(raise_exception=True)
paragraph_id_list = instance.get("id_list") paragraph_id_list = instance.get("id_list")
QuerySet(Paragraph).filter(id__in=paragraph_id_list).delete() QuerySet(Paragraph).filter(id__in=paragraph_id_list).delete()
QuerySet(ProblemParagraphMapping).filter(paragraph_id__in=paragraph_id_list).delete() delete_problems_and_mappings(paragraph_id_list)
update_document_char_length(self.data.get('document_id')) update_document_char_length(self.data.get('document_id'))
# 删除向量库 # 删除向量库
delete_embedding_by_paragraph_ids(paragraph_id_list) delete_embedding_by_paragraph_ids(paragraph_id_list)
@ -541,14 +541,7 @@ class ParagraphSerializers(ApiMixin, serializers.Serializer):
self.is_valid(raise_exception=True) self.is_valid(raise_exception=True)
paragraph_id = self.data.get('paragraph_id') paragraph_id = self.data.get('paragraph_id')
Paragraph.objects.filter(id=paragraph_id).delete() Paragraph.objects.filter(id=paragraph_id).delete()
delete_problems_and_mappings([paragraph_id])
problem_id = ProblemParagraphMapping.objects.filter(paragraph_id=paragraph_id).values_list('problem_id',
flat=True).first()
if problem_id is not None:
if ProblemParagraphMapping.objects.filter(problem_id=problem_id).count() == 1:
Problem.objects.filter(id=problem_id).delete()
ProblemParagraphMapping.objects.filter(paragraph_id=paragraph_id).delete()
update_document_char_length(self.data.get('document_id')) update_document_char_length(self.data.get('document_id'))
delete_embedding_by_paragraph(paragraph_id) delete_embedding_by_paragraph(paragraph_id)
@ -755,3 +748,18 @@ class ParagraphSerializers(ApiMixin, serializers.Serializer):
prompt) prompt)
except AlreadyQueued as e: except AlreadyQueued as e:
raise AppApiException(500, "任务正在执行中,请勿重复下发") raise AppApiException(500, "任务正在执行中,请勿重复下发")
def delete_problems_and_mappings(paragraph_ids):
problem_paragraph_mappings = ProblemParagraphMapping.objects.filter(paragraph_id__in=paragraph_ids)
problem_ids = set(problem_paragraph_mappings.values_list('problem_id', flat=True))
if problem_ids:
problem_paragraph_mappings.delete()
remaining_problem_counts = ProblemParagraphMapping.objects.filter(problem_id__in=problem_ids).values(
'problem_id').annotate(count=Count('problem_id'))
remaining_problem_ids = {pc['problem_id'] for pc in remaining_problem_counts}
problem_ids_to_delete = problem_ids - remaining_problem_ids
Problem.objects.filter(id__in=problem_ids_to_delete).delete()
else:
problem_paragraph_mappings.delete()

View File

@ -6,7 +6,6 @@
@date2024/8/19 14:13 @date2024/8/19 14:13
@desc: @desc:
""" """
import datetime
import logging import logging
import traceback import traceback
from typing import List from typing import List
@ -17,7 +16,7 @@ from django.db.models import QuerySet
from common.config.embedding_config import ModelManage from common.config.embedding_config import ModelManage
from common.event import ListenerManagement, UpdateProblemArgs, UpdateEmbeddingDatasetIdArgs, \ from common.event import ListenerManagement, UpdateProblemArgs, UpdateEmbeddingDatasetIdArgs, \
UpdateEmbeddingDocumentIdArgs UpdateEmbeddingDocumentIdArgs
from dataset.models import Document, Status, TaskType, State from dataset.models import Document, TaskType, State
from ops import celery_app from ops import celery_app
from setting.models import Model from setting.models import Model
from setting.models_provider import get_model from setting.models_provider import get_model

View File

@ -0,0 +1,19 @@
# Generated by Django 4.2.15 on 2024-10-15 14:49
from django.db import migrations, models
sql = """
UPDATE "public"."model"
SET "model_params_form" = '[{"attrs": {"max": 1, "min": 0.1, "step": 0.01, "precision": 2, "show-input": true, "show-input-controls": false}, "field": "temperature", "label": {"attrs": {"tooltip": "较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定"}, "label": "温度", "input_type": "TooltipLabel", "props_info": {}}, "required": true, "input_type": "Slider", "props_info": {}, "trigger_type": "OPTION_LIST", "default_value": 0.5, "relation_show_field_dict": {}, "relation_trigger_field_dict": {}}, {"attrs": {"max": 100000, "min": 1, "step": 1, "precision": 0, "show-input": true, "show-input-controls": false}, "field": "max_tokens", "label": {"attrs": {"tooltip": "指定模型可生成的最大token个数"}, "label": "输出最大Tokens", "input_type": "TooltipLabel", "props_info": {}}, "required": true, "input_type": "Slider", "props_info": {}, "trigger_type": "OPTION_LIST", "default_value": 4096, "relation_show_field_dict": {}, "relation_trigger_field_dict": {}}]'
WHERE jsonb_array_length(model_params_form)=0
"""
class Migration(migrations.Migration):
dependencies = [
('setting', '0008_modelparam'),
]
operations = [
migrations.RunSQL(sql)
]

View File

@ -79,7 +79,6 @@ class ModelSerializer(serializers.Serializer):
create_user = serializers.CharField(required=False, error_messages=ErrMessage.char("创建者")) create_user = serializers.CharField(required=False, error_messages=ErrMessage.char("创建者"))
def list(self, with_valid): def list(self, with_valid):
if with_valid: if with_valid:
self.is_valid(raise_exception=True) self.is_valid(raise_exception=True)
@ -92,7 +91,8 @@ class ModelSerializer(serializers.Serializer):
model_query_set = QuerySet(Model).filter(Q(user_id=create_user)) model_query_set = QuerySet(Model).filter(Q(user_id=create_user))
# 当前用户能查看其他人的模型,只能查看公开的 # 当前用户能查看其他人的模型,只能查看公开的
else: else:
model_query_set = QuerySet(Model).filter((Q(user_id=self.data.get('create_user')) & Q(permission_type='PUBLIC'))) model_query_set = QuerySet(Model).filter(
(Q(user_id=self.data.get('create_user')) & Q(permission_type='PUBLIC')))
else: else:
model_query_set = QuerySet(Model).filter((Q(user_id=user_id) | Q(permission_type='PUBLIC'))) model_query_set = QuerySet(Model).filter((Q(user_id=user_id) | Q(permission_type='PUBLIC')))
query_params = {} query_params = {}
@ -107,11 +107,11 @@ class ModelSerializer(serializers.Serializer):
if self.data.get('permission_type') is not None: if self.data.get('permission_type') is not None:
query_params['permission_type'] = self.data.get('permission_type') query_params['permission_type'] = self.data.get('permission_type')
return [ return [
{'id': str(model.id), 'provider': model.provider, 'name': model.name, 'model_type': model.model_type, {'id': str(model.id), 'provider': model.provider, 'name': model.name, 'model_type': model.model_type,
'model_name': model.model_name, 'status': model.status, 'meta': model.meta, 'model_name': model.model_name, 'status': model.status, 'meta': model.meta,
'permission_type': model.permission_type, 'user_id': model.user_id, 'username': model.user.username} for model in 'permission_type': model.permission_type, 'user_id': model.user_id, 'username': model.user.username}
for model in
model_query_set.filter(**query_params).order_by("-create_time")] model_query_set.filter(**query_params).order_by("-create_time")]
class Edit(serializers.Serializer): class Edit(serializers.Serializer):
@ -243,14 +243,7 @@ class ModelSerializer(serializers.Serializer):
self.is_valid(raise_exception=True) self.is_valid(raise_exception=True)
model_id = self.data.get('id') model_id = self.data.get('id')
model = QuerySet(Model).filter(id=model_id).first() model = QuerySet(Model).filter(id=model_id).first()
credential = get_model_credential(model.provider, model.model_type, model.model_name)
# 已经保存过的模型参数表单 # 已经保存过的模型参数表单
if model.model_params_form is not None and len(model.model_params_form) > 0:
return model.model_params_form
# 没有保存过的LLM类型的
if credential.get_model_params_setting_form(model.model_name) is not None:
return credential.get_model_params_setting_form(model.model_name).to_form_list()
# 其他的
return model.model_params_form return model.model_params_form
class ModelParamsForm(serializers.Serializer): class ModelParamsForm(serializers.Serializer):

View File

@ -7,6 +7,7 @@
@desc: @desc:
""" """
import os import os
import shutil
from smartdoc.const import CONFIG, PROJECT_DIR from smartdoc.const import CONFIG, PROJECT_DIR
@ -34,6 +35,11 @@ CELERY_TASK_SOFT_TIME_LIMIT = 3600
CELERY_WORKER_CANCEL_LONG_RUNNING_TASKS_ON_CONNECTION_LOSS = True CELERY_WORKER_CANCEL_LONG_RUNNING_TASKS_ON_CONNECTION_LOSS = True
CELERY_ACKS_LATE = True CELERY_ACKS_LATE = True
celery_once_path = os.path.join(celery_data_dir, "celery_once") celery_once_path = os.path.join(celery_data_dir, "celery_once")
try:
if os.path.exists(celery_once_path) and os.path.isdir(celery_once_path):
shutil.rmtree(celery_once_path)
except Exception as e:
pass
CELERY_ONCE = { CELERY_ONCE = {
'backend': 'celery_once.backends.File', 'backend': 'celery_once.backends.File',
'settings': {'location': celery_once_path} 'settings': {'location': celery_once_path}

View File

@ -4,7 +4,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "run-p --max_old_space_size=4096 type-check build-only", "build": "set NODE_OPTIONS=--max_old_space_size=4096 && run-p type-check build-only",
"preview": "vite preview", "preview": "vite preview",
"test:unit": "vitest", "test:unit": "vitest",
"build-only": "vite build", "build-only": "vite build",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -23,8 +23,9 @@ interface ApplicationFormType {
tts_type?: string tts_type?: string
} }
interface Chunk { interface Chunk {
real_node_id: string
chat_id: string chat_id: string
id: string chat_record_id: string
content: string content: string
node_id: string node_id: string
up_node_id: string up_node_id: string
@ -32,13 +33,20 @@ interface Chunk {
node_is_end: boolean node_is_end: boolean
node_type: string node_type: string
view_type: string view_type: string
runtime_node_id: string
child_node: any
} }
interface chatType { interface chatType {
id: string id: string
problem_text: string problem_text: string
answer_text: string answer_text: string
buffer: Array<String> buffer: Array<String>
answer_text_list: Array<string> answer_text_list: Array<{
content: string
chat_record_id?: string
runtime_node_id?: string
child_node?: any
}>
/** /**
* *
*/ */
@ -92,15 +100,24 @@ export class ChatRecordManage {
this.write_ed = false this.write_ed = false
this.node_list = [] this.node_list = []
} }
append_answer(chunk_answer: string, index?: number) { append_answer(
this.chat.answer_text_list[index != undefined ? index : this.chat.answer_text_list.length - 1] = chunk_answer: string,
this.chat.answer_text_list[ index?: number,
index !== undefined ? index : this.chat.answer_text_list.length - 1 chat_record_id?: string,
] runtime_node_id?: string,
? this.chat.answer_text_list[ child_node?: any
index !== undefined ? index : this.chat.answer_text_list.length - 1 ) {
] + chunk_answer const set_index = index != undefined ? index : this.chat.answer_text_list.length - 1
: chunk_answer const content = this.chat.answer_text_list[set_index]
? this.chat.answer_text_list[set_index].content + chunk_answer
: chunk_answer
this.chat.answer_text_list[set_index] = {
content: content,
chat_record_id,
runtime_node_id,
child_node
}
this.chat.answer_text = this.chat.answer_text + chunk_answer this.chat.answer_text = this.chat.answer_text + chunk_answer
} }
@ -127,14 +144,22 @@ export class ChatRecordManage {
run_node.view_type == 'single_view' || run_node.view_type == 'single_view' ||
(run_node.view_type == 'many_view' && current_up_node.view_type == 'single_view') (run_node.view_type == 'many_view' && current_up_node.view_type == 'single_view')
) { ) {
const none_index = this.chat.answer_text_list.indexOf('') const none_index = this.findIndex(
this.chat.answer_text_list,
(item) => item.content == '',
'index'
)
if (none_index > -1) { if (none_index > -1) {
answer_text_list_index = none_index answer_text_list_index = none_index
} else { } else {
answer_text_list_index = this.chat.answer_text_list.length answer_text_list_index = this.chat.answer_text_list.length
} }
} else { } else {
const none_index = this.chat.answer_text_list.indexOf('') const none_index = this.findIndex(
this.chat.answer_text_list,
(item) => item.content === '',
'index'
)
if (none_index > -1) { if (none_index > -1) {
answer_text_list_index = none_index answer_text_list_index = none_index
} else { } else {
@ -152,6 +177,19 @@ export class ChatRecordManage {
} }
return undefined return undefined
} }
findIndex<T>(array: Array<T>, find: (item: T) => boolean, type: 'last' | 'index') {
let set_index = -1
for (let index = 0; index < array.length; index++) {
const element = array[index]
if (find(element)) {
set_index = index
if (type == 'index') {
break
}
}
}
return set_index
}
closeInterval() { closeInterval() {
this.chat.write_ed = true this.chat.write_ed = true
this.write_ed = true this.write_ed = true
@ -161,7 +199,11 @@ export class ChatRecordManage {
if (this.id) { if (this.id) {
clearInterval(this.id) clearInterval(this.id)
} }
const last_index = this.chat.answer_text_list.lastIndexOf('') const last_index = this.findIndex(
this.chat.answer_text_list,
(item) => item.content == '',
'last'
)
if (last_index > 0) { if (last_index > 0) {
this.chat.answer_text_list.splice(last_index, 1) this.chat.answer_text_list.splice(last_index, 1)
} }
@ -193,19 +235,29 @@ export class ChatRecordManage {
) )
this.append_answer( this.append_answer(
(divider_content ? divider_content.splice(0).join('') : '') + context.join(''), (divider_content ? divider_content.splice(0).join('') : '') + context.join(''),
answer_text_list_index answer_text_list_index,
current_node.chat_record_id,
current_node.runtime_node_id,
current_node.child_node
) )
} else if (this.is_close) { } else if (this.is_close) {
while (true) { while (true) {
const node_info = this.get_run_node() const node_info = this.get_run_node()
if (node_info == undefined) { if (node_info == undefined) {
break break
} }
this.append_answer( this.append_answer(
(node_info.divider_content ? node_info.divider_content.splice(0).join('') : '') + (node_info.divider_content ? node_info.divider_content.splice(0).join('') : '') +
node_info.current_node.buffer.splice(0).join(''), node_info.current_node.buffer.splice(0).join(''),
node_info.answer_text_list_index node_info.answer_text_list_index,
current_node.chat_record_id,
current_node.runtime_node_id,
current_node.child_node
) )
if (node_info.current_node.buffer.length == 0) {
node_info.current_node.is_end = true
}
} }
this.closeInterval() this.closeInterval()
} else { } else {
@ -213,7 +265,10 @@ export class ChatRecordManage {
if (s !== undefined) { if (s !== undefined) {
this.append_answer( this.append_answer(
(divider_content ? divider_content.splice(0).join('') : '') + s, (divider_content ? divider_content.splice(0).join('') : '') + s,
answer_text_list_index answer_text_list_index,
current_node.chat_record_id,
current_node.runtime_node_id,
current_node.child_node
) )
} }
} }
@ -235,16 +290,18 @@ export class ChatRecordManage {
this.is_stop = false this.is_stop = false
} }
appendChunk(chunk: Chunk) { appendChunk(chunk: Chunk) {
let n = this.node_list.find( let n = this.node_list.find((item) => item.real_node_id == chunk.real_node_id)
(item) => item.node_id == chunk.node_id && item.up_node_id === chunk.up_node_id
)
if (n) { if (n) {
n.buffer.push(...chunk.content) n.buffer.push(...chunk.content)
} else { } else {
n = { n = {
buffer: [...chunk.content], buffer: [...chunk.content],
real_node_id: chunk.real_node_id,
node_id: chunk.node_id, node_id: chunk.node_id,
chat_record_id: chunk.chat_record_id,
up_node_id: chunk.up_node_id, up_node_id: chunk.up_node_id,
runtime_node_id: chunk.runtime_node_id,
child_node: chunk.child_node,
node_type: chunk.node_type, node_type: chunk.node_type,
index: this.node_list.length, index: this.node_list.length,
view_type: chunk.view_type, view_type: chunk.view_type,
@ -257,9 +314,12 @@ export class ChatRecordManage {
} }
} }
append(answer_text_block: string) { append(answer_text_block: string) {
const index =this.chat.answer_text_list.indexOf("") let set_index = this.findIndex(
this.chat.answer_text_list[index]=answer_text_block this.chat.answer_text_list,
(item) => item.content == '',
'index'
)
this.chat.answer_text_list[set_index] = { content: answer_text_block }
} }
} }

View File

@ -342,7 +342,7 @@
<template v-if="item.type === WorkflowType.FormNode"> <template v-if="item.type === WorkflowType.FormNode">
<div class="card-never border-r-4"> <div class="card-never border-r-4">
<h5 class="p-8-12"> <h5 class="p-8-12">
参数输<span style="color: #f54a45">{{ 参数输<span style="color: #f54a45">{{
item.is_submit ? '' : '(用户未提交)' item.is_submit ? '' : '(用户未提交)'
}}</span> }}</span>
</h5> </h5>

View File

@ -20,9 +20,12 @@
<el-input v-model="detail.padding_problem_text" disabled /> <el-input v-model="detail.padding_problem_text" disabled />
</el-form-item> </el-form-item>
<el-form-item label="引用分段"> <el-form-item label="引用分段">
<template v-for="(item, index) in detail.paragraph_list" :key="index"> <div v-if="detail.paragraph_list.length > 0">
<ParagraphCard :data="item" :index="index" /> <template v-for="(item, index) in detail.paragraph_list" :key="index">
</template> <ParagraphCard :data="item" :index="index" />
</template>
</div>
<span v-else> - </span>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>

View File

@ -17,7 +17,7 @@
</template> </template>
<template #footer> <template #footer>
<div class="footer-content flex-between"> <div class="footer-content flex-between">
<el-text class="flex align-center" style="width: 70%"> <el-text class="flex align-center" style="width: 50%">
<img :src="getImgUrl(data?.document_name?.trim())" alt="" width="20" class="mr-4" /> <img :src="getImgUrl(data?.document_name?.trim())" alt="" width="20" class="mr-4" />
<template v-if="meta?.source_url"> <template v-if="meta?.source_url">

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="item-content mb-16 lighter"> <div class="item-content mb-16 lighter">
<template v-for="(answer_text, index) in chatRecord.answer_text_list" :key="index"> <template v-for="(answer_text, index) in 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" />
@ -9,14 +9,18 @@
<el-card shadow="always" class="dialog-card mb-8"> <el-card shadow="always" class="dialog-card mb-8">
<MdRenderer <MdRenderer
v-if=" v-if="
(chatRecord.write_ed === undefined || chatRecord.write_ed === true) && !answer_text (chatRecord.write_ed === undefined || chatRecord.write_ed === true) &&
!answer_text.content
" "
source=" 抱歉,没有查找到相关内容,请重新描述您的问题或提供更多信息。" source=" 抱歉,没有查找到相关内容,请重新描述您的问题或提供更多信息。"
></MdRenderer> ></MdRenderer>
<MdRenderer <MdRenderer
:chat_record_id="answer_text.chat_record_id"
:child_node="answer_text.child_node"
:runtime_node_id="answer_text.runtime_node_id"
:loading="loading" :loading="loading"
v-else-if="answer_text" v-else-if="answer_text.content"
:source="answer_text" :source="answer_text.content"
:send-message="chatMessage" :send-message="chatMessage"
></MdRenderer> ></MdRenderer>
<span v-else-if="chatRecord.is_stop" shadow="always" class="dialog-card"> <span v-else-if="chatRecord.is_stop" shadow="always" class="dialog-card">
@ -36,7 +40,8 @@
<OperationButton <OperationButton
:type="type" :type="type"
:application="application" :application="application"
:chat-record="chatRecord" :chatRecord="chatRecord"
@update:chatRecord="(event: any) => emit('update:chatRecord', event)"
:loading="loading" :loading="loading"
:start-chat="startChat" :start-chat="startChat"
:stop-chat="stopChat" :stop-chat="stopChat"
@ -50,6 +55,7 @@ import KnowledgeSource from '@/components/ai-chat/KnowledgeSource.vue'
import MdRenderer from '@/components/markdown/MdRenderer.vue' import MdRenderer from '@/components/markdown/MdRenderer.vue'
import OperationButton from '@/components/ai-chat/component/operation-button/index.vue' import OperationButton from '@/components/ai-chat/component/operation-button/index.vue'
import { type chatType } from '@/api/type/application' import { type chatType } from '@/api/type/application'
import { computed } from 'vue'
const props = defineProps<{ const props = defineProps<{
chatRecord: chatType chatRecord: chatType
application: any application: any
@ -59,6 +65,8 @@ const props = defineProps<{
type: 'log' | 'ai-chat' | 'debug-ai-chat' type: 'log' | 'ai-chat' | 'debug-ai-chat'
}>() }>()
const emit = defineEmits(['update:chatRecord'])
const chatMessage = (question: string, type: 'old' | 'new', other_params_data?: any) => { const chatMessage = (question: string, type: 'old' | 'new', other_params_data?: any) => {
if (type === 'old') { if (type === 'old') {
add_answer_text_list(props.chatRecord.answer_text_list) add_answer_text_list(props.chatRecord.answer_text_list)
@ -68,9 +76,17 @@ const chatMessage = (question: string, type: 'old' | 'new', other_params_data?:
props.sendMessage(question, other_params_data) props.sendMessage(question, other_params_data)
} }
} }
const add_answer_text_list = (answer_text_list: Array<string>) => { const add_answer_text_list = (answer_text_list: Array<any>) => {
answer_text_list.push('') answer_text_list.push({ content: '' })
} }
const answer_text_list = computed(() => {
return props.chatRecord.answer_text_list.map((item) => {
if (typeof item == 'string') {
return { content: item }
}
return item
})
})
function showSource(row: any) { function showSource(row: any) {
if (props.type === 'log') { if (props.type === 'log') {

View File

@ -85,7 +85,8 @@
> >
<el-tooltip effect="dark" placement="top" popper-class="upload-tooltip-width"> <el-tooltip effect="dark" placement="top" popper-class="upload-tooltip-width">
<template #content> <template #content>
<div class="break-all pre-wrap">上传文件最多{{ <div class="break-all pre-wrap">
上传文件最多{{
props.applicationDetails.file_upload_setting.maxFiles props.applicationDetails.file_upload_setting.maxFiles
}}每个文件限制 }}每个文件限制
{{ props.applicationDetails.file_upload_setting.fileLimit }}MB<br />文件类型{{ {{ props.applicationDetails.file_upload_setting.fileLimit }}MB<br />文件类型{{
@ -93,7 +94,7 @@
}} }}
</div> </div>
</template> </template>
<el-button text> <el-button text :disabled="checkMaxFilesLimit()" class="mt-4">
<el-icon><Paperclip /></el-icon> <el-icon><Paperclip /></el-icon>
</el-button> </el-button>
</el-tooltip> </el-tooltip>
@ -223,6 +224,13 @@ const getAcceptList = () => {
return accepts.map((ext: any) => '.' + ext).join(',') return accepts.map((ext: any) => '.' + ext).join(',')
} }
const checkMaxFilesLimit = () => {
return (
props.applicationDetails.file_upload_setting.maxFiles <=
uploadImageList.value.length + uploadDocumentList.value.length
)
}
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
// //

View File

@ -27,7 +27,7 @@
</el-tooltip> </el-tooltip>
<el-divider direction="vertical" /> <el-divider direction="vertical" />
<el-tooltip <el-tooltip
v-if="data.improve_paragraph_id_list.length === 0" v-if="buttonData.improve_paragraph_id_list.length === 0"
effect="dark" effect="dark"
content="修改内容" content="修改内容"
placement="top" placement="top"
@ -59,7 +59,7 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { onMounted, ref } from 'vue'
import { copyClick } from '@/utils/clipboard' import { copyClick } from '@/utils/clipboard'
import EditContentDialog from '@/views/log/component/EditContentDialog.vue' import EditContentDialog from '@/views/log/component/EditContentDialog.vue'
import EditMarkDialog from '@/views/log/component/EditMarkDialog.vue' import EditMarkDialog from '@/views/log/component/EditMarkDialog.vue'

View File

@ -3,6 +3,7 @@
<LogOperationButton <LogOperationButton
v-if="type === 'log'" v-if="type === 'log'"
v-bind:data="chatRecord" v-bind:data="chatRecord"
@update:data="(event: any) => emit('update:chatRecord', event)"
:applicationId="application.id" :applicationId="application.id"
:tts="application.tts_model_enable" :tts="application.tts_model_enable"
:tts_type="application.tts_type" :tts_type="application.tts_type"
@ -54,5 +55,6 @@ defineProps<{
stopChat: (chat_record: any) => void stopChat: (chat_record: any) => void
regenerationChart: (chat_record: any) => void regenerationChart: (chat_record: any) => void
}>() }>()
const emit = defineEmits(['update:chatRecord'])
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@ -23,7 +23,7 @@
<AnswerContent <AnswerContent
:application="applicationDetails" :application="applicationDetails"
:loading="loading" :loading="loading"
:chat-record="item" v-model:chat-record="chatList[index]"
:type="type" :type="type"
:send-message="sendMessage" :send-message="sendMessage"
:chat-management="ChatManagement" :chat-management="ChatManagement"
@ -222,9 +222,10 @@ const getWrite = (chat: any, reader: any, stream: boolean) => {
for (const index in split) { for (const index in split) {
const chunk = JSON?.parse(split[index].replace('data:', '')) const chunk = JSON?.parse(split[index].replace('data:', ''))
chat.chat_id = chunk.chat_id chat.chat_id = chunk.chat_id
chat.record_id = chunk.id chat.record_id = chunk.chat_record_id
ChatManagement.appendChunk(chat.id, chunk) if (!chunk.is_end) {
ChatManagement.appendChunk(chat.id, chunk)
}
if (chunk.is_end) { if (chunk.is_end) {
// //
return Promise.resolve() return Promise.resolve()
@ -278,7 +279,7 @@ function chatMessage(chat?: any, problem?: string, re_chat?: boolean, other_para
id: randomId(), id: randomId(),
problem_text: problem ? problem : inputValue.value.trim(), problem_text: problem ? problem : inputValue.value.trim(),
answer_text: '', answer_text: '',
answer_text_list: [''], answer_text_list: [{ content: '' }],
buffer: [], buffer: [],
write_ed: false, write_ed: false,
is_stop: false, is_stop: false,

View File

@ -26,6 +26,7 @@
v-for="(option, $index) in formValue.option_list" v-for="(option, $index) in formValue.option_list"
:key="$index" :key="$index"
:gutter="10" :gutter="10"
class="mb-8"
> >
<el-col :span="10" <el-col :span="10"
><div class="grid-content ep-bg-purple" /> ><div class="grid-content ep-bg-purple" />

View File

@ -27,6 +27,7 @@
v-for="(option, $index) in formValue.option_list" v-for="(option, $index) in formValue.option_list"
:key="$index" :key="$index"
:gutter="10" :gutter="10"
class="mb-8"
> >
<el-col :span="10" <el-col :span="10"
><div class="grid-content ep-bg-purple" /> ><div class="grid-content ep-bg-purple" />

View File

@ -27,6 +27,7 @@
v-for="(option, $index) in formValue.option_list" v-for="(option, $index) in formValue.option_list"
:key="$index" :key="$index"
:gutter="10" :gutter="10"
class="mb-8"
> >
<el-col :span="10" <el-col :span="10"
><div class="grid-content ep-bg-purple" /> ><div class="grid-content ep-bg-purple" />
@ -93,7 +94,7 @@ const formField = computed<FormField>(() => {
}) })
const getData = () => { const getData = () => {
return { return {
input_type: 'RadioCard', input_type: 'RadioRow',
attrs: {}, attrs: {},
default_value: formValue.value.default_value, default_value: formValue.value.default_value,
text_field: 'label', text_field: 'label',

View File

@ -27,6 +27,7 @@
v-for="(option, $index) in formValue.option_list" v-for="(option, $index) in formValue.option_list"
:key="$index" :key="$index"
:gutter="10" :gutter="10"
class="mb-8"
> >
<el-col :span="10" <el-col :span="10"
><div class="grid-content ep-bg-purple" /> ><div class="grid-content ep-bg-purple" />

View File

@ -5,8 +5,8 @@
:key="item.value" :key="item.value"
class="item" class="item"
shadow="never" shadow="never"
:class="[modelValue == item[valueField] ? 'active' : '']" :class="[inputDisabled ? 'is-disabled' : '', modelValue == item[valueField] ? 'active' : '']"
@click="selected(item[valueField])" @click="inputDisabled ? () => {} : selected(item[valueField])"
> >
{{ item[textField] }} {{ item[textField] }}
</el-card> </el-card>
@ -15,6 +15,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import type { FormField } from '@/components/dynamics-form/type' import type { FormField } from '@/components/dynamics-form/type'
import { useFormDisabled } from 'element-plus'
const inputDisabled = useFormDisabled()
const props = defineProps<{ const props = defineProps<{
formValue?: any formValue?: any
formfieldList?: Array<FormField> formfieldList?: Array<FormField>
@ -24,6 +27,7 @@ const props = defineProps<{
view?: boolean view?: boolean
// //
modelValue?: any modelValue?: any
disabled?: boolean
}>() }>()
const selected = (activeValue: string | number) => { const selected = (activeValue: string | number) => {
@ -66,6 +70,16 @@ const option_list = computed(() => {
flex-wrap: wrap; flex-wrap: wrap;
justify-content: flex-start; justify-content: flex-start;
width: 100%; width: 100%;
.is-disabled {
border: 1px solid var(--el-card-border-color);
background-color: var(--el-fill-color-light);
color: var(--el-text-color-placeholder);
cursor: not-allowed;
&:hover {
cursor: not-allowed;
}
}
.active { .active {
border: 1px solid var(--el-color-primary); border: 1px solid var(--el-color-primary);
color: var(--el-color-primary); color: var(--el-color-primary);

View File

@ -4,7 +4,7 @@
v-for="item in option_list" v-for="item in option_list"
:key="item.value" :key="item.value"
class="item" class="item"
:class="[modelValue == item[valueField] ? 'active' : '']" :class="[inputDisabled ? 'is-disabled' : '', modelValue == item[valueField] ? 'active' : '']"
@click="selected(item[valueField])" @click="selected(item[valueField])"
> >
{{ item[textField] }} {{ item[textField] }}
@ -14,6 +14,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue' import { computed } from 'vue'
import type { FormField } from '@/components/dynamics-form/type' import type { FormField } from '@/components/dynamics-form/type'
import { useFormDisabled } from 'element-plus'
const inputDisabled = useFormDisabled()
const props = defineProps<{ const props = defineProps<{
formValue?: any formValue?: any
formfieldList?: Array<FormField> formfieldList?: Array<FormField>
@ -54,7 +56,15 @@ const option_list = computed(() => {
padding: 3px 4px; padding: 3px 4px;
box-sizing: border-box; box-sizing: border-box;
white-space: nowrap; white-space: nowrap;
.is-disabled {
border: 1px solid var(--el-card-border-color);
background-color: var(--el-fill-color-light);
color: var(--el-text-color-placeholder);
cursor: not-allowed;
&:hover {
cursor: not-allowed;
}
}
.active { .active {
border-radius: 4px; border-radius: 4px;
background: var(--el-color-primary-light-9); background: var(--el-color-primary-light-9);

View File

@ -10,7 +10,10 @@
v-model="form_data" v-model="form_data"
:model="form_data" :model="form_data"
></DynamicsForm> ></DynamicsForm>
<el-button :type="is_submit ? 'info' : 'primary'" :disabled="is_submit||loading" @click="submit" <el-button
:type="is_submit ? 'info' : 'primary'"
:disabled="is_submit || loading"
@click="submit"
>提交</el-button >提交</el-button
> >
</div> </div>
@ -18,13 +21,19 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import DynamicsForm from '@/components/dynamics-form/index.vue' import DynamicsForm from '@/components/dynamics-form/index.vue'
const props = withDefaults(defineProps<{ const props = withDefaults(
form_setting: string defineProps<{
loading?:boolean form_setting: string
sendMessage?: (question: string, type: 'old' | 'new', other_params_data?: any) => void loading?: boolean
}>(),{ sendMessage?: (question: string, type: 'old' | 'new', other_params_data?: any) => void
loading:false child_node?: any
}) chat_record_id?: string
runtime_node_id?: string
}>(),
{
loading: false
}
)
const form_setting_data = computed(() => { const form_setting_data = computed(() => {
if (props.form_setting) { if (props.form_setting) {
return JSON.parse(props.form_setting) return JSON.parse(props.form_setting)
@ -69,11 +78,11 @@ const dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()
const submit = () => { const submit = () => {
dynamicsFormRef.value?.validate().then(() => { dynamicsFormRef.value?.validate().then(() => {
_submit.value = true _submit.value = true
const setting = JSON.parse(props.form_setting)
if (props.sendMessage) { if (props.sendMessage) {
props.sendMessage('', 'old', { props.sendMessage('', 'old', {
runtime_node_id: setting.runtime_node_id, child_node: props.child_node,
chat_record_id: setting.chat_record_id, runtime_node_id: props.runtime_node_id,
chat_record_id: props.chat_record_id,
node_data: form_data.value node_data: form_data.value
}) })
} }

View File

@ -9,7 +9,7 @@
> >
<template #defFooters> <template #defFooters>
<el-button text type="info" @click="openDialog"> <el-button text type="info" @click="openDialog">
<AppIcon iconName="app-magnify" style="font-size: 16px"></AppIcon> <AppIcon class="color-secondary" iconName="app-magnify" style="font-size: 16px"></AppIcon>
</el-button> </el-button>
</template> </template>
</MdEditor> </MdEditor>

View File

@ -17,6 +17,9 @@
:option="item.content" :option="item.content"
></EchartsRander> ></EchartsRander>
<FormRander <FormRander
:chat_record_id="chat_record_id"
:runtime_node_id="runtime_node_id"
:child_node="child_node"
:loading="loading" :loading="loading"
:send-message="sendMessage" :send-message="sendMessage"
v-else-if="item.type === 'form_rander'" v-else-if="item.type === 'form_rander'"
@ -64,6 +67,9 @@ const props = withDefaults(
source?: string source?: string
inner_suffix?: boolean inner_suffix?: boolean
sendMessage?: (question: string, type: 'old' | 'new', other_params_data?: any) => void sendMessage?: (question: string, type: 'old' | 'new', other_params_data?: any) => void
child_node?: any
chat_record_id?: string
runtime_node_id?: string
loading?: boolean loading?: boolean
}>(), }>(),
{ {

View File

@ -65,7 +65,7 @@ export default {
jump_tip: 'Jumping to the authentication source page for authentication', jump_tip: 'Jumping to the authentication source page for authentication',
jump: 'Jump', jump: 'Jump',
oauth2: { oauth2: {
title: 'OAUTH2 Settings', title: 'OAuth2 Settings',
authEndpoint: 'Auth Endpoint', authEndpoint: 'Auth Endpoint',
authEndpointPlaceholder: 'Please enter Auth Endpoint', authEndpointPlaceholder: 'Please enter Auth Endpoint',
tokenEndpoint: 'Token Endpoint', tokenEndpoint: 'Token Endpoint',
@ -82,7 +82,7 @@ export default {
redirectUrlPlaceholder: 'Please enter Redirect URL', redirectUrlPlaceholder: 'Please enter Redirect URL',
filedMapping: 'Field Mapping', filedMapping: 'Field Mapping',
filedMappingPlaceholder: 'Please enter Field Mapping', filedMappingPlaceholder: 'Please enter Field Mapping',
enableAuthentication: 'Enable OAUTH2 Authentication', enableAuthentication: 'Enable OAuth2 Authentication',
save: 'Save', save: 'Save',
saveSuccess: 'Save Success' saveSuccess: 'Save Success'
} }

View File

@ -65,7 +65,7 @@ export default {
jump_tip: '即将跳转至认证源页面进行认证', jump_tip: '即将跳转至认证源页面进行认证',
jump: '跳转', jump: '跳转',
oauth2: { oauth2: {
title: 'OAUTH2 设置', title: 'OAuth2 设置',
authEndpoint: '授权端地址', authEndpoint: '授权端地址',
authEndpointPlaceholder: '请输入授权端地址', authEndpointPlaceholder: '请输入授权端地址',
tokenEndpoint: 'Token 端地址', tokenEndpoint: 'Token 端地址',
@ -82,7 +82,7 @@ export default {
redirectUrlPlaceholder: '请输入回调地址', redirectUrlPlaceholder: '请输入回调地址',
filedMapping: '字段映射', filedMapping: '字段映射',
filedMappingPlaceholder: '请输入字段映射', filedMappingPlaceholder: '请输入字段映射',
enableAuthentication: '启用 OAUTH2 认证', enableAuthentication: '启用 OAuth2 认证',
save: '保存', save: '保存',
saveSuccess: '保存成功' saveSuccess: '保存成功'
} }

View File

@ -256,6 +256,9 @@
} }
} }
.el-select__placeholder {
font-weight: 400;
}
.el-select__placeholder.is-transparent { .el-select__placeholder.is-transparent {
color: var(--app-input-color-placeholder); color: var(--app-input-color-placeholder);
font-weight: 400; font-weight: 400;

View File

@ -1,3 +1,6 @@
.md-editor {
font-weight: 400;
}
.md-editor-preview { .md-editor-preview {
padding: 0; padding: 0;
margin: 0; margin: 0;

View File

@ -1,7 +1,7 @@
<template> <template>
<div v-show="show" class="workflow-dropdown-menu border border-r-4"> <div v-show="show" class="workflow-dropdown-menu border border-r-4">
<el-tabs v-model="activeName" class="workflow-dropdown-tabs"> <el-tabs v-model="activeName" class="workflow-dropdown-tabs">
<div style="display: flex; width: 100%; justify-content: center"> <div style="display: flex; width: 100%; justify-content: center" class="mb-4">
<el-input v-model="search_text" style="width: 240px" placeholder="按名称搜索"> <el-input v-model="search_text" style="width: 240px" placeholder="按名称搜索">
<template #suffix> <template #suffix>
<el-icon class="el-input__icon"><search /></el-icon> <el-icon class="el-input__icon"><search /></el-icon>
@ -61,32 +61,37 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="应用" name="application"> <el-tab-pane label="应用" name="application">
<el-scrollbar height="400"> <el-scrollbar height="400">
<template v-for="(item, index) in filter_application_list" :key="index"> <div v-if="filter_application_list.length > 0">
<div <template v-for="(item, index) in filter_application_list" :key="index">
class="workflow-dropdown-item cursor flex p-8-12" <div
@click.stop="clickNodes(applicationNode, item, 'application')" class="workflow-dropdown-item cursor flex p-8-12"
@mousedown.stop="onmousedown(applicationNode, item, 'application')" @click.stop="clickNodes(applicationNode, item, 'application')"
> @mousedown.stop="onmousedown(applicationNode, item, 'application')"
<component >
:is="iconComponent(`application-node-icon`)" <component
class="mr-8 mt-4" :is="iconComponent(`application-node-icon`)"
:size="32" class="mr-8 mt-4"
:item="item" :size="32"
/> :item="item"
<div class="pre-wrap" style="width: 60%"> />
<auto-tooltip :content="item.name" style="width: 80%" class="lighter"> <div class="pre-wrap" style="width: 60%">
{{ item.name }} <auto-tooltip :content="item.name" style="width: 80%" class="lighter">
</auto-tooltip> {{ item.name }}
<el-text type="info" size="small" style="width: 80%">{{ item.desc }}</el-text> </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> </div>
<div class="status-tag" style="margin-left: auto"> </template>
<el-tag type="warning" v-if="isWorkFlow(item.type)" style="height: 22px" </div>
>高级编排</el-tag <div v-else class="ml-16 mt-8">
> <el-text type="info">没有找到相关结果</el-text>
<el-tag class="blue-tag" v-else style="height: 22px">简单配置</el-tag> </div>
</div>
</div>
</template>
</el-scrollbar> </el-scrollbar>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>

View File

@ -409,6 +409,7 @@
link link
@click="openTTSParamSettingDialog" @click="openTTSParamSettingDialog"
:disabled="!applicationForm.tts_model_id" :disabled="!applicationForm.tts_model_id"
class="mr-8"
> >
<el-icon class="mr-4"><Setting /></el-icon> <el-icon class="mr-4"><Setting /></el-icon>
设置 设置
@ -424,6 +425,7 @@
<el-radio-group <el-radio-group
v-model="applicationForm.tts_type" v-model="applicationForm.tts_type"
v-show="applicationForm.tts_model_enable" v-show="applicationForm.tts_model_enable"
class="mb-8"
> >
<el-radio value="BROWSER">浏览器播放(免费)</el-radio> <el-radio value="BROWSER">浏览器播放(免费)</el-radio>
<el-radio value="TTS">TTS模型</el-radio> <el-radio value="TTS">TTS模型</el-radio>

View File

@ -78,7 +78,7 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref, watch, onMounted } from 'vue' import { reactive, ref, onMounted } from 'vue'
import authApi from '@/api/auth-setting' import authApi from '@/api/auth-setting'
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
import { t } from '@/locales' import { t } from '@/locales'
@ -86,7 +86,7 @@ import { MsgSuccess } from '@/utils/message'
const form = ref<any>({ const form = ref<any>({
id: '', id: '',
auth_type: 'OAUTH2', auth_type: 'OAuth2',
config_data: { config_data: {
authEndpoint: '', authEndpoint: '',
tokenEndpoint: '', tokenEndpoint: '',

View File

@ -11,15 +11,15 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import LDAP from './component/LDAP.vue' import LDAP from './component/LDAP.vue'
import CAS from './component/CAS.vue' import CAS from './component/CAS.vue'
import OIDC from './component/OIDC.vue' import OIDC from './component/OIDC.vue'
import SCAN from './component/SCAN.vue' import SCAN from './component/SCAN.vue'
import OAuth2 from './component/OAuth2.vue'
import { t } from '@/locales' import { t } from '@/locales'
import useStore from '@/stores' import useStore from '@/stores'
import OAUTH2 from '@/views/authentication/component/OAUTH2.vue'
const { user } = useStore() const { user } = useStore()
const router = useRouter() const router = useRouter()
@ -43,8 +43,8 @@ const tabList = [
}, },
{ {
label: t('login.oauth2.title'), label: t('login.oauth2.title'),
name: 'OAUTH2', name: 'OAuth2',
component: OAUTH2 component: OAuth2
}, },
{ {
label: '扫码登录', label: '扫码登录',

View File

@ -1,6 +1,6 @@
<template> <template>
<el-dialog <el-dialog
title="生成关联问题" title="生成问题"
v-model="dialogVisible" v-model="dialogVisible"
width="600" width="600"
class="select-dataset-dialog" class="select-dataset-dialog"
@ -9,7 +9,7 @@
> >
<template #header="{ titleId, titleClass }"> <template #header="{ titleId, titleClass }">
<div class="my-header flex"> <div class="my-header flex">
<h4 :id="titleId" :class="titleClass">生成关联问题</h4> <h4 :id="titleId" :class="titleClass">生成问题</h4>
</div> </div>
</template> </template>
<div class="content-height"> <div class="content-height">
@ -174,7 +174,7 @@ const submitHandle = async (formEl: FormInstance) => {
prompt.save(user.userInfo?.id as string, form.value) prompt.save(user.userInfo?.id as string, form.value)
const data = { ...form.value, document_id_list: documentIdList.value } const data = { ...form.value, document_id_list: documentIdList.value }
documentApi.batchGenerateRelated(id, data).then(() => { documentApi.batchGenerateRelated(id, data).then(() => {
MsgSuccess('生成关联问题成功') MsgSuccess('生成问题成功')
emit('refresh') emit('refresh')
dialogVisible.value = false dialogVisible.value = false
}) })

View File

@ -1,7 +1,7 @@
<template> <template>
<el-row :gutter="3" v-for="status in statusTable" :key="status.type"> <div v-for="status in statusTable" :key="status.type">
<el-col :span="4">{{ taskTypeMap[status.type] }} </el-col> <span> {{ taskTypeMap[status.type] }}</span>
<el-col :span="4"> <span>
<el-text v-if="status.state === State.SUCCESS || status.state === State.REVOKED"> <el-text v-if="status.state === State.SUCCESS || status.state === State.REVOKED">
<el-icon class="success"><SuccessFilled /></el-icon> <el-icon class="success"><SuccessFilled /></el-icon>
{{ stateMap[status.state](status.type) }} {{ stateMap[status.state](status.type) }}
@ -22,23 +22,22 @@
<el-icon class="is-loading primary"><Loading /></el-icon> <el-icon class="is-loading primary"><Loading /></el-icon>
{{ stateMap[status.state](status.type) }} {{ stateMap[status.state](status.type) }}
</el-text> </el-text>
</el-col> </span>
<el-col :span="7"> <span
<span class="ml-8 lighter"
:style="{ color: [State.FAILURE, State.REVOKED].includes(status.state) ? '#F54A45' : '' }" :style="{ color: [State.FAILURE, State.REVOKED].includes(status.state) ? '#F54A45' : '' }"
> >
完成 完成
{{ {{
Object.keys(status.aggs ? status.aggs : {}) Object.keys(status.aggs ? status.aggs : {})
.filter((k) => k == State.SUCCESS) .filter((k) => k == State.SUCCESS)
.map((k) => status.aggs[k]) .map((k) => status.aggs[k])
.reduce((x: any, y: any) => x + y, 0) .reduce((x: any, y: any) => x + y, 0)
}}/{{ }}/{{
Object.values(status.aggs ? status.aggs : {}).reduce((x: any, y: any) => x + y, 0) Object.values(status.aggs ? status.aggs : {}).reduce((x: any, y: any) => x + y, 0)
}}</span }}</span
> >
</el-col> <el-text type="info" class="ml-4">
<el-col :span="9">
{{ {{
status.time status.time
? status.time[status.state == State.REVOKED ? State.REVOKED : State.PENDING]?.substring( ? status.time[status.state == State.REVOKED ? State.REVOKED : State.PENDING]?.substring(
@ -47,8 +46,8 @@
) )
: undefined : undefined
}} }}
</el-col> </el-text>
</el-row> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from 'vue'

View File

@ -26,7 +26,7 @@
向量化 向量化
</el-button> </el-button>
<el-button @click="openGenerateDialog()" :disabled="multipleSelection.length === 0"> <el-button @click="openGenerateDialog()" :disabled="multipleSelection.length === 0">
关联问题 生成问题
</el-button> </el-button>
<el-button @click="openBatchEditDocument" :disabled="multipleSelection.length === 0"> <el-button @click="openBatchEditDocument" :disabled="multipleSelection.length === 0">
设置 设置
@ -636,7 +636,7 @@ function batchGenerateRelated() {
} }
}) })
documentApi.batchGenerateRelated(id, arr, loading).then(() => { documentApi.batchGenerateRelated(id, arr, loading).then(() => {
MsgSuccess('批量关联问题成功') MsgSuccess('批量生成问题成功')
multipleTableRef.value?.clearSelection() multipleTableRef.value?.clearSelection()
}) })
} }

View File

@ -67,7 +67,13 @@
class="login-button-circle color-secondary" class="login-button-circle color-secondary"
@click="changeMode(item)" @click="changeMode(item)"
> >
<span style="font-size: 10px">{{ item }}</span> <span
:style="{
'font-size': item === 'OAUTH2' ? '8px' : '10px',
color: user.themeInfo?.theme
}"
>{{ item }}</span
>
</el-button> </el-button>
<el-button <el-button
v-if="item === 'QR_CODE' && loginMode !== item" v-if="item === 'QR_CODE' && loginMode !== item"

View File

@ -1,6 +1,6 @@
<template> <template>
<el-dialog <el-dialog
title="生成关联问题" title="生成问题"
v-model="dialogVisible" v-model="dialogVisible"
width="600" width="600"
class="select-dataset-dialog" class="select-dataset-dialog"
@ -9,7 +9,7 @@
> >
<template #header="{ titleId, titleClass }"> <template #header="{ titleId, titleClass }">
<div class="my-header flex"> <div class="my-header flex">
<h4 :id="titleId" :class="titleClass">生成关联问题</h4> <h4 :id="titleId" :class="titleClass">生成问题</h4>
</div> </div>
</template> </template>
<div class="content-height"> <div class="content-height">
@ -174,7 +174,7 @@ const submitHandle = async (formEl: FormInstance) => {
const data = { ...form.value, paragraph_id_list: paragraphIdList.value } const data = { ...form.value, paragraph_id_list: paragraphIdList.value }
paragraphApi.batchGenerateRelated(id, documentId, data).then(() => { paragraphApi.batchGenerateRelated(id, documentId, data).then(() => {
MsgSuccess('生成关联问题成功') MsgSuccess('生成问题成功')
emit('refresh') emit('refresh')
dialogVisible.value = false dialogVisible.value = false
}) })

View File

@ -123,7 +123,7 @@
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item @click="openGenerateDialog(item)"> <el-dropdown-item @click="openGenerateDialog(item)">
<el-icon><Connection /></el-icon> <el-icon><Connection /></el-icon>
生成关联问题</el-dropdown-item 生成问题</el-dropdown-item
> >
<el-dropdown-item @click="openSelectDocumentDialog(item)"> <el-dropdown-item @click="openSelectDocumentDialog(item)">
<AppIcon iconName="app-migrate"></AppIcon> <AppIcon iconName="app-migrate"></AppIcon>
@ -147,7 +147,7 @@
<div class="mul-operation border-t w-full" v-if="isBatch === true"> <div class="mul-operation border-t w-full" v-if="isBatch === true">
<el-button :disabled="multipleSelection.length === 0" @click="openGenerateDialog()"> <el-button :disabled="multipleSelection.length === 0" @click="openGenerateDialog()">
生成关联问题 生成问题
</el-button> </el-button>
<el-button :disabled="multipleSelection.length === 0" @click="openSelectDocumentDialog()"> <el-button :disabled="multipleSelection.length === 0" @click="openSelectDocumentDialog()">
迁移 迁移

View File

@ -93,6 +93,7 @@ const allChecked: any = ref({
if (val) { if (val) {
filterData.value.map((item: any) => { filterData.value.map((item: any) => {
item.operate[TeamEnum.MANAGE] = true item.operate[TeamEnum.MANAGE] = true
item.operate[TeamEnum.USE] = true
}) })
} else { } else {
filterData.value.map((item: any) => { filterData.value.map((item: any) => {
@ -113,6 +114,7 @@ const allChecked: any = ref({
} else { } else {
filterData.value.map((item: any) => { filterData.value.map((item: any) => {
item.operate[TeamEnum.USE] = false item.operate[TeamEnum.USE] = false
item.operate[TeamEnum.MANAGE] = false
}) })
} }
} }
@ -144,6 +146,11 @@ function checkedOperateChange(Name: string | number, row: any, e: boolean) {
props.data.map((item: any) => { props.data.map((item: any) => {
if (item.id === row.id) { if (item.id === row.id) {
item.operate[Name] = e item.operate[Name] = e
if (Name === TeamEnum.MANAGE && e) {
item.operate[TeamEnum.USE] = true
} else if (Name === TeamEnum.USE && !e) {
item.operate[TeamEnum.MANAGE] = false
}
} }
}) })
} }

View File

@ -112,6 +112,9 @@ const validate = () => {
} }
return Promise.resolve('') return Promise.resolve('')
} }
props.nodeModel.graphModel.eventCenter.on('refresh_incoming_node_field', () => {
getIncomingNode(props.nodeModel.id)
})
defineExpose({ validate }) defineExpose({ validate })
onMounted(() => { onMounted(() => {
options.value = getIncomingNode(props.nodeModel.id) options.value = getIncomingNode(props.nodeModel.id)

View File

@ -33,7 +33,7 @@
<div @mousemove.stop @mousedown.stop @keydown.stop @click.stop> <div @mousemove.stop @mousedown.stop @keydown.stop @click.stop>
<el-button text @click="showNode = !showNode" class="mr-4"> <el-button text @click="showNode = !showNode" class="mr-4">
<el-icon class="arrow-icon" :class="showNode ? 'rotate-180' : ''" <el-icon class="arrow-icon color-secondary" :class="showNode ? 'rotate-180' : ''"
><ArrowDownBold /> ><ArrowDownBold />
</el-icon> </el-icon>
</el-button> </el-button>

View File

@ -12,17 +12,17 @@
<el-divider direction="vertical" /> <el-divider direction="vertical" />
<el-button link @click="retract"> <el-button link @click="retract">
<el-tooltip class="box-item" effect="dark" content="收起全部节点" placement="top"> <el-tooltip class="box-item" effect="dark" content="收起全部节点" placement="top">
<AppIcon iconName="app-retract" title="收起全部节点"></AppIcon> <AppIcon style="font-size: 16px" iconName="app-retract" title="收起全部节点"></AppIcon>
</el-tooltip> </el-tooltip>
</el-button> </el-button>
<el-button link @click="extend"> <el-button link @click="extend">
<el-tooltip class="box-item" effect="dark" content="展开全部节点" placement="top"> <el-tooltip class="box-item" effect="dark" content="展开全部节点" placement="top">
<AppIcon iconName="app-extend" title="展开全部节点"></AppIcon> <AppIcon style="font-size: 16px" iconName="app-extend" title="展开全部节点"></AppIcon>
</el-tooltip> </el-tooltip>
</el-button> </el-button>
<el-button link @click="layout"> <el-button link @click="layout">
<el-tooltip class="box-item" effect="dark" content="一键美化" placement="top"> <el-tooltip class="box-item" effect="dark" content="一键美化" placement="top">
<AppIcon iconName="app-beautify" title="一键美化"></AppIcon> <AppIcon style="font-size: 16px" iconName="app-beautify" title="一键美化"></AppIcon>
</el-tooltip> </el-tooltip>
</el-button> </el-button>
</el-card> </el-card>

View File

@ -49,7 +49,7 @@
<el-table-column prop="default_value" label="默认值"> <el-table-column prop="default_value" label="默认值">
<template #default="{ row }"> <template #default="{ row }">
<span :title="row.default_value" class="ellipsis-1">{{ row.default_value }}</span> <span :title="row.default_value" class="ellipsis-1">{{ getDefaultValue(row) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="必填"> <el-table-column label="必填">
@ -126,6 +126,18 @@ function refreshFieldList(data: any, index: any) {
props.nodeModel.graphModel.eventCenter.emit('refreshFieldList') props.nodeModel.graphModel.eventCenter.emit('refreshFieldList')
} }
const getDefaultValue = (row: any) => {
if (row.default_value) {
const default_value = row.option_list?.filter((v: any) => row.default_value.indexOf(v.value) > -1)
.map((v: any) => v.label).join(',')
if (default_value) {
return default_value
}
return row.default_value
}
}
onMounted(() => { onMounted(() => {
if (!props.nodeModel.properties.user_input_field_list) { if (!props.nodeModel.properties.user_input_field_list) {
if (props.nodeModel.properties.input_field_list) { if (props.nodeModel.properties.input_field_list) {

View File

@ -1,122 +1,121 @@
<template> <template>
<NodeContainer :nodeModel="nodeModel"> <NodeContainer :nodeModel="nodeModel">
<el-form <h5 class="title-decoration-1 mb-8">节点设置</h5>
@submit.prevent <el-card shadow="never" class="card-never" style="--el-card-padding: 12px">
:model="form_data" <el-form
label-position="top" @submit.prevent
require-asterisk-position="right" :model="form_data"
label-width="auto" label-position="top"
ref="formNodeFormRef" require-asterisk-position="right"
hide-required-asterisk label-width="auto"
> ref="formNodeFormRef"
<el-form-item hide-required-asterisk
label="表单输出内容"
prop="form_content_format"
:rules="{
required: true,
message: '请表单输出内容',
trigger: 'blur'
}"
> >
<template #label> <el-form-item
<div class="flex align-center"> label="表单输出内容"
<div class="mr-4"> prop="form_content_format"
<span>表单输出内容<span class="danger">*</span></span> :rules="{
</div> required: true,
<el-tooltip effect="dark" placement="right" popper-class="max-w-200"> message: '请表单输出内容',
<template #content> trigger: 'blur'
设置执行该节点输出的内容{{ '{ form }' }}为表单的占位符 }"
</template>
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
</el-tooltip>
</div>
</template>
<MdEditorMagnify
title="表单输出内容"
v-model="form_data.form_content_format"
style="height: 150px"
@submitDialog="submitDialog"
/>
</el-form-item>
<el-form-item label="表单配置" @click.prevent>
<template #label>
<div class="flex-between mb-16">
<h5 class="lighter">{{ '表单配置' }}</h5>
<el-button link type="primary" @click="openAddFormCollect()">
<el-icon class="mr-4">
<Plus />
</el-icon>
添加
</el-button>
</div></template
> >
<template #label>
<el-table <div class="flex align-center">
v-if="form_data.form_field_list.length > 0" <div class="mr-4">
:data="form_data.form_field_list" <span>表单输出内容<span class="danger">*</span></span>
class="mb-16"
>
<el-table-column prop="field" label="参数">
<template #default="{ row }">
<span :title="row.field" class="ellipsis-1">{{ row.field }}</span>
</template>
</el-table-column>
<el-table-column prop="label" label="显示名称">
<template #default="{ row }">
<span v-if="row.label && row.label.input_type === 'TooltipLabel'">
<span :title="row.label.label" class="ellipsis-1">
{{ row.label.label }}
</span>
</span>
<span v-else>
<span :title="row.label" class="ellipsis-1">
{{ row.label }}
</span></span
>
</template>
</el-table-column>
<el-table-column label="组件类型" width="110px">
<template #default="{ row }">
<el-tag type="info" class="info-tag">{{
input_type_list.find((item) => item.value === row.input_type)?.label
}}</el-tag>
</template>
</el-table-column>
<el-table-column prop="default_value" label="默认值">
<template #default="{ row }">
<span :title="row.default_value" class="ellipsis-1">{{ row.default_value }}</span>
</template>
</el-table-column>
<el-table-column label="必填">
<template #default="{ row }">
<div @click.stop>
<el-switch disabled size="small" v-model="row.required" />
</div> </div>
</template> <el-tooltip effect="dark" placement="right" popper-class="max-w-200">
</el-table-column> <template #content>
<el-table-column label="操作" align="left" width="80"> 设置执行该节点输出的内容{{ '{ form }' }}为表单的占位符
<template #default="{ row, $index }"> </template>
<span class="mr-4"> <AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
<el-tooltip effect="dark" content="修改" placement="top"> </el-tooltip>
<el-button type="primary" text @click.stop="openEditFormCollect(row, $index)"> </div>
<el-icon><EditPen /></el-icon> </template>
<MdEditorMagnify
title="表单输出内容"
v-model="form_data.form_content_format"
style="height: 150px"
@submitDialog="submitDialog"
/>
</el-form-item>
<el-form-item label="表单配置" @click.prevent>
<template #label>
<div class="flex-between">
<h5 class="lighter">{{ '表单配置' }}</h5>
<el-button link type="primary" @click="openAddFormCollect()">
<el-icon class="mr-4">
<Plus />
</el-icon>
添加
</el-button>
</div></template
>
<el-table class="border" v-if="form_data.form_field_list.length > 0" :data="form_data.form_field_list">
<el-table-column prop="field" label="参数">
<template #default="{ row }">
<span :title="row.field" class="ellipsis-1">{{ row.field }}</span>
</template>
</el-table-column>
<el-table-column prop="label" label="显示名称">
<template #default="{ row }">
<span v-if="row.label && row.label.input_type === 'TooltipLabel'">
<span :title="row.label.label" class="ellipsis-1">
{{ row.label.label }}
</span>
</span>
<span v-else>
<span :title="row.label" class="ellipsis-1">
{{ row.label }}
</span></span
>
</template>
</el-table-column>
<el-table-column label="组件类型" width="110px">
<template #default="{ row }">
<el-tag type="info" class="info-tag">{{
input_type_list.find((item) => item.value === row.input_type)?.label
}}</el-tag>
</template>
</el-table-column>
<el-table-column prop="default_value" label="默认值">
<template #default="{ row }">
<span :title="row.default_value" class="ellipsis-1">{{ getDefaultValue(row) }}</span>
</template>
</el-table-column>
<el-table-column label="必填">
<template #default="{ row }">
<div @click.stop>
<el-switch disabled size="small" v-model="row.required" />
</div>
</template>
</el-table-column>
<el-table-column label="操作" align="left" width="80">
<template #default="{ row, $index }">
<span class="mr-4">
<el-tooltip effect="dark" content="修改" placement="top">
<el-button type="primary" text @click.stop="openEditFormCollect(row, $index)">
<el-icon><EditPen /></el-icon>
</el-button>
</el-tooltip>
</span>
<el-tooltip effect="dark" content="删除" placement="top">
<el-button type="primary" text @click="deleteField(row)">
<el-icon>
<Delete />
</el-icon>
</el-button> </el-button>
</el-tooltip> </el-tooltip>
</span> </template>
<el-tooltip effect="dark" content="删除" placement="top"> </el-table-column>
<el-button type="primary" text @click="deleteField(row)"> </el-table>
<el-icon> </el-form-item>
<Delete /> </el-form>
</el-icon> </el-card>
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</el-form-item>
</el-form>
<AddFormCollect ref="addFormCollectRef" :addFormField="addFormField"></AddFormCollect> <AddFormCollect ref="addFormCollectRef" :addFormField="addFormField"></AddFormCollect>
<EditFormCollect ref="editFormCollectRef" :editFormField="editFormField"></EditFormCollect> <EditFormCollect ref="editFormCollectRef" :editFormField="editFormField"></EditFormCollect>
</NodeContainer> </NodeContainer>
@ -169,7 +168,7 @@ const openAddFormCollect = () => {
addFormCollectRef.value?.open() addFormCollectRef.value?.open()
} }
const openEditFormCollect = (form_field_data: any, index: number) => { const openEditFormCollect = (form_field_data: any, index: number) => {
editFormCollectRef.value?.open(form_field_data, index) editFormCollectRef.value?.open(cloneDeep(form_field_data), index)
} }
const deleteField = (form_field_data: any) => { const deleteField = (form_field_data: any) => {
form_data.value.form_field_list = form_data.value.form_field_list.filter( form_data.value.form_field_list = form_data.value.form_field_list.filter(
@ -197,6 +196,20 @@ const form_data = computed({
set(props.nodeModel.properties, 'node_data', value) set(props.nodeModel.properties, 'node_data', value)
} }
}) })
const getDefaultValue = (row: any) => {
if (row.default_value) {
const default_value = row.option_list
?.filter((v: any) => row.default_value.indexOf(v.value) > -1)
.map((v: any) => v.label)
.join(',')
if (default_value) {
return default_value
}
return row.default_value
}
}
const validate = () => { const validate = () => {
return formNodeFormRef.value?.validate() return formNodeFormRef.value?.validate()
} }
@ -206,6 +219,7 @@ function submitDialog(val: string) {
onMounted(() => { onMounted(() => {
set(props.nodeModel, 'validate', validate) set(props.nodeModel, 'validate', validate)
sync_form_field_list() sync_form_field_list()
props.nodeModel.graphModel.eventCenter.emit('refresh_incoming_node_field')
}) })
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>