mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-27 20:32:44 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5971a4c183 | ||
|
|
c5c816b981 | ||
|
|
349328f608 | ||
|
|
452213e6e1 | ||
|
|
8b0b2b4415 | ||
|
|
de6b442f31 | ||
|
|
7974aeaa86 | ||
|
|
ebcef8f126 | ||
|
|
a9d767c67a | ||
|
|
77814972a4 | ||
|
|
1721344cc6 | ||
|
|
1117814a08 | ||
|
|
cb9bbccd4a | ||
|
|
38b58d3e5e | ||
|
|
84039b1272 | ||
|
|
e8e6c489fd | ||
|
|
73904d5407 | ||
|
|
9927c67ba3 | ||
|
|
dc371e25b0 | ||
|
|
f2b46225ac | ||
|
|
98cc8cd0e8 | ||
|
|
03ecebe506 | ||
|
|
89b9f06f45 | ||
|
|
209702cad2 |
|
|
@ -10,6 +10,7 @@ import time
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from typing import Type, Dict, List
|
from typing import Type, Dict, List
|
||||||
|
|
||||||
|
from django.core import cache
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
@ -18,7 +19,6 @@ from application.models.api_key_model import ApplicationPublicAccessClient
|
||||||
from common.constants.authentication_type import AuthenticationType
|
from common.constants.authentication_type import AuthenticationType
|
||||||
from common.field.common import InstanceField
|
from common.field.common import InstanceField
|
||||||
from common.util.field_message import ErrMessage
|
from common.util.field_message import ErrMessage
|
||||||
from django.core import cache
|
|
||||||
|
|
||||||
chat_cache = cache.caches['chat_cache']
|
chat_cache = cache.caches['chat_cache']
|
||||||
|
|
||||||
|
|
@ -27,9 +27,14 @@ def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
|
||||||
if step_variable is not None:
|
if step_variable is not None:
|
||||||
for key in step_variable:
|
for key in step_variable:
|
||||||
node.context[key] = step_variable[key]
|
node.context[key] = step_variable[key]
|
||||||
|
if workflow.is_result() and 'answer' in step_variable:
|
||||||
|
answer = step_variable['answer'] + '\n'
|
||||||
|
yield answer
|
||||||
|
workflow.answer += answer
|
||||||
if global_variable is not None:
|
if global_variable is not None:
|
||||||
for key in global_variable:
|
for key in global_variable:
|
||||||
workflow.context[key] = global_variable[key]
|
workflow.context[key] = global_variable[key]
|
||||||
|
node.context['run_time'] = time.time() - node.context['start_time']
|
||||||
|
|
||||||
|
|
||||||
class WorkFlowPostHandler:
|
class WorkFlowPostHandler:
|
||||||
|
|
@ -70,18 +75,14 @@ class WorkFlowPostHandler:
|
||||||
|
|
||||||
|
|
||||||
class NodeResult:
|
class NodeResult:
|
||||||
def __init__(self, node_variable: Dict, workflow_variable: Dict, _to_response=None, _write_context=write_context):
|
def __init__(self, node_variable: Dict, workflow_variable: Dict,
|
||||||
|
_write_context=write_context):
|
||||||
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._to_response = _to_response
|
|
||||||
|
|
||||||
def write_context(self, node, workflow):
|
def write_context(self, node, workflow):
|
||||||
self._write_context(self.node_variable, self.workflow_variable, node, workflow)
|
return self._write_context(self.node_variable, self.workflow_variable, node, workflow)
|
||||||
|
|
||||||
def to_response(self, chat_id, chat_record_id, node, workflow, post_handler: WorkFlowPostHandler):
|
|
||||||
return self._to_response(chat_id, chat_record_id, self.node_variable, self.workflow_variable, node, workflow,
|
|
||||||
post_handler)
|
|
||||||
|
|
||||||
def is_assertion_result(self):
|
def is_assertion_result(self):
|
||||||
return 'branch_id' in self.node_variable
|
return 'branch_id' in self.node_variable
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ class ChatNodeSerializer(serializers.Serializer):
|
||||||
# 多轮对话数量
|
# 多轮对话数量
|
||||||
dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer("多轮对话数量"))
|
dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer("多轮对话数量"))
|
||||||
|
|
||||||
|
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean('是否返回内容'))
|
||||||
|
|
||||||
|
|
||||||
class IChatNode(INode):
|
class IChatNode(INode):
|
||||||
type = 'ai-chat-node'
|
type = 'ai-chat-node'
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,25 @@ from typing import List, Dict
|
||||||
from langchain.schema import HumanMessage, SystemMessage
|
from langchain.schema import HumanMessage, SystemMessage
|
||||||
from langchain_core.messages import BaseMessage
|
from langchain_core.messages import BaseMessage
|
||||||
|
|
||||||
from application.flow import tools
|
|
||||||
from application.flow.i_step_node import NodeResult, INode
|
from application.flow.i_step_node import NodeResult, INode
|
||||||
from application.flow.step_node.ai_chat_step_node.i_chat_node import IChatNode
|
from application.flow.step_node.ai_chat_step_node.i_chat_node import IChatNode
|
||||||
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
||||||
|
|
||||||
|
|
||||||
|
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):
|
||||||
|
chat_model = node_variable.get('chat_model')
|
||||||
|
message_tokens = chat_model.get_num_tokens_from_messages(node_variable.get('message_list'))
|
||||||
|
answer_tokens = chat_model.get_num_tokens(answer)
|
||||||
|
node.context['message_tokens'] = message_tokens
|
||||||
|
node.context['answer_tokens'] = answer_tokens
|
||||||
|
node.context['answer'] = answer
|
||||||
|
node.context['history_message'] = node_variable['history_message']
|
||||||
|
node.context['question'] = node_variable['question']
|
||||||
|
node.context['run_time'] = time.time() - node.context['start_time']
|
||||||
|
if workflow.is_result():
|
||||||
|
workflow.answer += answer
|
||||||
|
|
||||||
|
|
||||||
def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):
|
def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):
|
||||||
"""
|
"""
|
||||||
写入上下文数据 (流式)
|
写入上下文数据 (流式)
|
||||||
|
|
@ -31,15 +44,10 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
|
||||||
answer = ''
|
answer = ''
|
||||||
for chunk in response:
|
for chunk in response:
|
||||||
answer += chunk.content
|
answer += chunk.content
|
||||||
chat_model = node_variable.get('chat_model')
|
yield chunk.content
|
||||||
message_tokens = chat_model.get_num_tokens_from_messages(node_variable.get('message_list'))
|
answer += '\n'
|
||||||
answer_tokens = chat_model.get_num_tokens(answer)
|
yield '\n'
|
||||||
node.context['message_tokens'] = message_tokens
|
_write_context(node_variable, workflow_variable, node, workflow, answer)
|
||||||
node.context['answer_tokens'] = answer_tokens
|
|
||||||
node.context['answer'] = answer
|
|
||||||
node.context['history_message'] = node_variable['history_message']
|
|
||||||
node.context['question'] = node_variable['question']
|
|
||||||
node.context['run_time'] = time.time() - node.context['start_time']
|
|
||||||
|
|
||||||
|
|
||||||
def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):
|
def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):
|
||||||
|
|
@ -51,71 +59,8 @@ def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wor
|
||||||
@param workflow: 工作流管理器
|
@param workflow: 工作流管理器
|
||||||
"""
|
"""
|
||||||
response = node_variable.get('result')
|
response = node_variable.get('result')
|
||||||
chat_model = node_variable.get('chat_model')
|
|
||||||
answer = response.content
|
answer = response.content
|
||||||
message_tokens = chat_model.get_num_tokens_from_messages(node_variable.get('message_list'))
|
_write_context(node_variable, workflow_variable, node, workflow, answer)
|
||||||
answer_tokens = chat_model.get_num_tokens(answer)
|
|
||||||
node.context['message_tokens'] = message_tokens
|
|
||||||
node.context['answer_tokens'] = answer_tokens
|
|
||||||
node.context['answer'] = answer
|
|
||||||
node.context['history_message'] = node_variable['history_message']
|
|
||||||
node.context['question'] = node_variable['question']
|
|
||||||
|
|
||||||
|
|
||||||
def get_to_response_write_context(node_variable: Dict, node: INode):
|
|
||||||
def _write_context(answer, status=200):
|
|
||||||
chat_model = node_variable.get('chat_model')
|
|
||||||
|
|
||||||
if status == 200:
|
|
||||||
answer_tokens = chat_model.get_num_tokens(answer)
|
|
||||||
message_tokens = chat_model.get_num_tokens_from_messages(node_variable.get('message_list'))
|
|
||||||
else:
|
|
||||||
answer_tokens = 0
|
|
||||||
message_tokens = 0
|
|
||||||
node.err_message = answer
|
|
||||||
node.status = status
|
|
||||||
node.context['message_tokens'] = message_tokens
|
|
||||||
node.context['answer_tokens'] = answer_tokens
|
|
||||||
node.context['answer'] = answer
|
|
||||||
node.context['run_time'] = time.time() - node.context['start_time']
|
|
||||||
|
|
||||||
return _write_context
|
|
||||||
|
|
||||||
|
|
||||||
def to_stream_response(chat_id, chat_record_id, node_variable: Dict, workflow_variable: Dict, node, workflow,
|
|
||||||
post_handler):
|
|
||||||
"""
|
|
||||||
将流式数据 转换为 流式响应
|
|
||||||
@param chat_id: 会话id
|
|
||||||
@param chat_record_id: 对话记录id
|
|
||||||
@param node_variable: 节点数据
|
|
||||||
@param workflow_variable: 工作流数据
|
|
||||||
@param node: 节点
|
|
||||||
@param workflow: 工作流管理器
|
|
||||||
@param post_handler: 后置处理器 输出结果后执行
|
|
||||||
@return: 流式响应
|
|
||||||
"""
|
|
||||||
response = node_variable.get('result')
|
|
||||||
_write_context = get_to_response_write_context(node_variable, node)
|
|
||||||
return tools.to_stream_response(chat_id, chat_record_id, response, workflow, _write_context, post_handler)
|
|
||||||
|
|
||||||
|
|
||||||
def to_response(chat_id, chat_record_id, node_variable: Dict, workflow_variable: Dict, node, workflow,
|
|
||||||
post_handler):
|
|
||||||
"""
|
|
||||||
将结果转换
|
|
||||||
@param chat_id: 会话id
|
|
||||||
@param chat_record_id: 对话记录id
|
|
||||||
@param node_variable: 节点数据
|
|
||||||
@param workflow_variable: 工作流数据
|
|
||||||
@param node: 节点
|
|
||||||
@param workflow: 工作流管理器
|
|
||||||
@param post_handler: 后置处理器
|
|
||||||
@return: 响应
|
|
||||||
"""
|
|
||||||
response = node_variable.get('result')
|
|
||||||
_write_context = get_to_response_write_context(node_variable, node)
|
|
||||||
return tools.to_response(chat_id, chat_record_id, response, workflow, _write_context, post_handler)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseChatNode(IChatNode):
|
class BaseChatNode(IChatNode):
|
||||||
|
|
@ -132,13 +77,12 @@ class BaseChatNode(IChatNode):
|
||||||
r = chat_model.stream(message_list)
|
r = chat_model.stream(message_list)
|
||||||
return NodeResult({'result': r, 'chat_model': chat_model, 'message_list': message_list,
|
return NodeResult({'result': r, 'chat_model': chat_model, 'message_list': message_list,
|
||||||
'history_message': history_message, 'question': question.content}, {},
|
'history_message': history_message, 'question': question.content}, {},
|
||||||
_write_context=write_context_stream,
|
_write_context=write_context_stream)
|
||||||
_to_response=to_stream_response)
|
|
||||||
else:
|
else:
|
||||||
r = chat_model.invoke(message_list)
|
r = chat_model.invoke(message_list)
|
||||||
return NodeResult({'result': r, 'chat_model': chat_model, 'message_list': message_list,
|
return NodeResult({'result': r, 'chat_model': chat_model, 'message_list': message_list,
|
||||||
'history_message': history_message, 'question': question.content}, {},
|
'history_message': history_message, 'question': question.content}, {},
|
||||||
_write_context=write_context, _to_response=to_response)
|
_write_context=write_context)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_history_message(history_chat_record, dialogue_number):
|
def get_history_message(history_chat_record, dialogue_number):
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ class ReplyNodeParamsSerializer(serializers.Serializer):
|
||||||
fields = serializers.ListField(required=False, error_messages=ErrMessage.list("引用字段"))
|
fields = serializers.ListField(required=False, error_messages=ErrMessage.list("引用字段"))
|
||||||
content = serializers.CharField(required=False, allow_blank=True, allow_null=True,
|
content = serializers.CharField(required=False, allow_blank=True, allow_null=True,
|
||||||
error_messages=ErrMessage.char("直接回答内容"))
|
error_messages=ErrMessage.char("直接回答内容"))
|
||||||
|
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean('是否返回内容'))
|
||||||
|
|
||||||
def is_valid(self, *, raise_exception=False):
|
def is_valid(self, *, raise_exception=False):
|
||||||
super().is_valid(raise_exception=True)
|
super().is_valid(raise_exception=True)
|
||||||
|
|
|
||||||
|
|
@ -6,69 +6,19 @@
|
||||||
@date:2024/6/11 17:25
|
@date:2024/6/11 17:25
|
||||||
@desc:
|
@desc:
|
||||||
"""
|
"""
|
||||||
from typing import List, Dict
|
from typing import List
|
||||||
|
|
||||||
from langchain_core.messages import AIMessage, AIMessageChunk
|
from application.flow.i_step_node import NodeResult
|
||||||
|
|
||||||
from application.flow import tools
|
|
||||||
from application.flow.i_step_node import NodeResult, INode
|
|
||||||
from application.flow.step_node.direct_reply_node.i_reply_node import IReplyNode
|
from application.flow.step_node.direct_reply_node.i_reply_node import IReplyNode
|
||||||
|
|
||||||
|
|
||||||
def get_to_response_write_context(node_variable: Dict, node: INode):
|
|
||||||
def _write_context(answer, status=200):
|
|
||||||
node.context['answer'] = answer
|
|
||||||
|
|
||||||
return _write_context
|
|
||||||
|
|
||||||
|
|
||||||
def to_stream_response(chat_id, chat_record_id, node_variable: Dict, workflow_variable: Dict, node, workflow,
|
|
||||||
post_handler):
|
|
||||||
"""
|
|
||||||
将流式数据 转换为 流式响应
|
|
||||||
@param chat_id: 会话id
|
|
||||||
@param chat_record_id: 对话记录id
|
|
||||||
@param node_variable: 节点数据
|
|
||||||
@param workflow_variable: 工作流数据
|
|
||||||
@param node: 节点
|
|
||||||
@param workflow: 工作流管理器
|
|
||||||
@param post_handler: 后置处理器 输出结果后执行
|
|
||||||
@return: 流式响应
|
|
||||||
"""
|
|
||||||
response = node_variable.get('result')
|
|
||||||
_write_context = get_to_response_write_context(node_variable, node)
|
|
||||||
return tools.to_stream_response(chat_id, chat_record_id, response, workflow, _write_context, post_handler)
|
|
||||||
|
|
||||||
|
|
||||||
def to_response(chat_id, chat_record_id, node_variable: Dict, workflow_variable: Dict, node, workflow,
|
|
||||||
post_handler):
|
|
||||||
"""
|
|
||||||
将结果转换
|
|
||||||
@param chat_id: 会话id
|
|
||||||
@param chat_record_id: 对话记录id
|
|
||||||
@param node_variable: 节点数据
|
|
||||||
@param workflow_variable: 工作流数据
|
|
||||||
@param node: 节点
|
|
||||||
@param workflow: 工作流管理器
|
|
||||||
@param post_handler: 后置处理器
|
|
||||||
@return: 响应
|
|
||||||
"""
|
|
||||||
response = node_variable.get('result')
|
|
||||||
_write_context = get_to_response_write_context(node_variable, node)
|
|
||||||
return tools.to_response(chat_id, chat_record_id, response, workflow, _write_context, post_handler)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseReplyNode(IReplyNode):
|
class BaseReplyNode(IReplyNode):
|
||||||
def execute(self, reply_type, stream, fields=None, content=None, **kwargs) -> NodeResult:
|
def execute(self, reply_type, stream, fields=None, content=None, **kwargs) -> NodeResult:
|
||||||
if reply_type == 'referencing':
|
if reply_type == 'referencing':
|
||||||
result = self.get_reference_content(fields)
|
result = self.get_reference_content(fields)
|
||||||
else:
|
else:
|
||||||
result = self.generate_reply_content(content)
|
result = self.generate_reply_content(content)
|
||||||
if stream:
|
return NodeResult({'answer': result}, {})
|
||||||
return NodeResult({'result': iter([AIMessageChunk(content=result)]), 'answer': result}, {},
|
|
||||||
_to_response=to_stream_response)
|
|
||||||
else:
|
|
||||||
return NodeResult({'result': AIMessage(content=result), 'answer': result}, {}, _to_response=to_response)
|
|
||||||
|
|
||||||
def generate_reply_content(self, prompt):
|
def generate_reply_content(self, prompt):
|
||||||
return self.workflow_manage.generate_prompt(prompt)
|
return self.workflow_manage.generate_prompt(prompt)
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ class QuestionNodeSerializer(serializers.Serializer):
|
||||||
# 多轮对话数量
|
# 多轮对话数量
|
||||||
dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer("多轮对话数量"))
|
dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer("多轮对话数量"))
|
||||||
|
|
||||||
|
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean('是否返回内容'))
|
||||||
|
|
||||||
|
|
||||||
class IQuestionNode(INode):
|
class IQuestionNode(INode):
|
||||||
type = 'question-node'
|
type = 'question-node'
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,25 @@ from typing import List, Dict
|
||||||
from langchain.schema import HumanMessage, SystemMessage
|
from langchain.schema import HumanMessage, SystemMessage
|
||||||
from langchain_core.messages import BaseMessage
|
from langchain_core.messages import BaseMessage
|
||||||
|
|
||||||
from application.flow import tools
|
|
||||||
from application.flow.i_step_node import NodeResult, INode
|
from application.flow.i_step_node import NodeResult, INode
|
||||||
from application.flow.step_node.question_node.i_question_node import IQuestionNode
|
from application.flow.step_node.question_node.i_question_node import IQuestionNode
|
||||||
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
||||||
|
|
||||||
|
|
||||||
|
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):
|
||||||
|
chat_model = node_variable.get('chat_model')
|
||||||
|
message_tokens = chat_model.get_num_tokens_from_messages(node_variable.get('message_list'))
|
||||||
|
answer_tokens = chat_model.get_num_tokens(answer)
|
||||||
|
node.context['message_tokens'] = message_tokens
|
||||||
|
node.context['answer_tokens'] = answer_tokens
|
||||||
|
node.context['answer'] = answer
|
||||||
|
node.context['history_message'] = node_variable['history_message']
|
||||||
|
node.context['question'] = node_variable['question']
|
||||||
|
node.context['run_time'] = time.time() - node.context['start_time']
|
||||||
|
if workflow.is_result():
|
||||||
|
workflow.answer += answer
|
||||||
|
|
||||||
|
|
||||||
def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):
|
def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):
|
||||||
"""
|
"""
|
||||||
写入上下文数据 (流式)
|
写入上下文数据 (流式)
|
||||||
|
|
@ -31,15 +44,10 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
|
||||||
answer = ''
|
answer = ''
|
||||||
for chunk in response:
|
for chunk in response:
|
||||||
answer += chunk.content
|
answer += chunk.content
|
||||||
chat_model = node_variable.get('chat_model')
|
yield chunk.content
|
||||||
message_tokens = chat_model.get_num_tokens_from_messages(node_variable.get('message_list'))
|
answer += '\n'
|
||||||
answer_tokens = chat_model.get_num_tokens(answer)
|
yield '\n'
|
||||||
node.context['message_tokens'] = message_tokens
|
_write_context(node_variable, workflow_variable, node, workflow, answer)
|
||||||
node.context['answer_tokens'] = answer_tokens
|
|
||||||
node.context['answer'] = answer
|
|
||||||
node.context['history_message'] = node_variable['history_message']
|
|
||||||
node.context['question'] = node_variable['question']
|
|
||||||
node.context['run_time'] = time.time() - node.context['start_time']
|
|
||||||
|
|
||||||
|
|
||||||
def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):
|
def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):
|
||||||
|
|
@ -51,71 +59,8 @@ def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wor
|
||||||
@param workflow: 工作流管理器
|
@param workflow: 工作流管理器
|
||||||
"""
|
"""
|
||||||
response = node_variable.get('result')
|
response = node_variable.get('result')
|
||||||
chat_model = node_variable.get('chat_model')
|
|
||||||
answer = response.content
|
answer = response.content
|
||||||
message_tokens = chat_model.get_num_tokens_from_messages(node_variable.get('message_list'))
|
_write_context(node_variable, workflow_variable, node, workflow, answer)
|
||||||
answer_tokens = chat_model.get_num_tokens(answer)
|
|
||||||
node.context['message_tokens'] = message_tokens
|
|
||||||
node.context['answer_tokens'] = answer_tokens
|
|
||||||
node.context['answer'] = answer
|
|
||||||
node.context['history_message'] = node_variable['history_message']
|
|
||||||
node.context['question'] = node_variable['question']
|
|
||||||
|
|
||||||
|
|
||||||
def get_to_response_write_context(node_variable: Dict, node: INode):
|
|
||||||
def _write_context(answer, status=200):
|
|
||||||
chat_model = node_variable.get('chat_model')
|
|
||||||
|
|
||||||
if status == 200:
|
|
||||||
answer_tokens = chat_model.get_num_tokens(answer)
|
|
||||||
message_tokens = chat_model.get_num_tokens_from_messages(node_variable.get('message_list'))
|
|
||||||
else:
|
|
||||||
answer_tokens = 0
|
|
||||||
message_tokens = 0
|
|
||||||
node.err_message = answer
|
|
||||||
node.status = status
|
|
||||||
node.context['message_tokens'] = message_tokens
|
|
||||||
node.context['answer_tokens'] = answer_tokens
|
|
||||||
node.context['answer'] = answer
|
|
||||||
node.context['run_time'] = time.time() - node.context['start_time']
|
|
||||||
|
|
||||||
return _write_context
|
|
||||||
|
|
||||||
|
|
||||||
def to_stream_response(chat_id, chat_record_id, node_variable: Dict, workflow_variable: Dict, node, workflow,
|
|
||||||
post_handler):
|
|
||||||
"""
|
|
||||||
将流式数据 转换为 流式响应
|
|
||||||
@param chat_id: 会话id
|
|
||||||
@param chat_record_id: 对话记录id
|
|
||||||
@param node_variable: 节点数据
|
|
||||||
@param workflow_variable: 工作流数据
|
|
||||||
@param node: 节点
|
|
||||||
@param workflow: 工作流管理器
|
|
||||||
@param post_handler: 后置处理器 输出结果后执行
|
|
||||||
@return: 流式响应
|
|
||||||
"""
|
|
||||||
response = node_variable.get('result')
|
|
||||||
_write_context = get_to_response_write_context(node_variable, node)
|
|
||||||
return tools.to_stream_response(chat_id, chat_record_id, response, workflow, _write_context, post_handler)
|
|
||||||
|
|
||||||
|
|
||||||
def to_response(chat_id, chat_record_id, node_variable: Dict, workflow_variable: Dict, node, workflow,
|
|
||||||
post_handler):
|
|
||||||
"""
|
|
||||||
将结果转换
|
|
||||||
@param chat_id: 会话id
|
|
||||||
@param chat_record_id: 对话记录id
|
|
||||||
@param node_variable: 节点数据
|
|
||||||
@param workflow_variable: 工作流数据
|
|
||||||
@param node: 节点
|
|
||||||
@param workflow: 工作流管理器
|
|
||||||
@param post_handler: 后置处理器
|
|
||||||
@return: 响应
|
|
||||||
"""
|
|
||||||
response = node_variable.get('result')
|
|
||||||
_write_context = get_to_response_write_context(node_variable, node)
|
|
||||||
return tools.to_response(chat_id, chat_record_id, response, workflow, _write_context, post_handler)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseQuestionNode(IQuestionNode):
|
class BaseQuestionNode(IQuestionNode):
|
||||||
|
|
@ -131,15 +76,13 @@ class BaseQuestionNode(IQuestionNode):
|
||||||
if stream:
|
if stream:
|
||||||
r = chat_model.stream(message_list)
|
r = chat_model.stream(message_list)
|
||||||
return NodeResult({'result': r, 'chat_model': chat_model, 'message_list': message_list,
|
return NodeResult({'result': r, 'chat_model': chat_model, 'message_list': message_list,
|
||||||
'get_to_response_write_context': get_to_response_write_context,
|
|
||||||
'history_message': history_message, 'question': question.content}, {},
|
'history_message': history_message, 'question': question.content}, {},
|
||||||
_write_context=write_context_stream,
|
_write_context=write_context_stream)
|
||||||
_to_response=to_stream_response)
|
|
||||||
else:
|
else:
|
||||||
r = chat_model.invoke(message_list)
|
r = chat_model.invoke(message_list)
|
||||||
return NodeResult({'result': r, 'chat_model': chat_model, 'message_list': message_list,
|
return NodeResult({'result': r, 'chat_model': chat_model, 'message_list': message_list,
|
||||||
'history_message': history_message, 'question': question.content}, {},
|
'history_message': history_message, 'question': question.content}, {},
|
||||||
_write_context=write_context, _to_response=to_response)
|
_write_context=write_context)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_history_message(history_chat_record, dialogue_number):
|
def get_history_message(history_chat_record, dialogue_number):
|
||||||
|
|
|
||||||
|
|
@ -85,3 +85,21 @@ def to_response(chat_id, chat_record_id, response: BaseMessage, workflow, write_
|
||||||
post_handler.handler(chat_id, chat_record_id, answer, workflow)
|
post_handler.handler(chat_id, chat_record_id, answer, workflow)
|
||||||
return result.success({'chat_id': str(chat_id), 'id': str(chat_record_id), 'operate': True,
|
return result.success({'chat_id': str(chat_id), 'id': str(chat_record_id), 'operate': True,
|
||||||
'content': answer, 'is_end': True})
|
'content': answer, 'is_end': True})
|
||||||
|
|
||||||
|
|
||||||
|
def to_response_simple(chat_id, chat_record_id, response: BaseMessage, workflow,
|
||||||
|
post_handler: WorkFlowPostHandler):
|
||||||
|
answer = response.content
|
||||||
|
post_handler.handler(chat_id, chat_record_id, answer, workflow)
|
||||||
|
return result.success({'chat_id': str(chat_id), 'id': str(chat_record_id), 'operate': True,
|
||||||
|
'content': answer, 'is_end': True})
|
||||||
|
|
||||||
|
|
||||||
|
def to_stream_response_simple(stream_event):
|
||||||
|
r = StreamingHttpResponse(
|
||||||
|
streaming_content=stream_event,
|
||||||
|
content_type='text/event-stream;charset=utf-8',
|
||||||
|
charset='utf-8')
|
||||||
|
|
||||||
|
r['Cache-Control'] = 'no-cache'
|
||||||
|
return r
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,11 @@
|
||||||
@date:2024/1/9 17:40
|
@date:2024/1/9 17:40
|
||||||
@desc:
|
@desc:
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
|
||||||
from langchain_core.messages import AIMessageChunk, AIMessage
|
from langchain_core.messages import AIMessage
|
||||||
from langchain_core.prompts import PromptTemplate
|
from langchain_core.prompts import PromptTemplate
|
||||||
|
|
||||||
from application.flow import tools
|
from application.flow import tools
|
||||||
|
|
@ -63,7 +64,6 @@ class Flow:
|
||||||
def get_search_node(self):
|
def get_search_node(self):
|
||||||
return [node for node in self.nodes if node.type == 'search-dataset-node']
|
return [node for node in self.nodes if node.type == 'search-dataset-node']
|
||||||
|
|
||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
"""
|
"""
|
||||||
校验工作流数据
|
校验工作流数据
|
||||||
|
|
@ -140,33 +140,71 @@ class WorkflowManage:
|
||||||
self.work_flow_post_handler = work_flow_post_handler
|
self.work_flow_post_handler = work_flow_post_handler
|
||||||
self.current_node = None
|
self.current_node = None
|
||||||
self.current_result = None
|
self.current_result = None
|
||||||
|
self.answer = ""
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
if self.params.get('stream'):
|
||||||
运行工作流
|
return self.run_stream()
|
||||||
"""
|
return self.run_block()
|
||||||
|
|
||||||
|
def run_block(self):
|
||||||
try:
|
try:
|
||||||
while self.has_next_node(self.current_result):
|
while self.has_next_node(self.current_result):
|
||||||
self.current_node = self.get_next_node()
|
self.current_node = self.get_next_node()
|
||||||
self.node_context.append(self.current_node)
|
self.node_context.append(self.current_node)
|
||||||
self.current_result = self.current_node.run()
|
self.current_result = self.current_node.run()
|
||||||
if self.has_next_node(self.current_result):
|
result = self.current_result.write_context(self.current_node, self)
|
||||||
self.current_result.write_context(self.current_node, self)
|
if result is not None:
|
||||||
else:
|
list(result)
|
||||||
r = self.current_result.to_response(self.params['chat_id'], self.params['chat_record_id'],
|
if not self.has_next_node(self.current_result):
|
||||||
self.current_node, self,
|
return tools.to_response_simple(self.params['chat_id'], self.params['chat_record_id'],
|
||||||
self.work_flow_post_handler)
|
AIMessage(self.answer), self,
|
||||||
return r
|
self.work_flow_post_handler)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if self.params.get('stream'):
|
return tools.to_response(self.params['chat_id'], self.params['chat_record_id'],
|
||||||
return tools.to_stream_response(self.params['chat_id'], self.params['chat_record_id'],
|
AIMessage(str(e)), self, self.current_node.get_write_error_context(e),
|
||||||
iter([AIMessageChunk(str(e))]), self,
|
self.work_flow_post_handler)
|
||||||
self.current_node.get_write_error_context(e),
|
|
||||||
self.work_flow_post_handler)
|
def run_stream(self):
|
||||||
else:
|
return tools.to_stream_response_simple(self.stream_event())
|
||||||
return tools.to_response(self.params['chat_id'], self.params['chat_record_id'],
|
|
||||||
AIMessage(str(e)), self, self.current_node.get_write_error_context(e),
|
def stream_event(self):
|
||||||
self.work_flow_post_handler)
|
try:
|
||||||
|
while self.has_next_node(self.current_result):
|
||||||
|
self.current_node = self.get_next_node()
|
||||||
|
self.node_context.append(self.current_node)
|
||||||
|
self.current_result = self.current_node.run()
|
||||||
|
result = self.current_result.write_context(self.current_node, self)
|
||||||
|
if result is not None:
|
||||||
|
for r in result:
|
||||||
|
if self.is_result():
|
||||||
|
yield self.get_chunk_content(r)
|
||||||
|
if not self.has_next_node(self.current_result):
|
||||||
|
yield self.get_chunk_content('', True)
|
||||||
|
break
|
||||||
|
self.work_flow_post_handler.handler(self.params['chat_id'], self.params['chat_record_id'],
|
||||||
|
self.answer,
|
||||||
|
self)
|
||||||
|
except Exception as e:
|
||||||
|
self.current_node.get_write_error_context(e)
|
||||||
|
self.answer += str(e)
|
||||||
|
self.work_flow_post_handler.handler(self.params['chat_id'], self.params['chat_record_id'],
|
||||||
|
self.answer,
|
||||||
|
self)
|
||||||
|
yield self.get_chunk_content(str(e), True)
|
||||||
|
|
||||||
|
def is_result(self):
|
||||||
|
"""
|
||||||
|
判断是否是返回节点
|
||||||
|
@return:
|
||||||
|
"""
|
||||||
|
return self.current_node.node_params.get('is_result', not self.has_next_node(
|
||||||
|
self.current_result)) if self.current_node.node_params is not None else False
|
||||||
|
|
||||||
|
def get_chunk_content(self, chunk, is_end=False):
|
||||||
|
return 'data: ' + json.dumps(
|
||||||
|
{'chat_id': self.params['chat_id'], 'id': self.params['chat_record_id'], 'operate': True,
|
||||||
|
'content': chunk, 'is_end': is_end}, ensure_ascii=False) + "\n\n"
|
||||||
|
|
||||||
def has_next_node(self, node_result: NodeResult | None):
|
def has_next_node(self, node_result: NodeResult | None):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ from application.models.api_key_model import ApplicationAccessToken
|
||||||
from application.serializers.application_serializers import ModelDatasetAssociation, DatasetSettingSerializer, \
|
from application.serializers.application_serializers import ModelDatasetAssociation, DatasetSettingSerializer, \
|
||||||
ModelSettingSerializer
|
ModelSettingSerializer
|
||||||
from application.serializers.chat_message_serializers import ChatInfo
|
from application.serializers.chat_message_serializers import ChatInfo
|
||||||
from common.config.embedding_config import ModelManage
|
|
||||||
from common.constants.permission_constants import RoleConstants
|
from common.constants.permission_constants import RoleConstants
|
||||||
from common.db.search import native_search, native_page_search, page_search, get_dynamics_model
|
from common.db.search import native_search, native_page_search, page_search, get_dynamics_model
|
||||||
from common.event import ListenerManagement
|
from common.event import ListenerManagement
|
||||||
|
|
@ -40,8 +39,6 @@ from common.util.lock import try_lock, un_lock
|
||||||
from dataset.models import Document, Problem, Paragraph, ProblemParagraphMapping
|
from dataset.models import Document, Problem, Paragraph, ProblemParagraphMapping
|
||||||
from dataset.serializers.common_serializers import get_embedding_model_by_dataset_id
|
from dataset.serializers.common_serializers import get_embedding_model_by_dataset_id
|
||||||
from dataset.serializers.paragraph_serializers import ParagraphSerializers
|
from dataset.serializers.paragraph_serializers import ParagraphSerializers
|
||||||
from setting.models import Model
|
|
||||||
from setting.models_provider import get_model
|
|
||||||
from smartdoc.conf import PROJECT_DIR
|
from smartdoc.conf import PROJECT_DIR
|
||||||
|
|
||||||
chat_cache = caches['chat_cache']
|
chat_cache = caches['chat_cache']
|
||||||
|
|
@ -312,7 +309,8 @@ class ChatSerializers(serializers.Serializer):
|
||||||
chat_id = str(uuid.uuid1())
|
chat_id = str(uuid.uuid1())
|
||||||
model_id = self.data.get('model_id')
|
model_id = self.data.get('model_id')
|
||||||
dataset_id_list = self.data.get('dataset_id_list')
|
dataset_id_list = self.data.get('dataset_id_list')
|
||||||
application = Application(id=None, dialogue_number=3, model_id=model_id,
|
dialogue_number = 3 if self.data.get('multiple_rounds_dialogue', False) else 0
|
||||||
|
application = Application(id=None, dialogue_number=dialogue_number, model_id=model_id,
|
||||||
dataset_setting=self.data.get('dataset_setting'),
|
dataset_setting=self.data.get('dataset_setting'),
|
||||||
model_setting=self.data.get('model_setting'),
|
model_setting=self.data.get('model_setting'),
|
||||||
problem_optimization=self.data.get('problem_optimization'),
|
problem_optimization=self.data.get('problem_optimization'),
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ class ModelManage:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_model(_id, get_model):
|
def get_model(_id, get_model):
|
||||||
model_instance = ModelManage.cache.get(_id)
|
model_instance = ModelManage.cache.get(_id)
|
||||||
if model_instance is None:
|
if model_instance is None or not model_instance.is_cache_model():
|
||||||
model_instance = get_model(_id)
|
model_instance = get_model(_id)
|
||||||
ModelManage.cache.set(_id, model_instance, timeout=60 * 30)
|
ModelManage.cache.set(_id, model_instance, timeout=60 * 30)
|
||||||
return model_instance
|
return model_instance
|
||||||
|
|
|
||||||
|
|
@ -110,11 +110,16 @@ class ListenerManagement:
|
||||||
@embedding_poxy
|
@embedding_poxy
|
||||||
def embedding_by_paragraph_data_list(data_list, paragraph_id_list, embedding_model: Embeddings):
|
def embedding_by_paragraph_data_list(data_list, paragraph_id_list, embedding_model: Embeddings):
|
||||||
max_kb.info(f'开始--->向量化段落:{paragraph_id_list}')
|
max_kb.info(f'开始--->向量化段落:{paragraph_id_list}')
|
||||||
|
status = Status.success
|
||||||
try:
|
try:
|
||||||
# 删除段落
|
# 删除段落
|
||||||
VectorStore.get_embedding_vector().delete_by_paragraph_ids(paragraph_id_list)
|
VectorStore.get_embedding_vector().delete_by_paragraph_ids(paragraph_id_list)
|
||||||
|
|
||||||
|
def is_save_function():
|
||||||
|
return QuerySet(Paragraph).filter(id__in=paragraph_id_list).exists()
|
||||||
|
|
||||||
# 批量向量化
|
# 批量向量化
|
||||||
VectorStore.get_embedding_vector().batch_save(data_list, embedding_model)
|
VectorStore.get_embedding_vector().batch_save(data_list, embedding_model, is_save_function)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
max_kb_error.error(f'向量化段落:{paragraph_id_list}出现错误{str(e)}{traceback.format_exc()}')
|
max_kb_error.error(f'向量化段落:{paragraph_id_list}出现错误{str(e)}{traceback.format_exc()}')
|
||||||
status = Status.error
|
status = Status.error
|
||||||
|
|
@ -141,8 +146,12 @@ class ListenerManagement:
|
||||||
os.path.join(PROJECT_DIR, "apps", "common", 'sql', 'list_embedding_text.sql')))
|
os.path.join(PROJECT_DIR, "apps", "common", 'sql', 'list_embedding_text.sql')))
|
||||||
# 删除段落
|
# 删除段落
|
||||||
VectorStore.get_embedding_vector().delete_by_paragraph_id(paragraph_id)
|
VectorStore.get_embedding_vector().delete_by_paragraph_id(paragraph_id)
|
||||||
|
|
||||||
|
def is_save_function():
|
||||||
|
return QuerySet(Paragraph).filter(id=paragraph_id).exists()
|
||||||
|
|
||||||
# 批量向量化
|
# 批量向量化
|
||||||
VectorStore.get_embedding_vector().batch_save(data_list, embedding_model)
|
VectorStore.get_embedding_vector().batch_save(data_list, embedding_model, is_save_function)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
max_kb_error.error(f'向量化段落:{paragraph_id}出现错误{str(e)}{traceback.format_exc()}')
|
max_kb_error.error(f'向量化段落:{paragraph_id}出现错误{str(e)}{traceback.format_exc()}')
|
||||||
status = Status.error
|
status = Status.error
|
||||||
|
|
@ -175,8 +184,12 @@ class ListenerManagement:
|
||||||
os.path.join(PROJECT_DIR, "apps", "common", 'sql', 'list_embedding_text.sql')))
|
os.path.join(PROJECT_DIR, "apps", "common", 'sql', 'list_embedding_text.sql')))
|
||||||
# 删除文档向量数据
|
# 删除文档向量数据
|
||||||
VectorStore.get_embedding_vector().delete_by_document_id(document_id)
|
VectorStore.get_embedding_vector().delete_by_document_id(document_id)
|
||||||
|
|
||||||
|
def is_save_function():
|
||||||
|
return QuerySet(Document).filter(id=document_id).exists()
|
||||||
|
|
||||||
# 批量向量化
|
# 批量向量化
|
||||||
VectorStore.get_embedding_vector().batch_save(data_list, embedding_model)
|
VectorStore.get_embedding_vector().batch_save(data_list, embedding_model, is_save_function)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
max_kb_error.error(f'向量化文档:{document_id}出现错误{str(e)}{traceback.format_exc()}')
|
max_kb_error.error(f'向量化文档:{document_id}出现错误{str(e)}{traceback.format_exc()}')
|
||||||
status = Status.error
|
status = Status.error
|
||||||
|
|
@ -258,7 +271,7 @@ class ListenerManagement:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_problem(args: UpdateProblemArgs):
|
def update_problem(args: UpdateProblemArgs):
|
||||||
problem_paragraph_mapping_list = QuerySet(ProblemParagraphMapping).filter(problem_id=args.problem_id)
|
problem_paragraph_mapping_list = QuerySet(ProblemParagraphMapping).filter(problem_id=args.problem_id)
|
||||||
embed_value = VectorStore.get_embedding_vector().embed_query(args.problem_content)
|
embed_value = args.embedding_model.embed_query(args.problem_content)
|
||||||
VectorStore.get_embedding_vector().update_by_source_ids([v.id for v in problem_paragraph_mapping_list],
|
VectorStore.get_embedding_vector().update_by_source_ids([v.id for v in problem_paragraph_mapping_list],
|
||||||
{'embedding': embed_value})
|
{'embedding': embed_value})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ def handle_sheet(file_name, sheet):
|
||||||
problem_list = [{'content': p[0:255]} for p in problem.split('\n') if len(p.strip()) > 0]
|
problem_list = [{'content': p[0:255]} for p in problem.split('\n') if len(p.strip()) > 0]
|
||||||
title = get_row_value(row, title_row_index_dict, 'title')
|
title = get_row_value(row, title_row_index_dict, 'title')
|
||||||
title = str(title) if title is not None else ''
|
title = str(title) if title is not None else ''
|
||||||
|
content = str(content)
|
||||||
paragraph_list.append({'title': title[0:255],
|
paragraph_list.append({'title': title[0:255],
|
||||||
'content': content[0:4096],
|
'content': content[0:4096],
|
||||||
'problem_list': problem_list})
|
'problem_list': problem_list})
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ def handle_sheet(file_name, sheet):
|
||||||
problem_list = [{'content': p[0:255]} for p in problem.split('\n') if len(p.strip()) > 0]
|
problem_list = [{'content': p[0:255]} for p in problem.split('\n') if len(p.strip()) > 0]
|
||||||
title = get_row_value(row, title_row_index_dict, 'title')
|
title = get_row_value(row, title_row_index_dict, 'title')
|
||||||
title = str(title.value) if title is not None and title.value is not None else ''
|
title = str(title.value) if title is not None and title.value is not None else ''
|
||||||
content = content.value
|
content = str(content.value)
|
||||||
paragraph_list.append({'title': title[0:255],
|
paragraph_list.append({'title': title[0:255],
|
||||||
'content': content[0:4096],
|
'content': content[0:4096],
|
||||||
'problem_list': problem_list})
|
'problem_list': problem_list})
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ def get_level_block(text, level_content_list, level_content_index, cursor):
|
||||||
level_content_list) else None
|
level_content_list) else None
|
||||||
start_index = text.index(start_content, cursor)
|
start_index = text.index(start_content, cursor)
|
||||||
end_index = text.index(next_content, start_index + 1) if next_content is not None else len(text)
|
end_index = text.index(next_content, start_index + 1) if next_content is not None else len(text)
|
||||||
return text[start_index+len(start_content):end_index], end_index
|
return text[start_index + len(start_content):end_index], end_index
|
||||||
|
|
||||||
|
|
||||||
def to_tree_obj(content, state='title'):
|
def to_tree_obj(content, state='title'):
|
||||||
|
|
@ -303,17 +303,20 @@ class SplitModel:
|
||||||
level_content_list.insert(0, to_tree_obj(""))
|
level_content_list.insert(0, to_tree_obj(""))
|
||||||
|
|
||||||
cursor = 0
|
cursor = 0
|
||||||
for i in range(len(level_content_list)):
|
level_title_content_list = [item for item in level_content_list if item.get('state') == 'title']
|
||||||
block, cursor = get_level_block(text, level_content_list, i, cursor)
|
for i in range(len(level_title_content_list)):
|
||||||
|
start_content: str = level_title_content_list[i].get('content')
|
||||||
|
if cursor < text.index(start_content, cursor):
|
||||||
|
level_content_list.insert(0, to_tree_obj(text[cursor: text.index(start_content, cursor)], 'block'))
|
||||||
|
block, cursor = get_level_block(text, level_title_content_list, i, cursor)
|
||||||
if len(block) == 0:
|
if len(block) == 0:
|
||||||
level_content_list[i]['children'] = [to_tree_obj("", "block")]
|
|
||||||
continue
|
continue
|
||||||
children = self.parse_to_tree(text=block, index=index + 1)
|
children = self.parse_to_tree(text=block, index=index + 1)
|
||||||
level_content_list[i]['children'] = children
|
level_title_content_list[i]['children'] = children
|
||||||
first_child_idx_in_block = block.lstrip().index(children[0]["content"].lstrip())
|
first_child_idx_in_block = block.lstrip().index(children[0]["content"].lstrip())
|
||||||
if first_child_idx_in_block != 0:
|
if first_child_idx_in_block != 0:
|
||||||
inner_children = self.parse_to_tree(block[:first_child_idx_in_block], index + 1)
|
inner_children = self.parse_to_tree(block[:first_child_idx_in_block], index + 1)
|
||||||
level_content_list[i]['children'].extend(inner_children)
|
level_title_content_list[i]['children'].extend(inner_children)
|
||||||
return level_content_list
|
return level_content_list
|
||||||
|
|
||||||
def parse(self, text: str):
|
def parse(self, text: str):
|
||||||
|
|
|
||||||
|
|
@ -463,6 +463,7 @@ class DataSetSerializers(serializers.ModelSerializer):
|
||||||
dataset = DataSet(
|
dataset = DataSet(
|
||||||
**{'id': dataset_id, 'name': instance.get("name"), 'desc': instance.get('desc'), 'user_id': user_id,
|
**{'id': dataset_id, 'name': instance.get("name"), 'desc': instance.get('desc'), 'user_id': user_id,
|
||||||
'type': Type.web,
|
'type': Type.web,
|
||||||
|
'embedding_mode_id': instance.get('embedding_mode_id'),
|
||||||
'meta': {'source_url': instance.get('source_url'), 'selector': instance.get('selector'),
|
'meta': {'source_url': instance.get('source_url'), 'selector': instance.get('selector'),
|
||||||
'embedding_mode_id': instance.get('embedding_mode_id')}})
|
'embedding_mode_id': instance.get('embedding_mode_id')}})
|
||||||
dataset.save()
|
dataset.save()
|
||||||
|
|
|
||||||
|
|
@ -365,7 +365,7 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
|
||||||
if document.type != Type.web:
|
if document.type != Type.web:
|
||||||
return True
|
return True
|
||||||
try:
|
try:
|
||||||
document.status = Status.embedding
|
document.status = Status.queue_up
|
||||||
document.save()
|
document.save()
|
||||||
source_url = document.meta.get('source_url')
|
source_url = document.meta.get('source_url')
|
||||||
selector_list = document.meta.get('selector').split(
|
selector_list = document.meta.get('selector').split(
|
||||||
|
|
|
||||||
|
|
@ -84,9 +84,9 @@ class BaseVectorStore(ABC):
|
||||||
chunk_list = chunk_data(data)
|
chunk_list = chunk_data(data)
|
||||||
result = sub_array(chunk_list)
|
result = sub_array(chunk_list)
|
||||||
for child_array in result:
|
for child_array in result:
|
||||||
self._batch_save(child_array, embedding)
|
self._batch_save(child_array, embedding, lambda: True)
|
||||||
|
|
||||||
def batch_save(self, data_list: List[Dict], embedding: Embeddings):
|
def batch_save(self, data_list: List[Dict], embedding: Embeddings, is_save_function):
|
||||||
# 获取锁
|
# 获取锁
|
||||||
lock.acquire()
|
lock.acquire()
|
||||||
try:
|
try:
|
||||||
|
|
@ -100,7 +100,10 @@ class BaseVectorStore(ABC):
|
||||||
chunk_list = chunk_data_list(data_list)
|
chunk_list = chunk_data_list(data_list)
|
||||||
result = sub_array(chunk_list)
|
result = sub_array(chunk_list)
|
||||||
for child_array in result:
|
for child_array in result:
|
||||||
self._batch_save(child_array, embedding)
|
if is_save_function():
|
||||||
|
self._batch_save(child_array, embedding, is_save_function)
|
||||||
|
else:
|
||||||
|
break
|
||||||
finally:
|
finally:
|
||||||
# 释放锁
|
# 释放锁
|
||||||
lock.release()
|
lock.release()
|
||||||
|
|
@ -113,7 +116,7 @@ class BaseVectorStore(ABC):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _batch_save(self, text_list: List[Dict], embedding: Embeddings):
|
def _batch_save(self, text_list: List[Dict], embedding: Embeddings, is_save_function):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def search(self, query_text, dataset_id_list: list[str], exclude_document_id_list: list[str],
|
def search(self, query_text, dataset_id_list: list[str], exclude_document_id_list: list[str],
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ class PGVector(BaseVectorStore):
|
||||||
embedding.save()
|
embedding.save()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _batch_save(self, text_list: List[Dict], embedding: Embeddings):
|
def _batch_save(self, text_list: List[Dict], embedding: Embeddings, is_save_function):
|
||||||
texts = [row.get('text') for row in text_list]
|
texts = [row.get('text') for row in text_list]
|
||||||
embeddings = embedding.embed_documents(texts)
|
embeddings = embedding.embed_documents(texts)
|
||||||
embedding_list = [Embedding(id=uuid.uuid1(),
|
embedding_list = [Embedding(id=uuid.uuid1(),
|
||||||
|
|
@ -68,7 +68,8 @@ class PGVector(BaseVectorStore):
|
||||||
embedding=embeddings[index],
|
embedding=embeddings[index],
|
||||||
search_vector=to_ts_vector(text_list[index]['text'])) for index in
|
search_vector=to_ts_vector(text_list[index]['text'])) for index in
|
||||||
range(0, len(text_list))]
|
range(0, len(text_list))]
|
||||||
QuerySet(Embedding).bulk_create(embedding_list) if len(embedding_list) > 0 else None
|
if is_save_function():
|
||||||
|
QuerySet(Embedding).bulk_create(embedding_list) if len(embedding_list) > 0 else None
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def hit_test(self, query_text, dataset_id_list: list[str], exclude_document_id_list: list[str], top_number: int,
|
def hit_test(self, query_text, dataset_id_list: list[str], exclude_document_id_list: list[str], top_number: int,
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,10 @@ class MaxKBBaseModel(ABC):
|
||||||
def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):
|
def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_cache_model():
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class BaseModelCredential(ABC):
|
class BaseModelCredential(ABC):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,10 @@ from setting.models_provider.base_model_provider import MaxKBBaseModel
|
||||||
|
|
||||||
def get_base_url(url: str):
|
def get_base_url(url: str):
|
||||||
parse = urlparse(url)
|
parse = urlparse(url)
|
||||||
return ParseResult(scheme=parse.scheme, netloc=parse.netloc, path='', params='',
|
result_url = ParseResult(scheme=parse.scheme, netloc=parse.netloc, path=parse.path, params='',
|
||||||
query='',
|
query='',
|
||||||
fragment='').geturl()
|
fragment='').geturl()
|
||||||
|
return result_url[:-1] if result_url.endswith("/") else result_url
|
||||||
|
|
||||||
|
|
||||||
class OllamaChatModel(MaxKBBaseModel, ChatOpenAI):
|
class OllamaChatModel(MaxKBBaseModel, ChatOpenAI):
|
||||||
|
|
@ -28,7 +29,8 @@ class OllamaChatModel(MaxKBBaseModel, ChatOpenAI):
|
||||||
def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):
|
def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):
|
||||||
api_base = model_credential.get('api_base', '')
|
api_base = model_credential.get('api_base', '')
|
||||||
base_url = get_base_url(api_base)
|
base_url = get_base_url(api_base)
|
||||||
return OllamaChatModel(model=model_name, openai_api_base=(base_url + '/v1'),
|
base_url = base_url if base_url.endswith('/v1') else (base_url + '/v1')
|
||||||
|
return OllamaChatModel(model=model_name, openai_api_base=base_url,
|
||||||
openai_api_key=model_credential.get('api_key'))
|
openai_api_key=model_credential.get('api_key'))
|
||||||
|
|
||||||
def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int:
|
def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int:
|
||||||
|
|
|
||||||
|
|
@ -113,9 +113,10 @@ model_info_manage = ModelInfoManage.builder().append_model_info_list(model_info_
|
||||||
|
|
||||||
def get_base_url(url: str):
|
def get_base_url(url: str):
|
||||||
parse = urlparse(url)
|
parse = urlparse(url)
|
||||||
return ParseResult(scheme=parse.scheme, netloc=parse.netloc, path='', params='',
|
result_url = ParseResult(scheme=parse.scheme, netloc=parse.netloc, path=parse.path, params='',
|
||||||
query='',
|
query='',
|
||||||
fragment='').geturl()
|
fragment='').geturl()
|
||||||
|
return result_url[:-1] if result_url.endswith("/") else result_url
|
||||||
|
|
||||||
|
|
||||||
def convert_to_down_model_chunk(row_str: str, chunk_index: int):
|
def convert_to_down_model_chunk(row_str: str, chunk_index: int):
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,9 @@ from setting.models_provider.base_model_provider import MaxKBBaseModel
|
||||||
|
|
||||||
|
|
||||||
class XFChatSparkLLM(MaxKBBaseModel, ChatSparkLLM):
|
class XFChatSparkLLM(MaxKBBaseModel, ChatSparkLLM):
|
||||||
|
@staticmethod
|
||||||
|
def is_cache_model():
|
||||||
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):
|
def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ class ModelSerializer(serializers.Serializer):
|
||||||
class Query(serializers.Serializer):
|
class Query(serializers.Serializer):
|
||||||
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
|
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
|
||||||
|
|
||||||
name = serializers.CharField(required=False, max_length=20,
|
name = serializers.CharField(required=False, max_length=64,
|
||||||
error_messages=ErrMessage.char("模型名称"))
|
error_messages=ErrMessage.char("模型名称"))
|
||||||
|
|
||||||
model_type = serializers.CharField(required=False, error_messages=ErrMessage.char("模型类型"))
|
model_type = serializers.CharField(required=False, error_messages=ErrMessage.char("模型类型"))
|
||||||
|
|
@ -99,7 +99,7 @@ class ModelSerializer(serializers.Serializer):
|
||||||
class Edit(serializers.Serializer):
|
class Edit(serializers.Serializer):
|
||||||
user_id = serializers.CharField(required=False, error_messages=ErrMessage.uuid("用户id"))
|
user_id = serializers.CharField(required=False, error_messages=ErrMessage.uuid("用户id"))
|
||||||
|
|
||||||
name = serializers.CharField(required=False, max_length=20,
|
name = serializers.CharField(required=False, max_length=64,
|
||||||
error_messages=ErrMessage.char("模型名称"))
|
error_messages=ErrMessage.char("模型名称"))
|
||||||
|
|
||||||
model_type = serializers.CharField(required=False, error_messages=ErrMessage.char("模型类型"))
|
model_type = serializers.CharField(required=False, error_messages=ErrMessage.char("模型类型"))
|
||||||
|
|
@ -142,7 +142,7 @@ class ModelSerializer(serializers.Serializer):
|
||||||
class Create(serializers.Serializer):
|
class Create(serializers.Serializer):
|
||||||
user_id = serializers.CharField(required=True, error_messages=ErrMessage.uuid("用户id"))
|
user_id = serializers.CharField(required=True, error_messages=ErrMessage.uuid("用户id"))
|
||||||
|
|
||||||
name = serializers.CharField(required=True, max_length=20, error_messages=ErrMessage.char("模型名称"))
|
name = serializers.CharField(required=True, max_length=64, error_messages=ErrMessage.char("模型名称"))
|
||||||
|
|
||||||
provider = serializers.CharField(required=True, error_messages=ErrMessage.char("供应商"))
|
provider = serializers.CharField(required=True, error_messages=ErrMessage.char("供应商"))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,15 @@ import mimetypes
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
from ..const import CONFIG, PROJECT_DIR
|
from ..const import CONFIG, PROJECT_DIR
|
||||||
|
|
||||||
mimetypes.add_type("text/css", ".css", True)
|
mimetypes.add_type("text/css", ".css", True)
|
||||||
mimetypes.add_type("text/javascript", ".js", True)
|
mimetypes.add_type("text/javascript", ".js", True)
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
Image.MAX_IMAGE_PIXELS = 20000000000
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-never border-r-4 mt-8">
|
<div class="card-never border-r-4 mt-8">
|
||||||
<h5 class="p-8-12">本次对话</h5>
|
<h5 class="p-8-12">本次对话</h5>
|
||||||
<div class="p-8-12 border-t-dashed lighter pre-line">
|
<div class="p-8-12 border-t-dashed lighter pre-wrap">
|
||||||
{{ item.question || '-' }}
|
{{ item.question || '-' }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
</AppAvatar>
|
</AppAvatar>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="text break-all">
|
<div class="text break-all pre-wrap">
|
||||||
{{ item.problem_text }}
|
{{ item.problem_text }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -607,7 +607,7 @@ watch(
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (quickInputRef.value) {
|
if (quickInputRef.value && mode === 'embed') {
|
||||||
quickInputRef.value.textarea.style.height = '0'
|
quickInputRef.value.textarea.style.height = '0'
|
||||||
}
|
}
|
||||||
}, 1800)
|
}, 1800)
|
||||||
|
|
|
||||||
|
|
@ -48,10 +48,10 @@ export default {
|
||||||
upload: 'Upload',
|
upload: 'Upload',
|
||||||
default: 'Default Logo',
|
default: 'Default Logo',
|
||||||
custom: 'Custom',
|
custom: 'Custom',
|
||||||
sizeTip: 'Suggested size 32*32, supports ico, png, size no more than 200KB',
|
sizeTip: 'Suggested size 32*32, supports jpg, png, gif, size no more than 10 MB',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
fileSizeExceeded: 'File size exceeds 200KB',
|
fileSizeExceeded: 'File size exceeds 10 MB',
|
||||||
setSuccess: 'Setting Successful',
|
setSuccess: 'Setting Successful',
|
||||||
uploadImagePrompt: 'Please upload an image'
|
uploadImagePrompt: 'Please upload an image'
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -42,15 +42,15 @@ export default {
|
||||||
disabledSuccess: '已禁用'
|
disabledSuccess: '已禁用'
|
||||||
},
|
},
|
||||||
EditAvatarDialog: {
|
EditAvatarDialog: {
|
||||||
title: '编辑logo',
|
title: '应用头像',
|
||||||
customizeUpload: '自定义上传',
|
customizeUpload: '自定义上传',
|
||||||
upload: '上传',
|
upload: '上传',
|
||||||
default: '默认logo',
|
default: '默认logo',
|
||||||
custom: '自定义',
|
custom: '自定义',
|
||||||
sizeTip: '建议尺寸 32*32,支持 ico、png,大小不超过200KB',
|
sizeTip: '建议尺寸 32*32,支持 JPG、PNG, GIF,大小不超过 10 MB',
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
save: '保存',
|
save: '保存',
|
||||||
fileSizeExceeded: '文件大小超过 200KB',
|
fileSizeExceeded: '文件大小超过 10 MB',
|
||||||
setSuccess: '设置成功',
|
setSuccess: '设置成功',
|
||||||
uploadImagePrompt: '请上传一张图片'
|
uploadImagePrompt: '请上传一张图片'
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -341,8 +341,8 @@ h5 {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pre-line {
|
.pre-wrap {
|
||||||
white-space: pre-line;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
}}</el-button>
|
}}</el-button>
|
||||||
<template #tip>
|
<template #tip>
|
||||||
<div class="el-upload__tip info" style="margin-top: 0">
|
<div class="el-upload__tip info" style="margin-top: 0">
|
||||||
建议尺寸 32*32,支持 JPG、PNG,大小不超过 200 KB
|
建议尺寸 32*32,支持 JPG、PNG, GIF,大小不超过 10 MB
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
|
|
@ -84,7 +84,7 @@
|
||||||
}}</el-button>
|
}}</el-button>
|
||||||
<template #tip>
|
<template #tip>
|
||||||
<div class="el-upload__tip info" style="margin-top: 0">
|
<div class="el-upload__tip info" style="margin-top: 0">
|
||||||
建议尺寸 32*32,支持 JPG、PNG,大小不超过 200 KB
|
建议尺寸 32*32,支持 JPG、PNG, GIF,大小不超过 10 MB
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
|
|
@ -178,8 +178,8 @@ function resetForm() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChange = (file: any, fileList: UploadFiles, attr: string) => {
|
const onChange = (file: any, fileList: UploadFiles, attr: string) => {
|
||||||
//1、判断文件大小是否合法,文件限制不能大于 200KB
|
//1、判断文件大小是否合法,文件限制不能大于 10 MB
|
||||||
const isLimit = file?.size / 1024 < 200
|
const isLimit = file?.size / 1024 / 1024 < 10
|
||||||
if (!isLimit) {
|
if (!isLimit) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
MsgError(t('views.applicationOverview.appInfo.EditAvatarDialog.fileSizeExceeded'))
|
MsgError(t('views.applicationOverview.appInfo.EditAvatarDialog.fileSizeExceeded'))
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<el-dialog :title="$t('views.applicationOverview.appInfo.EditAvatarDialog.title')" v-model="dialogVisible">
|
<el-dialog
|
||||||
|
:title="$t('views.applicationOverview.appInfo.EditAvatarDialog.title')"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
>
|
||||||
<el-radio-group v-model="radioType" class="radio-block mb-16">
|
<el-radio-group v-model="radioType" class="radio-block mb-16">
|
||||||
<div>
|
<div>
|
||||||
<el-radio value="default">
|
<el-radio value="default">
|
||||||
<p>{{$t('views.applicationOverview.appInfo.EditAvatarDialog.default')}}</p>
|
<p>{{ $t('views.applicationOverview.appInfo.EditAvatarDialog.default') }}</p>
|
||||||
<AppAvatar
|
<AppAvatar
|
||||||
v-if="detail?.name"
|
v-if="detail?.name"
|
||||||
:name="detail?.name"
|
:name="detail?.name"
|
||||||
|
|
@ -16,7 +19,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<el-radio value="custom">
|
<el-radio value="custom">
|
||||||
<p>{{$t('views.applicationOverview.appInfo.EditAvatarDialog.customizeUpload')}}</p>
|
<p>{{ $t('views.applicationOverview.appInfo.EditAvatarDialog.customizeUpload') }}</p>
|
||||||
<div class="flex mt-8">
|
<div class="flex mt-8">
|
||||||
<AppAvatar
|
<AppAvatar
|
||||||
v-if="fileURL"
|
v-if="fileURL"
|
||||||
|
|
@ -35,19 +38,25 @@
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
:on-change="onChange"
|
:on-change="onChange"
|
||||||
>
|
>
|
||||||
<el-button icon="Upload" :disabled="radioType !== 'custom'">{{$t('views.applicationOverview.appInfo.EditAvatarDialog.upload')}}</el-button>
|
<el-button icon="Upload" :disabled="radioType !== 'custom'">{{
|
||||||
|
$t('views.applicationOverview.appInfo.EditAvatarDialog.upload')
|
||||||
|
}}</el-button>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
</div>
|
</div>
|
||||||
<div class="el-upload__tip info mt-16">
|
<div class="el-upload__tip info mt-16">
|
||||||
{{$t('views.applicationOverview.appInfo.EditAvatarDialog.sizeTip')}}
|
{{ $t('views.applicationOverview.appInfo.EditAvatarDialog.sizeTip') }}
|
||||||
</div>
|
</div>
|
||||||
</el-radio>
|
</el-radio>
|
||||||
</div>
|
</div>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<span class="dialog-footer">
|
||||||
<el-button @click.prevent="dialogVisible = false"> {{$t('views.applicationOverview.appInfo.EditAvatarDialog.cancel')}}</el-button>
|
<el-button @click.prevent="dialogVisible = false">
|
||||||
<el-button type="primary" @click="submit" :loading="loading"> {{$t('views.applicationOverview.appInfo.EditAvatarDialog.save')}}</el-button>
|
{{ $t('views.applicationOverview.appInfo.EditAvatarDialog.cancel') }}</el-button
|
||||||
|
>
|
||||||
|
<el-button type="primary" @click="submit" :loading="loading">
|
||||||
|
{{ $t('views.applicationOverview.appInfo.EditAvatarDialog.save') }}</el-button
|
||||||
|
>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
@ -94,17 +103,16 @@ const open = (data: any) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChange = (file: any) => {
|
const onChange = (file: any) => {
|
||||||
//1、判断文件大小是否合法,文件限制不能大于 200KB
|
//1、判断文件大小是否合法,文件限制不能大于10MB
|
||||||
const isLimit = file?.size / 1024 < 200
|
const isLimit = file?.size / 1024 / 1024 < 10
|
||||||
if (!isLimit) {
|
if (!isLimit) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
MsgError(t('views.applicationOverview.appInfo.EditAvatarDialog.fileSizeExceeded'))
|
MsgError(t('views.applicationOverview.appInfo.EditAvatarDialog.fileSizeExceeded'))
|
||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
iconFile.value = file
|
iconFile.value = file
|
||||||
fileURL.value = URL.createObjectURL(file.raw)
|
fileURL.value = URL.createObjectURL(file.raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function submit() {
|
function submit() {
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
<AppIcon iconName="app-copy"></AppIcon>
|
<AppIcon iconName="app-copy"></AppIcon>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-8 white-space">
|
<div class="mt-8 pre-wrap">
|
||||||
{{ source2 }}
|
{{ source2 }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -109,9 +109,6 @@ defineExpose({ open })
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
height: 180px;
|
height: 180px;
|
||||||
.white-space {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
@mousedown="onmousedown(item)"
|
@mousedown="onmousedown(item)"
|
||||||
>
|
>
|
||||||
<component :is="iconComponent(`${item.type}-icon`)" class="mr-8 mt-4" :size="32" />
|
<component :is="iconComponent(`${item.type}-icon`)" class="mr-8 mt-4" :size="32" />
|
||||||
<div class="pre-line">
|
<div class="pre-wrap">
|
||||||
<div class="lighter">{{ item.label }}</div>
|
<div class="lighter">{{ item.label }}</div>
|
||||||
<el-text type="info" size="small">{{ item.text }}</el-text>
|
<el-text type="info" size="small">{{ item.text }}</el-text>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -114,6 +114,7 @@ import { MsgSuccess, MsgConfirm, MsgError } from '@/utils/message'
|
||||||
import { datetimeFormat } from '@/utils/time'
|
import { datetimeFormat } from '@/utils/time'
|
||||||
import useStore from '@/stores'
|
import useStore from '@/stores'
|
||||||
import { WorkFlowInstance } from '@/workflow/common/validate'
|
import { WorkFlowInstance } from '@/workflow/common/validate'
|
||||||
|
import { hasPermission } from '@/utils/permission'
|
||||||
|
|
||||||
const { user, application } = useStore()
|
const { user, application } = useStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
@ -250,7 +251,9 @@ const closeInterval = () => {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getDetail()
|
getDetail()
|
||||||
// 初始化定时任务
|
// 初始化定时任务
|
||||||
initInterval()
|
if (hasPermission(`APPLICATION:MANAGE:${id}`, 'AND')) {
|
||||||
|
initInterval()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<span v-else class="pre-line">{{ form?.content }}</span>
|
<span v-else class="pre-wrap">{{ form?.content }}</span>
|
||||||
</div>
|
</div>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,10 @@ const props = defineProps({
|
||||||
})
|
})
|
||||||
|
|
||||||
function zoomIn() {
|
function zoomIn() {
|
||||||
props.lf?.zoom(true)
|
props.lf?.zoom(true, [0, 0])
|
||||||
}
|
}
|
||||||
function zoomOut() {
|
function zoomOut() {
|
||||||
props.lf?.zoom(false)
|
props.lf?.zoom(false, [0, 0])
|
||||||
}
|
}
|
||||||
function fitView() {
|
function fitView() {
|
||||||
props.lf?.resetZoom()
|
props.lf?.resetZoom()
|
||||||
|
|
|
||||||
|
|
@ -170,3 +170,13 @@ export const nodeDict: any = {
|
||||||
export function isWorkFlow(type: string | undefined) {
|
export function isWorkFlow(type: string | undefined) {
|
||||||
return type === 'WORK_FLOW'
|
return type === 'WORK_FLOW'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isLastNode(nodeModel: any) {
|
||||||
|
const incoming = nodeModel.graphModel.getNodeIncomingNode(nodeModel.id)
|
||||||
|
const outcomming = nodeModel.graphModel.getNodeOutgoingNode(nodeModel.id)
|
||||||
|
if (incoming.length > 0 && outcomming.length === 0) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,13 +115,23 @@
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-input
|
<MdEditor
|
||||||
|
@wheel="wheel"
|
||||||
|
@keydown="isKeyDown = true"
|
||||||
|
@keyup="isKeyDown = false"
|
||||||
|
class="reply-node-editor"
|
||||||
|
style="height: 150px"
|
||||||
v-model="chat_data.prompt"
|
v-model="chat_data.prompt"
|
||||||
:rows="6"
|
:preview="false"
|
||||||
type="textarea"
|
:toolbars="[]"
|
||||||
maxlength="2048"
|
:footers="footers"
|
||||||
:placeholder="defaultPrompt"
|
>
|
||||||
/>
|
<template #defFooters>
|
||||||
|
<el-button text type="info" @click="openDialog">
|
||||||
|
<AppIcon iconName="app-magnify" style="font-size: 16px"></AppIcon>
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</MdEditor>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="历史聊天记录">
|
<el-form-item label="历史聊天记录">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
|
|
@ -132,9 +142,34 @@
|
||||||
class="w-full"
|
class="w-full"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="返回内容" @click.prevent>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex align-center">
|
||||||
|
<div class="mr-4">
|
||||||
|
<span>返回内容<span class="danger">*</span></span>
|
||||||
|
</div>
|
||||||
|
<el-tooltip effect="dark" placement="right" popper-class="max-w-200">
|
||||||
|
<template #content>
|
||||||
|
关闭后该节点的内容则不输出给用户。
|
||||||
|
如果你想让用户看到该节点的输出内容,请打开开关。
|
||||||
|
</template>
|
||||||
|
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-switch size="small" v-model="chat_data.is_result" />
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
<!-- 回复内容弹出层 -->
|
||||||
|
<el-dialog v-model="dialogVisible" title="提示词" append-to-body>
|
||||||
|
<MdEditor v-model="cloneContent" :preview="false" :toolbars="[]" :footers="[]"> </MdEditor>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer mt-24">
|
||||||
|
<el-button type="primary" @click="submitDialog"> 确认 </el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
<!-- 添加模版 -->
|
<!-- 添加模版 -->
|
||||||
<CreateModelDialog
|
<CreateModelDialog
|
||||||
ref="createModelRef"
|
ref="createModelRef"
|
||||||
|
|
@ -156,6 +191,7 @@ import applicationApi from '@/api/application'
|
||||||
import useStore from '@/stores'
|
import useStore from '@/stores'
|
||||||
import { relatedObject } from '@/utils/utils'
|
import { relatedObject } from '@/utils/utils'
|
||||||
import type { Provider } from '@/api/type/model'
|
import type { Provider } from '@/api/type/model'
|
||||||
|
import { isLastNode } from '@/workflow/common/data'
|
||||||
|
|
||||||
const { model } = useStore()
|
const { model } = useStore()
|
||||||
const isKeyDown = ref(false)
|
const isKeyDown = ref(false)
|
||||||
|
|
@ -167,6 +203,17 @@ const wheel = (e: any) => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const cloneContent = ref('')
|
||||||
|
const footers: any = [null, '=', 0]
|
||||||
|
function openDialog() {
|
||||||
|
cloneContent.value = chat_data.value.prompt
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
function submitDialog() {
|
||||||
|
set(props.nodeModel.properties.node_data, 'prompt', cloneContent.value)
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
const {
|
const {
|
||||||
params: { id }
|
params: { id }
|
||||||
} = app.config.globalProperties.$route as any
|
} = app.config.globalProperties.$route as any
|
||||||
|
|
@ -180,7 +227,8 @@ const form = {
|
||||||
model_id: '',
|
model_id: '',
|
||||||
system: '',
|
system: '',
|
||||||
prompt: defaultPrompt,
|
prompt: defaultPrompt,
|
||||||
dialogue_number: 1
|
dialogue_number: 1,
|
||||||
|
is_result: false
|
||||||
}
|
}
|
||||||
|
|
||||||
const chat_data = computed({
|
const chat_data = computed({
|
||||||
|
|
@ -240,7 +288,17 @@ const openCreateModel = (provider?: Provider) => {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getProvider()
|
getProvider()
|
||||||
getModel()
|
getModel()
|
||||||
|
if (isLastNode(props.nodeModel)) {
|
||||||
|
set(props.nodeModel.properties.node_data, 'is_result', true)
|
||||||
|
}
|
||||||
|
|
||||||
set(props.nodeModel, 'validate', validate)
|
set(props.nodeModel, 'validate', validate)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped>
|
||||||
|
.reply-node-editor {
|
||||||
|
:deep(.md-editor-footer) {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -40,14 +40,32 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="开场白">
|
<el-form-item label="开场白">
|
||||||
<MdEditor
|
<MdEditor
|
||||||
|
@wheel="wheel"
|
||||||
|
@keydown="isKeyDown = true"
|
||||||
|
@keyup="isKeyDown = false"
|
||||||
style="height: 150px"
|
style="height: 150px"
|
||||||
v-model="form_data.prologue"
|
v-model="form_data.prologue"
|
||||||
:preview="false"
|
:preview="false"
|
||||||
:toolbars="[]"
|
:toolbars="[]"
|
||||||
:footers="[]"
|
class="reply-node-editor"
|
||||||
/>
|
:footers="footers"
|
||||||
|
>
|
||||||
|
<template #defFooters>
|
||||||
|
<el-button text type="info" @click="openDialog">
|
||||||
|
<AppIcon iconName="app-magnify" style="font-size: 16px"></AppIcon>
|
||||||
|
</el-button> </template
|
||||||
|
></MdEditor>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
<!-- 回复内容弹出层 -->
|
||||||
|
<el-dialog v-model="dialogVisible" title="开场白" append-to-body>
|
||||||
|
<MdEditor v-model="cloneContent" :preview="false" :toolbars="[]" :footers="[]"> </MdEditor>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer mt-24">
|
||||||
|
<el-button type="primary" @click="submitDialog"> 确认 </el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</NodeContainer>
|
</NodeContainer>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
@ -63,6 +81,26 @@ const form = {
|
||||||
prologue:
|
prologue:
|
||||||
'您好,我是 MaxKB 小助手,您可以向我提出 MaxKB 使用问题。\n- MaxKB 主要功能有什么?\n- MaxKB 支持哪些大语言模型?\n- MaxKB 支持哪些文档类型?'
|
'您好,我是 MaxKB 小助手,您可以向我提出 MaxKB 使用问题。\n- MaxKB 主要功能有什么?\n- MaxKB 支持哪些大语言模型?\n- MaxKB 支持哪些文档类型?'
|
||||||
}
|
}
|
||||||
|
const isKeyDown = ref(false)
|
||||||
|
const wheel = (e: any) => {
|
||||||
|
if (isKeyDown.value) {
|
||||||
|
e.preventDefault()
|
||||||
|
} else {
|
||||||
|
e.stopPropagation()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const cloneContent = ref('')
|
||||||
|
const footers: any = [null, '=', 0]
|
||||||
|
function openDialog() {
|
||||||
|
cloneContent.value = form_data.value.prologue
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
function submitDialog() {
|
||||||
|
set(props.nodeModel.properties.node_data, 'prologue', cloneContent.value)
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
const form_data = computed({
|
const form_data = computed({
|
||||||
get: () => {
|
get: () => {
|
||||||
if (props.nodeModel.properties.node_data) {
|
if (props.nodeModel.properties.node_data) {
|
||||||
|
|
@ -89,4 +127,10 @@ onMounted(() => {
|
||||||
set(props.nodeModel, 'validate', validate)
|
set(props.nodeModel, 'validate', validate)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped>
|
||||||
|
.reply-node-editor {
|
||||||
|
:deep(.md-editor-footer) {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -116,13 +116,23 @@
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-input
|
<MdEditor
|
||||||
|
@wheel="wheel"
|
||||||
|
@keydown="isKeyDown = true"
|
||||||
|
@keyup="isKeyDown = false"
|
||||||
|
class="reply-node-editor"
|
||||||
|
style="height: 150px"
|
||||||
v-model="form_data.prompt"
|
v-model="form_data.prompt"
|
||||||
:rows="6"
|
:preview="false"
|
||||||
type="textarea"
|
:toolbars="[]"
|
||||||
maxlength="2048"
|
:footers="footers"
|
||||||
:placeholder="defaultPrompt"
|
>
|
||||||
/>
|
<template #defFooters>
|
||||||
|
<el-button text type="info" @click="openDialog">
|
||||||
|
<AppIcon iconName="app-magnify" style="font-size: 16px"></AppIcon>
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</MdEditor>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="历史聊天记录">
|
<el-form-item label="历史聊天记录">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
|
|
@ -133,8 +143,34 @@
|
||||||
class="w-full"
|
class="w-full"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="返回内容" @click.prevent>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex align-center">
|
||||||
|
<div class="mr-4">
|
||||||
|
<span>返回内容<span class="danger">*</span></span>
|
||||||
|
</div>
|
||||||
|
<el-tooltip effect="dark" placement="right" popper-class="max-w-200">
|
||||||
|
<template #content>
|
||||||
|
关闭后该节点的内容则不输出给用户。
|
||||||
|
如果你想让用户看到该节点的输出内容,请打开开关。
|
||||||
|
</template>
|
||||||
|
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-switch size="small" v-model="form_data.is_result" />
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
<!-- 回复内容弹出层 -->
|
||||||
|
<el-dialog v-model="dialogVisible" title="提示词" append-to-body>
|
||||||
|
<MdEditor v-model="cloneContent" :preview="false" :toolbars="[]" :footers="[]"> </MdEditor>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer mt-24">
|
||||||
|
<el-button type="primary" @click="submitDialog"> 确认 </el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
<!-- 添加模版 -->
|
<!-- 添加模版 -->
|
||||||
<CreateModelDialog
|
<CreateModelDialog
|
||||||
ref="createModelRef"
|
ref="createModelRef"
|
||||||
|
|
@ -156,6 +192,8 @@ import applicationApi from '@/api/application'
|
||||||
import useStore from '@/stores'
|
import useStore from '@/stores'
|
||||||
import { relatedObject } from '@/utils/utils'
|
import { relatedObject } from '@/utils/utils'
|
||||||
import type { Provider } from '@/api/type/model'
|
import type { Provider } from '@/api/type/model'
|
||||||
|
import { isLastNode } from '@/workflow/common/data'
|
||||||
|
|
||||||
const { model } = useStore()
|
const { model } = useStore()
|
||||||
const isKeyDown = ref(false)
|
const isKeyDown = ref(false)
|
||||||
const wheel = (e: any) => {
|
const wheel = (e: any) => {
|
||||||
|
|
@ -166,6 +204,17 @@ const wheel = (e: any) => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const cloneContent = ref('')
|
||||||
|
const footers: any = [null, '=', 0]
|
||||||
|
function openDialog() {
|
||||||
|
cloneContent.value = form_data.value.prompt
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
function submitDialog() {
|
||||||
|
set(props.nodeModel.properties.node_data, 'prompt', cloneContent.value)
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
const {
|
const {
|
||||||
params: { id }
|
params: { id }
|
||||||
} = app.config.globalProperties.$route as any
|
} = app.config.globalProperties.$route as any
|
||||||
|
|
@ -177,7 +226,8 @@ const form = {
|
||||||
model_id: '',
|
model_id: '',
|
||||||
system: '你是一个问题优化大师',
|
system: '你是一个问题优化大师',
|
||||||
prompt: defaultPrompt,
|
prompt: defaultPrompt,
|
||||||
dialogue_number: 1
|
dialogue_number: 1,
|
||||||
|
is_result: false
|
||||||
}
|
}
|
||||||
|
|
||||||
const form_data = computed({
|
const form_data = computed({
|
||||||
|
|
@ -237,7 +287,16 @@ const openCreateModel = (provider?: Provider) => {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getProvider()
|
getProvider()
|
||||||
getModel()
|
getModel()
|
||||||
|
if (isLastNode(props.nodeModel)) {
|
||||||
|
set(props.nodeModel.properties.node_data, 'is_result', true)
|
||||||
|
}
|
||||||
set(props.nodeModel, 'validate', validate)
|
set(props.nodeModel, 'validate', validate)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped>
|
||||||
|
.reply-node-editor {
|
||||||
|
:deep(.md-editor-footer) {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,12 @@
|
||||||
<template #label>
|
<template #label>
|
||||||
<div class="flex-between">
|
<div class="flex-between">
|
||||||
<span>回复内容</span>
|
<span>回复内容</span>
|
||||||
<el-select v-model="form_data.reply_type" size="small" style="width: 85px">
|
<el-select
|
||||||
|
:teleported="false"
|
||||||
|
v-model="form_data.reply_type"
|
||||||
|
size="small"
|
||||||
|
style="width: 85px"
|
||||||
|
>
|
||||||
<el-option label="引用变量" value="referencing" />
|
<el-option label="引用变量" value="referencing" />
|
||||||
<el-option label="自定义" value="content" />
|
<el-option label="自定义" value="content" />
|
||||||
</el-select>
|
</el-select>
|
||||||
|
|
@ -24,6 +29,9 @@
|
||||||
</template>
|
</template>
|
||||||
<MdEditor
|
<MdEditor
|
||||||
v-if="form_data.reply_type === 'content'"
|
v-if="form_data.reply_type === 'content'"
|
||||||
|
@wheel="wheel"
|
||||||
|
@keydown="isKeyDown = true"
|
||||||
|
@keyup="isKeyDown = false"
|
||||||
class="reply-node-editor"
|
class="reply-node-editor"
|
||||||
style="height: 150px"
|
style="height: 150px"
|
||||||
v-model="form_data.content"
|
v-model="form_data.content"
|
||||||
|
|
@ -46,6 +54,23 @@
|
||||||
v-model="form_data.fields"
|
v-model="form_data.fields"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="返回内容" @click.prevent>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex align-center">
|
||||||
|
<div class="mr-4">
|
||||||
|
<span>返回内容<span class="danger">*</span></span>
|
||||||
|
</div>
|
||||||
|
<el-tooltip effect="dark" placement="right" popper-class="max-w-200">
|
||||||
|
<template #content>
|
||||||
|
关闭后该节点的内容则不输出给用户。
|
||||||
|
如果你想让用户看到该节点的输出内容,请打开开关。
|
||||||
|
</template>
|
||||||
|
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-switch size="small" v-model="form_data.is_result" />
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-card>
|
</el-card>
|
||||||
<!-- 回复内容弹出层 -->
|
<!-- 回复内容弹出层 -->
|
||||||
|
|
@ -64,12 +89,23 @@ import { set } from 'lodash'
|
||||||
import NodeContainer from '@/workflow/common/NodeContainer.vue'
|
import NodeContainer from '@/workflow/common/NodeContainer.vue'
|
||||||
import NodeCascader from '@/workflow/common/NodeCascader.vue'
|
import NodeCascader from '@/workflow/common/NodeCascader.vue'
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { isLastNode } from '@/workflow/common/data'
|
||||||
|
|
||||||
const props = defineProps<{ nodeModel: any }>()
|
const props = defineProps<{ nodeModel: any }>()
|
||||||
|
const isKeyDown = ref(false)
|
||||||
|
const wheel = (e: any) => {
|
||||||
|
if (isKeyDown.value) {
|
||||||
|
e.preventDefault()
|
||||||
|
} else {
|
||||||
|
e.stopPropagation()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
const form = {
|
const form = {
|
||||||
reply_type: 'content',
|
reply_type: 'content',
|
||||||
content: '',
|
content: '',
|
||||||
fields: []
|
fields: [],
|
||||||
|
is_result: false
|
||||||
}
|
}
|
||||||
const footers: any = [null, '=', 0]
|
const footers: any = [null, '=', 0]
|
||||||
|
|
||||||
|
|
@ -111,6 +147,10 @@ const validate = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
if (isLastNode(props.nodeModel)) {
|
||||||
|
set(props.nodeModel.properties.node_data, 'is_result', true)
|
||||||
|
}
|
||||||
|
|
||||||
set(props.nodeModel, 'validate', validate)
|
set(props.nodeModel, 'validate', validate)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue