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']
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:
def __init__(self, chat_info, client_id, client_type):
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' in row and row.get('answer_tokens') is not None])
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:
chat_record = workflow.chat_record
chat_record.answer_text = answer_text
@ -91,10 +95,11 @@ class WorkFlowPostHandler:
class NodeResult:
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.node_variable = node_variable
self.workflow_variable = workflow_variable
self._is_interrupt = _is_interrupt
def write_context(self, 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):
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):
node_id = serializers.CharField(required=True, error_messages=ErrMessage.char("节点id"))
@ -139,14 +152,18 @@ class INode:
pass
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.err_message = ''
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_manage = workflow_manage
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("用户输入字段"))
image_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):
@ -55,5 +57,5 @@ class IApplicationNode(INode):
message=str(question), **kwargs)
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

View File

@ -2,19 +2,25 @@
import json
import time
import uuid
from typing import List, Dict
from typing import Dict
from application.flow.i_step_node import NodeResult, INode
from application.flow.step_node.application_node.i_application_node import IApplicationNode
from application.models import Chat
from common.handle.impl.response.openai_to_response import OpenaiToResponse
def string_to_uuid(input_str):
return str(uuid.uuid5(uuid.NAMESPACE_DNS, input_str))
def _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):
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['answer_tokens'] = result.get('usage', {}).get('completion_tokens', 0)
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')
answer = ''
usage = {}
node_child_node = {}
is_interrupt_exec = False
for chunk in response:
# 先把流转成字符串
response_content = chunk.decode('utf-8')[6:]
response_content = json.loads(response_content)
choices = response_content.get('choices')
if choices and isinstance(choices, list) and len(choices) > 0:
content = choices[0].get('delta', {}).get('content', '')
answer += content
yield content
content = response_content.get('content', '')
runtime_node_id = response_content.get('runtime_node_id', '')
chat_record_id = response_content.get('chat_record_id', '')
child_node = response_content.get('child_node')
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', {})
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)
@ -64,6 +87,11 @@ def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wor
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):
self.context['answer'] = details.get('answer')
@ -72,7 +100,7 @@ class BaseApplicationNode(IApplicationNode):
self.answer_text = details.get('answer')
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:
from application.serializers.chat_message_serializers import ChatMessageSerializer
# 生成嵌入应用的chat_id
@ -85,6 +113,14 @@ class BaseApplicationNode(IApplicationNode):
app_document_list = []
if app_image_list is None:
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(
data={'chat_id': current_chat_id, 'message': message,
're_chat': re_chat,
@ -94,16 +130,20 @@ class BaseApplicationNode(IApplicationNode):
'client_type': client_type,
'document_list': app_document_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 stream:
content_generator = response.streaming_content
return NodeResult({'result': content_generator, 'question': message}, {},
_write_context=write_context_stream)
_write_context=write_context_stream, _is_interrupt=_is_interrupt_exec)
else:
data = json.loads(response.content)
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):
global_fields = []

View File

@ -17,6 +17,7 @@ from common.util.field_message import ErrMessage
class FormNodeParamsSerializer(serializers.Serializer):
form_field_list = serializers.ListField(required=True, error_messages=ErrMessage.list("表单配置"))
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):
@ -29,5 +30,5 @@ class IFormNode(INode):
def _run(self):
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

View File

@ -42,7 +42,12 @@ class BaseFormNode(IFormNode):
for key in form_data:
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,
"chat_record_id": self.flow_params_serializer.data.get("chat_record_id"),
"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>'
prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2')
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):
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,
document_list=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:
form_data = {}
if image_list is None:
image_list = []
if document_list is None:
document_list = []
self.start_node_id = start_node_id
self.start_node = None
self.start_node_result_future = None
self.form_data = form_data
self.image_list = image_list
self.document_list = document_list
@ -270,6 +270,7 @@ class WorkflowManage:
self.base_to_response = base_to_response
self.chat_record = chat_record
self.await_future_map = {}
self.child_node = child_node
if start_node_id is not None:
self.load_node(chat_record, start_node_id, start_node_data)
else:
@ -290,11 +291,17 @@ class WorkflowManage:
for node_details in sorted(chat_record.details.values(), key=lambda d: d.get('index')):
node_id = node_details.get('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'))
self.start_node.valid_args(self.start_node.node_params, self.start_node.workflow_params)
self.start_node.save_context(node_details, self)
node_result = NodeResult({**start_node_data, 'form_data': start_node_data, 'is_submit': True}, {})
self.start_node_result_future = NodeResultFuture(node_result, None)
def get_node_params(n):
is_result = False
if n.type == 'application-node':
is_result = True
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)
continue
@ -306,7 +313,7 @@ class WorkflowManage:
def run(self):
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()
def run_block(self):
@ -352,9 +359,19 @@ class WorkflowManage:
break
yield chunk
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.answer,
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):
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):
node_chunk = NodeChunk()
real_node_id = current_node.runtime_node_id
child_node = {}
try:
current_result = node_result_future.result()
result = current_result.write_context(current_node, self)
@ -430,21 +449,38 @@ class WorkflowManage:
if self.is_result(current_node, current_result):
self.node_chunk_manage.add_node_chunk(node_chunk)
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'],
self.params['chat_record_id'],
current_node.id,
current_node.up_node_id_list,
r, False, 0, 0,
content, False, 0, 0,
{'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)
chunk = self.base_to_response.to_stream_chunk_response(self.params['chat_id'],
self.params['chat_record_id'],
current_node.id,
current_node.up_node_id_list,
'', False, 0, 0, {'node_is_end': True,
'runtime_node_id': current_node.runtime_node_id,
'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)
else:
list(result)
@ -461,8 +497,12 @@ class WorkflowManage:
current_node.id,
current_node.up_node_id_list,
str(e), False, 0, 0,
{'node_is_end': True, 'node_type': current_node.type,
'view_type': current_node.view_type})
{'node_is_end': True,
'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):
self.node_chunk_manage.add_node_chunk(node_chunk)
node_chunk.end(chunk)
@ -554,9 +594,9 @@ class WorkflowManage:
else:
if len(result) > 0:
exec_index = len(result) - 1
content = result[exec_index]
result[exec_index] += answer_text if len(
content) == 0 else ('\n\n' + answer_text)
content = result[exec_index]['content']
result[exec_index]['content'] += answer_text['content'] if len(
content) == 0 else ('\n\n' + answer_text['content'])
else:
answer_text = node.get_answer_text()
result.insert(0, answer_text)
@ -613,8 +653,8 @@ class WorkflowManage:
@param current_node_result: 当前可执行节点结果
@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 []
node_list = []
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']
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:
if node.id == node_id:
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 None

View File

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

View File

@ -69,7 +69,6 @@ class Application(AppModelMixin):
file_upload_enable = models.BooleanField(verbose_name="文件上传是否启用", default=False)
file_upload_setting = models.JSONField(verbose_name="文件上传相关设置", default=dict)
@staticmethod
def get_default_model_prompt():
return ('已知信息:'
@ -148,7 +147,7 @@ class ChatRecord(AppModelMixin):
problem_text = models.CharField(max_length=10240, verbose_name="问题")
answer_text = models.CharField(max_length=40960, verbose_name="答案")
answer_text_list = ArrayField(verbose_name="改进标注列表",
base_field=models.CharField(max_length=40960)
base_field=models.JSONField()
, default=list)
message_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,
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"))
client_id = serializers.CharField(required=True, error_messages=ErrMessage.char("客户端id"))
client_type = serializers.CharField(required=True, error_messages=ErrMessage.char("客户端类型"))
form_data = serializers.DictField(required=False, error_messages=ErrMessage.char("全局变量"))
image_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):
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),
base_to_response, form_data, image_list, document_list,
self.data.get('runtime_node_id'),
self.data.get('node_data'), chat_record)
self.data.get('node_data'), chat_record, self.data.get('child_node'))
r = work_flow_manage.run()
return r

View File

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

View File

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

View File

@ -10,8 +10,14 @@ import setting.models
from setting.models import Model
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():
# 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,
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,
prompt_tokens, other_params: dict = None):
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,
index=0)],
usage=CompletionUsage(completion_tokens=completion_tokens,

View File

@ -28,7 +28,7 @@ class SystemToResponse(BaseToResponse):
prompt_tokens, other_params: dict = None):
if other_params is None:
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,
'usage': {'completion_tokens': completion_tokens,
'prompt_tokens': prompt_tokens,

View File

@ -142,7 +142,10 @@ class Fork:
if len(charset_list) > 0:
charset = charset_list[0]
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 beautiful_soup

View File

@ -18,7 +18,7 @@ import openpyxl
from celery_once import AlreadyQueued
from django.core import validators
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.http import HttpResponse
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, \
embedding_by_document_list
from smartdoc.conf import PROJECT_DIR
from django.db import models
parse_qa_handle_list = [XlsParseQAHandle(), CsvParseQAHandle(), XlsxParseQAHandle()]
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=ProblemParagraphMapping).filter(document_id=document_id).delete()
delete_problems_and_mappings([document_id])
# 删除向量库
delete_embedding_by_document(document_id)
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=ProblemParagraphMapping).filter(document_id=document_id).delete()
delete_problems_and_mappings([document_id])
# 删除向量库
delete_embedding_by_document(document_id)
return True
@ -987,7 +989,7 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
document_id_list = instance.get("id_list")
QuerySet(Document).filter(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)
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):
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)
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 django.db import transaction
from django.db.models import QuerySet
from django.db.models import QuerySet, Count
from drf_yasg import openapi
from rest_framework import serializers
@ -291,7 +291,7 @@ class ParagraphSerializers(ApiMixin, serializers.Serializer):
self.is_valid(raise_exception=True)
paragraph_id_list = instance.get("id_list")
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'))
# 删除向量库
delete_embedding_by_paragraph_ids(paragraph_id_list)
@ -541,14 +541,7 @@ class ParagraphSerializers(ApiMixin, serializers.Serializer):
self.is_valid(raise_exception=True)
paragraph_id = self.data.get('paragraph_id')
Paragraph.objects.filter(id=paragraph_id).delete()
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()
delete_problems_and_mappings([paragraph_id])
update_document_char_length(self.data.get('document_id'))
delete_embedding_by_paragraph(paragraph_id)
@ -755,3 +748,18 @@ class ParagraphSerializers(ApiMixin, serializers.Serializer):
prompt)
except AlreadyQueued as e:
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
@desc:
"""
import datetime
import logging
import traceback
from typing import List
@ -17,7 +16,7 @@ from django.db.models import QuerySet
from common.config.embedding_config import ModelManage
from common.event import ListenerManagement, UpdateProblemArgs, UpdateEmbeddingDatasetIdArgs, \
UpdateEmbeddingDocumentIdArgs
from dataset.models import Document, Status, TaskType, State
from dataset.models import Document, TaskType, State
from ops import celery_app
from setting.models import 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("创建者"))
def list(self, with_valid):
if with_valid:
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))
# 当前用户能查看其他人的模型,只能查看公开的
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:
model_query_set = QuerySet(Model).filter((Q(user_id=user_id) | Q(permission_type='PUBLIC')))
query_params = {}
@ -107,11 +107,11 @@ class ModelSerializer(serializers.Serializer):
if self.data.get('permission_type') is not None:
query_params['permission_type'] = self.data.get('permission_type')
return [
{'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,
'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")]
class Edit(serializers.Serializer):
@ -243,14 +243,7 @@ class ModelSerializer(serializers.Serializer):
self.is_valid(raise_exception=True)
model_id = self.data.get('id')
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
class ModelParamsForm(serializers.Serializer):

View File

@ -7,6 +7,7 @@
@desc:
"""
import os
import shutil
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_ACKS_LATE = True
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 = {
'backend': 'celery_once.backends.File',
'settings': {'location': celery_once_path}

View File

@ -4,7 +4,7 @@
"private": true,
"scripts": {
"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",
"test:unit": "vitest",
"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
}
interface Chunk {
real_node_id: string
chat_id: string
id: string
chat_record_id: string
content: string
node_id: string
up_node_id: string
@ -32,13 +33,20 @@ interface Chunk {
node_is_end: boolean
node_type: string
view_type: string
runtime_node_id: string
child_node: any
}
interface chatType {
id: string
problem_text: string
answer_text: 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.node_list = []
}
append_answer(chunk_answer: string, index?: number) {
this.chat.answer_text_list[index != undefined ? index : this.chat.answer_text_list.length - 1] =
this.chat.answer_text_list[
index !== undefined ? index : this.chat.answer_text_list.length - 1
]
? this.chat.answer_text_list[
index !== undefined ? index : this.chat.answer_text_list.length - 1
] + chunk_answer
: chunk_answer
append_answer(
chunk_answer: string,
index?: number,
chat_record_id?: string,
runtime_node_id?: string,
child_node?: any
) {
const set_index = index != undefined ? index : this.chat.answer_text_list.length - 1
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
}
@ -127,14 +144,22 @@ export class ChatRecordManage {
run_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) {
answer_text_list_index = none_index
} else {
answer_text_list_index = this.chat.answer_text_list.length
}
} 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) {
answer_text_list_index = none_index
} else {
@ -152,6 +177,19 @@ export class ChatRecordManage {
}
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() {
this.chat.write_ed = true
this.write_ed = true
@ -161,7 +199,11 @@ export class ChatRecordManage {
if (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) {
this.chat.answer_text_list.splice(last_index, 1)
}
@ -193,19 +235,29 @@ export class ChatRecordManage {
)
this.append_answer(
(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) {
while (true) {
const node_info = this.get_run_node()
if (node_info == undefined) {
break
}
this.append_answer(
(node_info.divider_content ? node_info.divider_content.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()
} else {
@ -213,7 +265,10 @@ export class ChatRecordManage {
if (s !== undefined) {
this.append_answer(
(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
}
appendChunk(chunk: Chunk) {
let n = this.node_list.find(
(item) => item.node_id == chunk.node_id && item.up_node_id === chunk.up_node_id
)
let n = this.node_list.find((item) => item.real_node_id == chunk.real_node_id)
if (n) {
n.buffer.push(...chunk.content)
} else {
n = {
buffer: [...chunk.content],
real_node_id: chunk.real_node_id,
node_id: chunk.node_id,
chat_record_id: chunk.chat_record_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,
index: this.node_list.length,
view_type: chunk.view_type,
@ -257,9 +314,12 @@ export class ChatRecordManage {
}
}
append(answer_text_block: string) {
const index =this.chat.answer_text_list.indexOf("")
this.chat.answer_text_list[index]=answer_text_block
let set_index = this.findIndex(
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">
<div class="card-never border-r-4">
<h5 class="p-8-12">
参数输<span style="color: #f54a45">{{
参数输<span style="color: #f54a45">{{
item.is_submit ? '' : '(用户未提交)'
}}</span>
</h5>

View File

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

View File

@ -17,7 +17,7 @@
</template>
<template #footer>
<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" />
<template v-if="meta?.source_url">

View File

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

View File

@ -85,7 +85,8 @@
>
<el-tooltip effect="dark" placement="top" popper-class="upload-tooltip-width">
<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.fileLimit }}MB<br />文件类型{{
@ -93,7 +94,7 @@
}}
</div>
</template>
<el-button text>
<el-button text :disabled="checkMaxFilesLimit()" class="mt-4">
<el-icon><Paperclip /></el-icon>
</el-button>
</el-tooltip>
@ -223,6 +224,13 @@ const getAcceptList = () => {
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 { maxFiles, fileLimit } = props.applicationDetails.file_upload_setting
//

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@
>
<template #defFooters>
<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>
</template>
</MdEditor>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -67,7 +67,13 @@
class="login-button-circle color-secondary"
@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
v-if="item === 'QR_CODE' && loginMode !== item"

View File

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

View File

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

View File

@ -93,6 +93,7 @@ const allChecked: any = ref({
if (val) {
filterData.value.map((item: any) => {
item.operate[TeamEnum.MANAGE] = true
item.operate[TeamEnum.USE] = true
})
} else {
filterData.value.map((item: any) => {
@ -113,6 +114,7 @@ const allChecked: any = ref({
} else {
filterData.value.map((item: any) => {
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) => {
if (item.id === row.id) {
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('')
}
props.nodeModel.graphModel.eventCenter.on('refresh_incoming_node_field', () => {
getIncomingNode(props.nodeModel.id)
})
defineExpose({ validate })
onMounted(() => {
options.value = getIncomingNode(props.nodeModel.id)

View File

@ -33,7 +33,7 @@
<div @mousemove.stop @mousedown.stop @keydown.stop @click.stop>
<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 />
</el-icon>
</el-button>

View File

@ -12,17 +12,17 @@
<el-divider direction="vertical" />
<el-button link @click="retract">
<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-button>
<el-button link @click="extend">
<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-button>
<el-button link @click="layout">
<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-button>
</el-card>

View File

@ -49,7 +49,7 @@
<el-table-column prop="default_value" label="默认值">
<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>
</el-table-column>
<el-table-column label="必填">
@ -126,6 +126,18 @@ function refreshFieldList(data: any, index: any) {
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(() => {
if (!props.nodeModel.properties.user_input_field_list) {
if (props.nodeModel.properties.input_field_list) {

View File

@ -1,122 +1,121 @@
<template>
<NodeContainer :nodeModel="nodeModel">
<el-form
@submit.prevent
:model="form_data"
label-position="top"
require-asterisk-position="right"
label-width="auto"
ref="formNodeFormRef"
hide-required-asterisk
>
<el-form-item
label="表单输出内容"
prop="form_content_format"
:rules="{
required: true,
message: '请表单输出内容',
trigger: 'blur'
}"
<h5 class="title-decoration-1 mb-8">节点设置</h5>
<el-card shadow="never" class="card-never" style="--el-card-padding: 12px">
<el-form
@submit.prevent
:model="form_data"
label-position="top"
require-asterisk-position="right"
label-width="auto"
ref="formNodeFormRef"
hide-required-asterisk
>
<template #label>
<div class="flex align-center">
<div class="mr-4">
<span>表单输出内容<span class="danger">*</span></span>
</div>
<el-tooltip effect="dark" placement="right" popper-class="max-w-200">
<template #content>
设置执行该节点输出的内容{{ '{ 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
<el-form-item
label="表单输出内容"
prop="form_content_format"
:rules="{
required: true,
message: '请表单输出内容',
trigger: 'blur'
}"
>
<el-table
v-if="form_data.form_field_list.length > 0"
:data="form_data.form_field_list"
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" />
<template #label>
<div class="flex align-center">
<div class="mr-4">
<span>表单输出内容<span class="danger">*</span></span>
</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-tooltip effect="dark" placement="right" popper-class="max-w-200">
<template #content>
设置执行该节点输出的内容{{ '{ 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">
<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-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-tooltip>
</template>
</el-table-column>
</el-table>
</el-form-item>
</el-form>
</template>
</el-table-column>
</el-table>
</el-form-item>
</el-form>
</el-card>
<AddFormCollect ref="addFormCollectRef" :addFormField="addFormField"></AddFormCollect>
<EditFormCollect ref="editFormCollectRef" :editFormField="editFormField"></EditFormCollect>
</NodeContainer>
@ -169,7 +168,7 @@ const openAddFormCollect = () => {
addFormCollectRef.value?.open()
}
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) => {
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)
}
})
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 = () => {
return formNodeFormRef.value?.validate()
}
@ -206,6 +219,7 @@ function submitDialog(val: string) {
onMounted(() => {
set(props.nodeModel, 'validate', validate)
sync_form_field_list()
props.nodeModel.graphModel.eventCenter.emit('refresh_incoming_node_field')
})
</script>
<style lang="scss" scoped></style>