Merge remote-tracking branch 'origin/main'

This commit is contained in:
liqiang-fit2cloud 2024-09-18 14:56:55 +08:00
commit 47e24d87a6
68 changed files with 873 additions and 453 deletions

View File

@ -37,6 +37,8 @@ class IGenerateHumanMessageStep(IBaseChatPipelineStep):
"最大携带知识库段落长度")) "最大携带知识库段落长度"))
# 模板 # 模板
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char("提示词")) prompt = serializers.CharField(required=True, error_messages=ErrMessage.char("提示词"))
system = serializers.CharField(required=False, allow_null=True, allow_blank=True,
error_messages=ErrMessage.char("系统提示词(角色)"))
# 补齐问题 # 补齐问题
padding_problem_text = serializers.CharField(required=False, error_messages=ErrMessage.char("补齐问题")) padding_problem_text = serializers.CharField(required=False, error_messages=ErrMessage.char("补齐问题"))
# 未查询到引用分段 # 未查询到引用分段
@ -59,6 +61,7 @@ class IGenerateHumanMessageStep(IBaseChatPipelineStep):
prompt: str, prompt: str,
padding_problem_text: str = None, padding_problem_text: str = None,
no_references_setting=None, no_references_setting=None,
system=None,
**kwargs) -> List[BaseMessage]: **kwargs) -> List[BaseMessage]:
""" """
@ -71,6 +74,7 @@ class IGenerateHumanMessageStep(IBaseChatPipelineStep):
:param padding_problem_text 用户修改文本 :param padding_problem_text 用户修改文本
:param kwargs: 其他参数 :param kwargs: 其他参数
:param no_references_setting: 无引用分段设置 :param no_references_setting: 无引用分段设置
:param system 系统提示称
:return: :return:
""" """
pass pass

View File

@ -9,6 +9,7 @@
from typing import List, Dict from typing import List, Dict
from langchain.schema import BaseMessage, HumanMessage from langchain.schema import BaseMessage, HumanMessage
from langchain_core.messages import SystemMessage
from application.chat_pipeline.I_base_chat_pipeline import ParagraphPipelineModel from application.chat_pipeline.I_base_chat_pipeline import ParagraphPipelineModel
from application.chat_pipeline.step.generate_human_message_step.i_generate_human_message_step import \ from application.chat_pipeline.step.generate_human_message_step.i_generate_human_message_step import \
@ -27,6 +28,7 @@ class BaseGenerateHumanMessageStep(IGenerateHumanMessageStep):
prompt: str, prompt: str,
padding_problem_text: str = None, padding_problem_text: str = None,
no_references_setting=None, no_references_setting=None,
system=None,
**kwargs) -> List[BaseMessage]: **kwargs) -> List[BaseMessage]:
prompt = prompt if (paragraph_list is not None and len(paragraph_list) > 0) else no_references_setting.get( prompt = prompt if (paragraph_list is not None and len(paragraph_list) > 0) else no_references_setting.get(
'value') 'value')
@ -35,6 +37,11 @@ class BaseGenerateHumanMessageStep(IGenerateHumanMessageStep):
history_message = [[history_chat_record[index].get_human_message(), history_chat_record[index].get_ai_message()] history_message = [[history_chat_record[index].get_human_message(), history_chat_record[index].get_ai_message()]
for index in for index in
range(start_index if start_index > 0 else 0, len(history_chat_record))] range(start_index if start_index > 0 else 0, len(history_chat_record))]
if system is not None and len(system) > 0:
return [SystemMessage(system), *flat_map(history_message),
self.to_human_message(prompt, exec_problem_text, max_paragraph_char_number, paragraph_list,
no_references_setting)]
return [*flat_map(history_message), return [*flat_map(history_message),
self.to_human_message(prompt, exec_problem_text, max_paragraph_char_number, paragraph_list, self.to_human_message(prompt, exec_problem_text, max_paragraph_char_number, paragraph_list,
no_references_setting)] no_references_setting)]

View File

@ -29,6 +29,8 @@ class IResetProblemStep(IBaseChatPipelineStep):
error_messages=ErrMessage.list("历史对答")) error_messages=ErrMessage.list("历史对答"))
# 大语言模型 # 大语言模型
chat_model = ModelField(required=False, allow_null=True, error_messages=ErrMessage.base("大语言模型")) chat_model = ModelField(required=False, allow_null=True, error_messages=ErrMessage.base("大语言模型"))
problem_optimization_prompt = serializers.CharField(required=False, max_length=102400,
error_messages=ErrMessage.char("问题补全提示词"))
def get_step_serializer(self, manage: PipelineManage) -> Type[serializers.Serializer]: def get_step_serializer(self, manage: PipelineManage) -> Type[serializers.Serializer]:
return self.InstanceSerializer return self.InstanceSerializer
@ -47,5 +49,6 @@ class IResetProblemStep(IBaseChatPipelineStep):
@abstractmethod @abstractmethod
def execute(self, problem_text: str, history_chat_record: List[ChatRecord] = None, chat_model: BaseChatModel = None, def execute(self, problem_text: str, history_chat_record: List[ChatRecord] = None, chat_model: BaseChatModel = None,
problem_optimization_prompt=None,
**kwargs): **kwargs):
pass pass

View File

@ -21,6 +21,7 @@ prompt = (
class BaseResetProblemStep(IResetProblemStep): class BaseResetProblemStep(IResetProblemStep):
def execute(self, problem_text: str, history_chat_record: List[ChatRecord] = None, chat_model: BaseChatModel = None, def execute(self, problem_text: str, history_chat_record: List[ChatRecord] = None, chat_model: BaseChatModel = None,
problem_optimization_prompt=None,
**kwargs) -> str: **kwargs) -> str:
if chat_model is None: if chat_model is None:
self.context['message_tokens'] = 0 self.context['message_tokens'] = 0
@ -30,8 +31,9 @@ class BaseResetProblemStep(IResetProblemStep):
history_message = [[history_chat_record[index].get_human_message(), history_chat_record[index].get_ai_message()] history_message = [[history_chat_record[index].get_human_message(), history_chat_record[index].get_ai_message()]
for index in for index in
range(start_index if start_index > 0 else 0, len(history_chat_record))] range(start_index if start_index > 0 else 0, len(history_chat_record))]
reset_prompt = problem_optimization_prompt if problem_optimization_prompt else prompt
message_list = [*flat_map(history_message), message_list = [*flat_map(history_message),
HumanMessage(content=prompt.format(**{'question': problem_text}))] HumanMessage(content=reset_prompt.replace('{question}', problem_text))]
response = chat_model.invoke(message_list) response = chat_model.invoke(message_list)
padding_problem = problem_text padding_problem = problem_text
if response.content.__contains__("<data>") and response.content.__contains__('</data>'): if response.content.__contains__("<data>") and response.content.__contains__('</data>'):
@ -39,6 +41,9 @@ class BaseResetProblemStep(IResetProblemStep):
response.content.index('<data>') + 6:response.content.index('</data>')] response.content.index('<data>') + 6:response.content.index('</data>')]
if padding_problem_data is not None and len(padding_problem_data.strip()) > 0: if padding_problem_data is not None and len(padding_problem_data.strip()) > 0:
padding_problem = padding_problem_data padding_problem = padding_problem_data
elif len(response.content) > 0:
padding_problem = response.content
try: try:
request_token = chat_model.get_num_tokens_from_messages(message_list) request_token = chat_model.get_num_tokens_from_messages(message_list)
response_token = chat_model.get_num_tokens(padding_problem) response_token = chat_model.get_num_tokens(padding_problem)

View File

@ -16,9 +16,6 @@ from application.flow.i_step_node import INode, NodeResult
class IStarNode(INode): class IStarNode(INode):
type = 'start-node' type = 'start-node'
def get_node_params_serializer_class(self) -> Type[serializers.Serializer] | None:
return None
def _run(self): def _run(self):
return self.execute(**self.flow_params_serializer.data) return self.execute(**self.flow_params_serializer.data)

View File

@ -15,11 +15,16 @@ from application.flow.step_node.start_node.i_start_node import IStarNode
class BaseStartStepNode(IStarNode): class BaseStartStepNode(IStarNode):
def execute(self, question, **kwargs) -> NodeResult: def execute(self, question, **kwargs) -> NodeResult:
history_chat_record = self.flow_params_serializer.data.get('history_chat_record', [])
history_context = [{'question': chat_record.problem_text, 'answer': chat_record.answer_text} for chat_record in
history_chat_record]
chat_id = self.flow_params_serializer.data.get('chat_id')
""" """
开始节点 初始化全局变量 开始节点 初始化全局变量
""" """
return NodeResult({'question': question}, return NodeResult({'question': question},
{'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'start_time': time.time()}) {'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'start_time': time.time(),
'history_context': history_context, 'chat_id': str(chat_id)})
def get_details(self, index: int, **kwargs): def get_details(self, index: int, **kwargs):
global_fields = [] global_fields = []

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.15 on 2024-09-13 18:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('application', '0013_application_tts_type'),
]
operations = [
migrations.AddField(
model_name='application',
name='problem_optimization_prompt',
field=models.CharField(blank=True, default='()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中', max_length=102400, null=True, verbose_name='问题优化提示词'),
),
]

View File

@ -35,7 +35,7 @@ def get_dataset_setting_dict():
def get_model_setting_dict(): def get_model_setting_dict():
return {'prompt': Application.get_default_model_prompt()} return {'prompt': Application.get_default_model_prompt(), 'no_references_prompt': '{question}'}
class Application(AppModelMixin): class Application(AppModelMixin):
@ -54,8 +54,13 @@ class Application(AppModelMixin):
work_flow = models.JSONField(verbose_name="工作流数据", default=dict) work_flow = models.JSONField(verbose_name="工作流数据", default=dict)
type = models.CharField(verbose_name="应用类型", choices=ApplicationTypeChoices.choices, type = models.CharField(verbose_name="应用类型", choices=ApplicationTypeChoices.choices,
default=ApplicationTypeChoices.SIMPLE, max_length=256) default=ApplicationTypeChoices.SIMPLE, max_length=256)
tts_model = models.ForeignKey(Model, related_name='tts_model_id', on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) problem_optimization_prompt = models.CharField(verbose_name="问题优化提示词", max_length=102400, blank=True,
stt_model = models.ForeignKey(Model, related_name='stt_model_id', on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) null=True,
default="()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中")
tts_model = models.ForeignKey(Model, related_name='tts_model_id', on_delete=models.SET_NULL, db_constraint=False,
blank=True, null=True)
stt_model = models.ForeignKey(Model, related_name='stt_model_id', on_delete=models.SET_NULL, db_constraint=False,
blank=True, null=True)
tts_model_enable = models.BooleanField(verbose_name="语音合成模型是否启用", default=False) tts_model_enable = models.BooleanField(verbose_name="语音合成模型是否启用", default=False)
stt_model_enable = models.BooleanField(verbose_name="语音识别模型是否启用", default=False) stt_model_enable = models.BooleanField(verbose_name="语音识别模型是否启用", default=False)
tts_type = models.CharField(verbose_name="语音播放类型", max_length=20, default="BROWSER") tts_type = models.CharField(verbose_name="语音播放类型", max_length=20, default="BROWSER")

View File

@ -120,7 +120,12 @@ class DatasetSettingSerializer(serializers.Serializer):
class ModelSettingSerializer(serializers.Serializer): class ModelSettingSerializer(serializers.Serializer):
prompt = serializers.CharField(required=True, max_length=2048, error_messages=ErrMessage.char("提示词")) prompt = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400,
error_messages=ErrMessage.char("提示词"))
system = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400,
error_messages=ErrMessage.char("角色提示词"))
no_references_prompt = serializers.CharField(required=True, max_length=102400, allow_null=True, allow_blank=True,
error_messages=ErrMessage.char("无引用分段提示词"))
class ApplicationWorkflowSerializer(serializers.Serializer): class ApplicationWorkflowSerializer(serializers.Serializer):
@ -174,7 +179,7 @@ class ApplicationSerializer(serializers.Serializer):
error_messages=ErrMessage.char("应用描述")) error_messages=ErrMessage.char("应用描述"))
model_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, model_id = serializers.CharField(required=False, allow_null=True, allow_blank=True,
error_messages=ErrMessage.char("模型")) error_messages=ErrMessage.char("模型"))
multiple_rounds_dialogue = serializers.BooleanField(required=True, error_messages=ErrMessage.char("多轮对话")) dialogue_number = serializers.BooleanField(required=True, error_messages=ErrMessage.char("会话次数"))
prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=4096, prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=4096,
error_messages=ErrMessage.char("开场白")) error_messages=ErrMessage.char("开场白"))
dataset_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True), dataset_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True),
@ -185,6 +190,8 @@ class ApplicationSerializer(serializers.Serializer):
model_setting = ModelSettingSerializer(required=True) model_setting = ModelSettingSerializer(required=True)
# 问题补全 # 问题补全
problem_optimization = serializers.BooleanField(required=True, error_messages=ErrMessage.boolean("问题补全")) problem_optimization = serializers.BooleanField(required=True, error_messages=ErrMessage.boolean("问题补全"))
problem_optimization_prompt = serializers.CharField(required=False, max_length=102400,
error_messages=ErrMessage.char("问题补全提示词"))
# 应用类型 # 应用类型
type = serializers.CharField(required=True, error_messages=ErrMessage.char("应用类型"), type = serializers.CharField(required=True, error_messages=ErrMessage.char("应用类型"),
validators=[ validators=[
@ -364,8 +371,8 @@ class ApplicationSerializer(serializers.Serializer):
error_messages=ErrMessage.char("应用描述")) error_messages=ErrMessage.char("应用描述"))
model_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, model_id = serializers.CharField(required=False, allow_blank=True, allow_null=True,
error_messages=ErrMessage.char("模型")) error_messages=ErrMessage.char("模型"))
multiple_rounds_dialogue = serializers.BooleanField(required=False, dialogue_number = serializers.IntegerField(required=False,
error_messages=ErrMessage.boolean("多轮会话")) error_messages=ErrMessage.boolean("多轮会话"))
prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=4096, prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=4096,
error_messages=ErrMessage.char("开场白")) error_messages=ErrMessage.char("开场白"))
dataset_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True), dataset_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True),
@ -430,13 +437,14 @@ class ApplicationSerializer(serializers.Serializer):
def to_application_model(user_id: str, application: Dict): def to_application_model(user_id: str, application: Dict):
return Application(id=uuid.uuid1(), name=application.get('name'), desc=application.get('desc'), return Application(id=uuid.uuid1(), name=application.get('name'), desc=application.get('desc'),
prologue=application.get('prologue'), prologue=application.get('prologue'),
dialogue_number=3 if application.get('multiple_rounds_dialogue') else 0, dialogue_number=application.get('dialogue_number', 0),
user_id=user_id, model_id=application.get('model_id'), user_id=user_id, model_id=application.get('model_id'),
dataset_setting=application.get('dataset_setting'), dataset_setting=application.get('dataset_setting'),
model_setting=application.get('model_setting'), model_setting=application.get('model_setting'),
problem_optimization=application.get('problem_optimization'), problem_optimization=application.get('problem_optimization'),
type=ApplicationTypeChoices.SIMPLE, type=ApplicationTypeChoices.SIMPLE,
model_params_setting=application.get('model_params_setting', {}), model_params_setting=application.get('model_params_setting', {}),
problem_optimization_prompt=application.get('problem_optimization_prompt', None),
work_flow={} work_flow={}
) )
@ -601,7 +609,8 @@ class ApplicationSerializer(serializers.Serializer):
if with_valid: if with_valid:
self.is_valid(raise_exception=True) self.is_valid(raise_exception=True)
application = QuerySet(Application).filter(id=self.data.get("application_id")).first() application = QuerySet(Application).filter(id=self.data.get("application_id")).first()
return FunctionLibSerializer.Query(data={'user_id': application.user_id}).list(with_valid=True) return FunctionLibSerializer.Query(data={'user_id': application.user_id, 'is_active': True}).list(
with_valid=True)
def get_function_lib(self, function_lib_id, with_valid=True): def get_function_lib(self, function_lib_id, with_valid=True):
if with_valid: if with_valid:

View File

@ -60,6 +60,17 @@ class ChatInfo:
self.chat_record_list: List[ChatRecord] = [] self.chat_record_list: List[ChatRecord] = []
self.work_flow_version = work_flow_version self.work_flow_version = work_flow_version
@staticmethod
def get_no_references_setting(dataset_setting, model_setting):
no_references_setting = dataset_setting.get(
'no_references_setting', {
'status': 'ai_questioning',
'value': '{question}'})
if no_references_setting.get('status') == 'ai_questioning':
no_references_prompt = model_setting.get('no_references_prompt', '{question}')
no_references_setting['value'] = no_references_prompt if len(no_references_prompt) > 0 else "{question}"
return no_references_setting
def to_base_pipeline_manage_params(self): def to_base_pipeline_manage_params(self):
dataset_setting = self.application.dataset_setting dataset_setting = self.application.dataset_setting
model_setting = self.application.model_setting model_setting = self.application.model_setting
@ -80,8 +91,13 @@ class ChatInfo:
'history_chat_record': self.chat_record_list, 'history_chat_record': self.chat_record_list,
'chat_id': self.chat_id, 'chat_id': self.chat_id,
'dialogue_number': self.application.dialogue_number, 'dialogue_number': self.application.dialogue_number,
'problem_optimization_prompt': self.application.problem_optimization_prompt if self.application.problem_optimization_prompt is not None and len(
self.application.problem_optimization_prompt) > 0 else '()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中',
'prompt': model_setting.get( 'prompt': model_setting.get(
'prompt') if 'prompt' in model_setting else Application.get_default_model_prompt(), 'prompt') if 'prompt' in model_setting and len(model_setting.get(
'prompt')) > 0 else Application.get_default_model_prompt(),
'system': model_setting.get(
'system', None),
'model_id': model_id, 'model_id': model_id,
'problem_optimization': self.application.problem_optimization, 'problem_optimization': self.application.problem_optimization,
'stream': True, 'stream': True,
@ -89,11 +105,7 @@ class ChatInfo:
self.application.model_params_setting.keys()) == 0 else self.application.model_params_setting, self.application.model_params_setting.keys()) == 0 else self.application.model_params_setting,
'search_mode': self.application.dataset_setting.get( 'search_mode': self.application.dataset_setting.get(
'search_mode') if 'search_mode' in self.application.dataset_setting else 'embedding', 'search_mode') if 'search_mode' in self.application.dataset_setting else 'embedding',
'no_references_setting': self.application.dataset_setting.get( 'no_references_setting': self.get_no_references_setting(self.application.dataset_setting, model_setting),
'no_references_setting') if 'no_references_setting' in self.application.dataset_setting else {
'status': 'ai_questioning',
'value': '{question}',
},
'user_id': self.application.user_id 'user_id': self.application.user_id
} }

View File

@ -40,15 +40,15 @@ class ApplicationApi(ApiMixin):
def get_response_body_api(): def get_response_body_api():
return openapi.Schema( return openapi.Schema(
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=['id', 'name', 'desc', 'model_id', 'multiple_rounds_dialogue', 'user_id', 'status', 'create_time', required=['id', 'name', 'desc', 'model_id', 'dialogue_number', 'user_id', 'status', 'create_time',
'update_time'], 'update_time'],
properties={ properties={
'id': openapi.Schema(type=openapi.TYPE_STRING, title="", description="主键id"), 'id': openapi.Schema(type=openapi.TYPE_STRING, title="", description="主键id"),
'name': openapi.Schema(type=openapi.TYPE_STRING, title="应用名称", description="应用名称"), 'name': openapi.Schema(type=openapi.TYPE_STRING, title="应用名称", description="应用名称"),
'desc': openapi.Schema(type=openapi.TYPE_STRING, title="应用描述", description="应用描述"), 'desc': openapi.Schema(type=openapi.TYPE_STRING, title="应用描述", description="应用描述"),
'model_id': openapi.Schema(type=openapi.TYPE_STRING, title="模型id", description="模型id"), 'model_id': openapi.Schema(type=openapi.TYPE_STRING, title="模型id", description="模型id"),
"multiple_rounds_dialogue": openapi.Schema(type=openapi.TYPE_BOOLEAN, title="是否开启多轮对话", "dialogue_number": openapi.Schema(type=openapi.TYPE_NUMBER, title="多轮对话次数",
description="是否开启多轮对话"), description="多轮对话次数"),
'prologue': openapi.Schema(type=openapi.TYPE_STRING, title="开场白", description="开场白"), 'prologue': openapi.Schema(type=openapi.TYPE_STRING, title="开场白", description="开场白"),
'example': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING), 'example': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING),
title="示例列表", description="示例列表"), title="示例列表", description="示例列表"),
@ -164,8 +164,8 @@ class ApplicationApi(ApiMixin):
'name': openapi.Schema(type=openapi.TYPE_STRING, title="应用名称", description="应用名称"), 'name': openapi.Schema(type=openapi.TYPE_STRING, title="应用名称", description="应用名称"),
'desc': openapi.Schema(type=openapi.TYPE_STRING, title="应用描述", description="应用描述"), 'desc': openapi.Schema(type=openapi.TYPE_STRING, title="应用描述", description="应用描述"),
'model_id': openapi.Schema(type=openapi.TYPE_STRING, title="模型id", description="模型id"), 'model_id': openapi.Schema(type=openapi.TYPE_STRING, title="模型id", description="模型id"),
"multiple_rounds_dialogue": openapi.Schema(type=openapi.TYPE_BOOLEAN, title="是否开启多轮对话", "dialogue_number": openapi.Schema(type=openapi.TYPE_NUMBER, title="多轮对话次数",
description="是否开启多轮对话"), description="多轮对话次数"),
'prologue': openapi.Schema(type=openapi.TYPE_STRING, title="开场白", description="开场白"), 'prologue': openapi.Schema(type=openapi.TYPE_STRING, title="开场白", description="开场白"),
'dataset_id_list': openapi.Schema(type=openapi.TYPE_ARRAY, 'dataset_id_list': openapi.Schema(type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_STRING), items=openapi.Schema(type=openapi.TYPE_STRING),
@ -176,7 +176,22 @@ class ApplicationApi(ApiMixin):
description="是否开启问题优化", default=True), description="是否开启问题优化", default=True),
'icon': openapi.Schema(type=openapi.TYPE_STRING, title="icon", 'icon': openapi.Schema(type=openapi.TYPE_STRING, title="icon",
description="icon", default="/ui/favicon.ico"), description="icon", default="/ui/favicon.ico"),
'work_flow': ApplicationApi.WorkFlow.get_request_body_api() 'type': openapi.Schema(type=openapi.TYPE_STRING, title="应用类型",
description="应用类型 简易:SIMPLE|工作流:WORK_FLOW"),
'work_flow': ApplicationApi.WorkFlow.get_request_body_api(),
'problem_optimization_prompt': openapi.Schema(type=openapi.TYPE_STRING, title='问题优化提示词',
description="问题优化提示词",
default="()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中"),
'tts_model_id': openapi.Schema(type=openapi.TYPE_STRING, title="文字转语音模型ID",
description="文字转语音模型ID"),
'stt_model_id': openapi.Schema(type=openapi.TYPE_STRING, title="语音转文字模型id",
description="语音转文字模型id"),
'stt_model_enable': openapi.Schema(type=openapi.TYPE_STRING, title="语音转文字是否开启",
description="语音转文字是否开启"),
'tts_model_enable': openapi.Schema(type=openapi.TYPE_STRING, title="语音转文字是否开启",
description="语音转文字是否开启"),
'tts_type': openapi.Schema(type=openapi.TYPE_STRING, title="文字转语音类型",
description="文字转语音类型")
} }
) )
@ -248,6 +263,11 @@ class ApplicationApi(ApiMixin):
'\n问题:' '\n问题:'
'\n{question}')), '\n{question}')),
'system': openapi.Schema(type=openapi.TYPE_STRING, title="系统提示词(角色)",
description="系统提示词(角色)"),
'no_references_prompt': openapi.Schema(type=openapi.TYPE_STRING, title="无引用分段提示词",
default="{question}", description="无引用分段提示词")
} }
) )
@ -267,14 +287,14 @@ class ApplicationApi(ApiMixin):
def get_request_body_api(): def get_request_body_api():
return openapi.Schema( return openapi.Schema(
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=['name', 'desc', 'model_id', 'multiple_rounds_dialogue', 'dataset_setting', 'model_setting', required=['name', 'desc', 'model_id', 'dialogue_number', 'dataset_setting', 'model_setting',
'problem_optimization'], 'problem_optimization', 'stt_model_enable', 'stt_model_enable', 'tts_type'],
properties={ properties={
'name': openapi.Schema(type=openapi.TYPE_STRING, title="应用名称", description="应用名称"), 'name': openapi.Schema(type=openapi.TYPE_STRING, title="应用名称", description="应用名称"),
'desc': openapi.Schema(type=openapi.TYPE_STRING, title="应用描述", description="应用描述"), 'desc': openapi.Schema(type=openapi.TYPE_STRING, title="应用描述", description="应用描述"),
'model_id': openapi.Schema(type=openapi.TYPE_STRING, title="模型id", description="模型id"), 'model_id': openapi.Schema(type=openapi.TYPE_STRING, title="模型id", description="模型id"),
"multiple_rounds_dialogue": openapi.Schema(type=openapi.TYPE_BOOLEAN, title="是否开启多轮对话", "dialogue_number": openapi.Schema(type=openapi.TYPE_NUMBER, title="多轮对话次数",
description="是否开启多轮对话"), description="多轮对话次数"),
'prologue': openapi.Schema(type=openapi.TYPE_STRING, title="开场白", description="开场白"), 'prologue': openapi.Schema(type=openapi.TYPE_STRING, title="开场白", description="开场白"),
'dataset_id_list': openapi.Schema(type=openapi.TYPE_ARRAY, 'dataset_id_list': openapi.Schema(type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_STRING), items=openapi.Schema(type=openapi.TYPE_STRING),
@ -284,8 +304,20 @@ class ApplicationApi(ApiMixin):
'problem_optimization': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="问题优化", 'problem_optimization': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="问题优化",
description="是否开启问题优化", default=True), description="是否开启问题优化", default=True),
'type': openapi.Schema(type=openapi.TYPE_STRING, title="应用类型", 'type': openapi.Schema(type=openapi.TYPE_STRING, title="应用类型",
description="应用类型 简易:SIMPLE|工作流:WORK_FLOW") description="应用类型 简易:SIMPLE|工作流:WORK_FLOW"),
'problem_optimization_prompt': openapi.Schema(type=openapi.TYPE_STRING, title='问题优化提示词',
description="问题优化提示词",
default="()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中"),
'tts_model_id': openapi.Schema(type=openapi.TYPE_STRING, title="文字转语音模型ID",
description="文字转语音模型ID"),
'stt_model_id': openapi.Schema(type=openapi.TYPE_STRING, title="语音转文字模型id",
description="语音转文字模型id"),
'stt_model_enable': openapi.Schema(type=openapi.TYPE_STRING, title="语音转文字是否开启",
description="语音转文字是否开启"),
'tts_model_enable': openapi.Schema(type=openapi.TYPE_STRING, title="语音转文字是否开启",
description="语音转文字是否开启"),
'tts_type': openapi.Schema(type=openapi.TYPE_STRING, title="文字转语音类型",
description="文字转语音类型")
} }
) )

View File

@ -19,26 +19,41 @@ class XlsSplitHandle(BaseParseTableHandle):
def handle(self, file, get_buffer, save_image): def handle(self, file, get_buffer, save_image):
buffer = get_buffer(file) buffer = get_buffer(file)
try: try:
wb = xlrd.open_workbook(file_contents=buffer) wb = xlrd.open_workbook(file_contents=buffer, formatting_info=True)
result = [] result = []
sheets = wb.sheets() sheets = wb.sheets()
for sheet in sheets: for sheet in sheets:
# 获取合并单元格的范围信息
merged_cells = sheet.merged_cells
print(merged_cells)
data = []
paragraphs = [] paragraphs = []
rows = iter([sheet.row_values(i) for i in range(sheet.nrows)]) # 获取第一行作为标题行
if not rows: continue headers = [sheet.cell_value(0, col_idx) for col_idx in range(sheet.ncols)]
ti = next(rows) # 从第二行开始遍历每一行(跳过标题行)
for r in rows: for row_idx in range(1, sheet.nrows):
l = [] row_data = {}
for i, c in enumerate(r): for col_idx in range(sheet.ncols):
if not c: cell_value = sheet.cell_value(row_idx, col_idx)
continue
t = str(ti[i]) if i < len(ti) else "" # 检查是否为空单元格,如果为空检查是否在合并区域中
t += (": " if t else "") + str(c) if cell_value == "":
l.append(t) # 检查当前单元格是否在合并区域
l = "; ".join(l) for (rlo, rhi, clo, chi) in merged_cells:
if sheet.name.lower().find("sheet") < 0: if rlo <= row_idx < rhi and clo <= col_idx < chi:
l += " ——" + sheet.name # 使用合并区域的左上角单元格的值
paragraphs.append({'title': '', 'content': l}) cell_value = sheet.cell_value(rlo, clo)
break
# 将标题作为键,单元格的值作为值存入字典
row_data[headers[col_idx]] = cell_value
data.append(row_data)
for row in data:
row_output = "; ".join([f"{key}: {value}" for key, value in row.items()])
# print(row_output)
paragraphs.append({'title': '', 'content': row_output})
result.append({'name': sheet.name, 'paragraphs': paragraphs}) result.append({'name': sheet.name, 'paragraphs': paragraphs})
except BaseException as e: except BaseException as e:

View File

@ -17,6 +17,35 @@ class XlsxSplitHandle(BaseParseTableHandle):
return True return True
return False return False
def fill_merged_cells(self, sheet, image_dict):
data = []
# 获取第一行作为标题行
headers = [cell.value for cell in sheet[1]]
# 从第二行开始遍历每一行
for row in sheet.iter_rows(min_row=2, values_only=False):
row_data = {}
for col_idx, cell in enumerate(row):
cell_value = cell.value
# 如果单元格为空,并且该单元格在合并单元格内,获取合并单元格的值
if cell_value is None:
for merged_range in sheet.merged_cells.ranges:
if cell.coordinate in merged_range:
cell_value = sheet[merged_range.min_row][merged_range.min_col - 1].value
break
image = image_dict.get(cell_value, None)
if image is not None:
cell_value = f'![](/api/image/{image.id})'
# 使用标题作为键,单元格的值作为值存入字典
row_data[headers[col_idx]] = cell_value
data.append(row_data)
return data
def handle(self, file, get_buffer, save_image): def handle(self, file, get_buffer, save_image):
buffer = get_buffer(file) buffer = get_buffer(file)
try: try:
@ -30,25 +59,13 @@ class XlsxSplitHandle(BaseParseTableHandle):
for sheetname in wb.sheetnames: for sheetname in wb.sheetnames:
paragraphs = [] paragraphs = []
ws = wb[sheetname] ws = wb[sheetname]
rows = list(ws.rows) data = self.fill_merged_cells(ws, image_dict)
if not rows: continue
ti = list(rows[0]) for row in data:
for r in list(rows[1:]): row_output = "; ".join([f"{key}: {value}" for key, value in row.items()])
l = [] # print(row_output)
for i, c in enumerate(r): paragraphs.append({'title': '', 'content': row_output})
if not c.value:
continue
t = str(ti[i].value) if i < len(ti) else ""
content = str(c.value)
image = image_dict.get(content, None)
if image is not None:
content = f'![](/api/image/{image.id})'
t += (": " if t else "") + content
l.append(t)
l = "; ".join(l)
if sheetname.lower().find("sheet") < 0:
l += " ——" + sheetname
paragraphs.append({'title': '', 'content': l})
result.append({'name': sheetname, 'paragraphs': paragraphs}) result.append({'name': sheetname, 'paragraphs': paragraphs})
except BaseException as e: except BaseException as e:

View File

@ -23,6 +23,7 @@ urlpatterns = [
path('dataset/<str:dataset_id>/document/_bach', views.Document.Batch.as_view()), path('dataset/<str:dataset_id>/document/_bach', views.Document.Batch.as_view()),
path('dataset/<str:dataset_id>/document/batch_hit_handling', views.Document.BatchEditHitHandling.as_view()), path('dataset/<str:dataset_id>/document/batch_hit_handling', views.Document.BatchEditHitHandling.as_view()),
path('dataset/<str:dataset_id>/document/<int:current_page>/<int:page_size>', views.Document.Page.as_view()), path('dataset/<str:dataset_id>/document/<int:current_page>/<int:page_size>', views.Document.Page.as_view()),
path('dataset/<str:dataset_id>/document/batch_refresh', views.Document.BatchRefresh.as_view()),
path('dataset/<str:dataset_id>/document/<str:document_id>', views.Document.Operate.as_view(), path('dataset/<str:dataset_id>/document/<str:document_id>', views.Document.Operate.as_view(),
name="document_operate"), name="document_operate"),
path('dataset/document/split', views.Document.Split.as_view(), path('dataset/document/split', views.Document.Split.as_view(),
@ -34,7 +35,6 @@ urlpatterns = [
name="document_export"), name="document_export"),
path('dataset/<str:dataset_id>/document/<str:document_id>/sync', views.Document.SyncWeb.as_view()), path('dataset/<str:dataset_id>/document/<str:document_id>/sync', views.Document.SyncWeb.as_view()),
path('dataset/<str:dataset_id>/document/<str:document_id>/refresh', views.Document.Refresh.as_view()), path('dataset/<str:dataset_id>/document/<str:document_id>/refresh', views.Document.Refresh.as_view()),
path('dataset/<str:dataset_id>/document/batch_refresh', views.Document.BatchRefresh.as_view()),
path('dataset/<str:dataset_id>/document/<str:document_id>/paragraph', views.Paragraph.as_view()), path('dataset/<str:dataset_id>/document/<str:document_id>/paragraph', views.Paragraph.as_view()),
path( path(
'dataset/<str:dataset_id>/document/<str:document_id>/paragraph/migrate/dataset/<str:target_dataset_id>/document/<str:target_document_id>', 'dataset/<str:dataset_id>/document/<str:document_id>/paragraph/migrate/dataset/<str:target_dataset_id>/document/<str:target_document_id>',

View File

@ -239,7 +239,7 @@ class Document(APIView):
class BatchRefresh(APIView): class BatchRefresh(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['POST'], detail=False) @action(methods=['PUT'], detail=False)
@swagger_auto_schema(operation_summary="批量刷新文档向量库", @swagger_auto_schema(operation_summary="批量刷新文档向量库",
operation_id="批量刷新文档向量库", operation_id="批量刷新文档向量库",
request_body= request_body=

View File

@ -0,0 +1,23 @@
# Generated by Django 4.2.15 on 2024-09-14 11:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('function_lib', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='functionlib',
name='is_active',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='functionlib',
name='permission_type',
field=models.CharField(choices=[('PUBLIC', '公开'), ('PRIVATE', '私有')], default='PRIVATE', max_length=20, verbose_name='权限类型'),
),
]

View File

@ -15,6 +15,11 @@ from common.mixins.app_model_mixin import AppModelMixin
from users.models import User from users.models import User
class PermissionType(models.TextChoices):
PUBLIC = "PUBLIC", '公开'
PRIVATE = "PRIVATE", "私有"
class FunctionLib(AppModelMixin): class FunctionLib(AppModelMixin):
id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id") id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id")
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户id") user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户id")
@ -24,6 +29,9 @@ class FunctionLib(AppModelMixin):
input_field_list = ArrayField(verbose_name="输入字段列表", input_field_list = ArrayField(verbose_name="输入字段列表",
base_field=models.JSONField(verbose_name="输入字段", default=dict) base_field=models.JSONField(verbose_name="输入字段", default=dict)
, default=list) , default=list)
is_active = models.BooleanField(default=True)
permission_type = models.CharField(max_length=20, verbose_name='权限类型', choices=PermissionType.choices,
default=PermissionType.PRIVATE)
class Meta: class Meta:
db_table = "function_lib" db_table = "function_lib"

View File

@ -11,7 +11,7 @@ import re
import uuid import uuid
from django.core import validators from django.core import validators
from django.db.models import QuerySet from django.db.models import QuerySet, Q
from rest_framework import serializers from rest_framework import serializers
from common.db.search import page_search from common.db.search import page_search
@ -27,7 +27,7 @@ function_executor = FunctionExecutor(CONFIG.get('SANDBOX'))
class FunctionLibModelSerializer(serializers.ModelSerializer): class FunctionLibModelSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = FunctionLib model = FunctionLib
fields = ['id', 'name', 'desc', 'code', 'input_field_list', fields = ['id', 'name', 'desc', 'code', 'input_field_list', 'permission_type', 'is_active',
'create_time', 'update_time'] 'create_time', 'update_time']
@ -68,6 +68,8 @@ class EditFunctionLib(serializers.Serializer):
input_field_list = FunctionLibInputField(required=False, many=True) input_field_list = FunctionLibInputField(required=False, many=True)
is_active = serializers.BooleanField(required=False, error_messages=ErrMessage.char('是否可用'))
class CreateFunctionLib(serializers.Serializer): class CreateFunctionLib(serializers.Serializer):
name = serializers.CharField(required=True, error_messages=ErrMessage.char("函数名称")) name = serializers.CharField(required=True, error_messages=ErrMessage.char("函数名称"))
@ -79,6 +81,12 @@ class CreateFunctionLib(serializers.Serializer):
input_field_list = FunctionLibInputField(required=True, many=True) input_field_list = FunctionLibInputField(required=True, many=True)
permission_type = serializers.CharField(required=True, error_messages=ErrMessage.char("权限"), validators=[
validators.RegexValidator(regex=re.compile("^PUBLIC|PRIVATE$"),
message="权限只支持PUBLIC|PRIVATE", code=500)
])
is_active = serializers.BooleanField(required=False, error_messages=ErrMessage.char('是否可用'))
class FunctionLibSerializer(serializers.Serializer): class FunctionLibSerializer(serializers.Serializer):
class Query(serializers.Serializer): class Query(serializers.Serializer):
@ -87,15 +95,19 @@ class FunctionLibSerializer(serializers.Serializer):
desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, desc = serializers.CharField(required=False, allow_null=True, allow_blank=True,
error_messages=ErrMessage.char("函数描述")) error_messages=ErrMessage.char("函数描述"))
is_active = serializers.BooleanField(required=False, error_messages=ErrMessage.char("是否可用"))
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id")) user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
def get_query_set(self): def get_query_set(self):
query_set = QuerySet(FunctionLib).filter(user_id=self.data.get('user_id')) query_set = QuerySet(FunctionLib).filter(
(Q(user_id=self.data.get('user_id')) | Q(permission_type='PUBLIC')))
if self.data.get('name') is not None: if self.data.get('name') is not None:
query_set = query_set.filter(name__contains=self.data.get('name')) query_set = query_set.filter(name__contains=self.data.get('name'))
if self.data.get('desc') is not None: if self.data.get('desc') is not None:
query_set = query_set.filter(desc__contains=self.data.get('desc')) query_set = query_set.filter(desc__contains=self.data.get('desc'))
if self.data.get('is_active') is not None:
query_set = query_set.filter(is_active=self.data.get('is_active'))
query_set = query_set.order_by("-create_time") query_set = query_set.order_by("-create_time")
return query_set return query_set
@ -120,7 +132,9 @@ class FunctionLibSerializer(serializers.Serializer):
function_lib = FunctionLib(id=uuid.uuid1(), name=instance.get('name'), desc=instance.get('desc'), function_lib = FunctionLib(id=uuid.uuid1(), name=instance.get('name'), desc=instance.get('desc'),
code=instance.get('code'), code=instance.get('code'),
user_id=self.data.get('user_id'), user_id=self.data.get('user_id'),
input_field_list=instance.get('input_field_list')) input_field_list=instance.get('input_field_list'),
permission_type=instance.get('permission_type'),
is_active=instance.get('is_active', True))
function_lib.save() function_lib.save()
return FunctionLibModelSerializer(function_lib).data return FunctionLibModelSerializer(function_lib).data
@ -193,7 +207,7 @@ class FunctionLibSerializer(serializers.Serializer):
if with_valid: if with_valid:
self.is_valid(raise_exception=True) self.is_valid(raise_exception=True)
EditFunctionLib(data=instance).is_valid(raise_exception=True) EditFunctionLib(data=instance).is_valid(raise_exception=True)
edit_field_list = ['name', 'desc', 'code', 'input_field_list'] edit_field_list = ['name', 'desc', 'code', 'input_field_list', 'permission_type', 'is_active']
edit_dict = {field: instance.get(field) for field in edit_field_list if ( edit_dict = {field: instance.get(field) for field in edit_field_list if (
field in instance and instance.get(field) is not None)} field in instance and instance.get(field) is not None)}
QuerySet(FunctionLib).filter(id=self.data.get('id')).update(**edit_dict) QuerySet(FunctionLib).filter(id=self.data.get('id')).update(**edit_dict)

View File

@ -103,6 +103,8 @@ class FunctionLibApi(ApiMixin):
'name': openapi.Schema(type=openapi.TYPE_STRING, title="函数名称", description="函数名称"), 'name': openapi.Schema(type=openapi.TYPE_STRING, title="函数名称", description="函数名称"),
'desc': openapi.Schema(type=openapi.TYPE_STRING, title="函数描述", description="函数描述"), 'desc': openapi.Schema(type=openapi.TYPE_STRING, title="函数描述", description="函数描述"),
'code': openapi.Schema(type=openapi.TYPE_STRING, title="函数内容", description="函数内容"), 'code': openapi.Schema(type=openapi.TYPE_STRING, title="函数内容", description="函数内容"),
'permission_type': openapi.Schema(type=openapi.TYPE_STRING, title="权限", description="权限"),
'is_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="是否可用", description="是否可用"),
'input_field_list': openapi.Schema(type=openapi.TYPE_ARRAY, 'input_field_list': openapi.Schema(type=openapi.TYPE_ARRAY,
description="输入变量列表", description="输入变量列表",
items=openapi.Schema(type=openapi.TYPE_OBJECT, items=openapi.Schema(type=openapi.TYPE_OBJECT,
@ -135,11 +137,13 @@ class FunctionLibApi(ApiMixin):
def get_request_body_api(): def get_request_body_api():
return openapi.Schema( return openapi.Schema(
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=['name', 'code', 'input_field_list'], required=['name', 'code', 'input_field_list', 'permission_type'],
properties={ properties={
'name': openapi.Schema(type=openapi.TYPE_STRING, title="函数名称", description="函数名称"), 'name': openapi.Schema(type=openapi.TYPE_STRING, title="函数名称", description="函数名称"),
'desc': openapi.Schema(type=openapi.TYPE_STRING, title="函数描述", description="函数描述"), 'desc': openapi.Schema(type=openapi.TYPE_STRING, title="函数描述", description="函数描述"),
'code': openapi.Schema(type=openapi.TYPE_STRING, title="函数内容", description="函数内容"), 'code': openapi.Schema(type=openapi.TYPE_STRING, title="函数内容", description="函数内容"),
'permission_type': openapi.Schema(type=openapi.TYPE_STRING, title="权限", description="权限"),
'is_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="是否可用", description="是否可用"),
'input_field_list': openapi.Schema(type=openapi.TYPE_ARRAY, 'input_field_list': openapi.Schema(type=openapi.TYPE_ARRAY,
description="输入变量列表", description="输入变量列表",
items=openapi.Schema(type=openapi.TYPE_OBJECT, items=openapi.Schema(type=openapi.TYPE_OBJECT,

View File

@ -19,7 +19,7 @@ class BedrockLLMModelParams(BaseForm):
precision=2) precision=2)
max_tokens = forms.SliderField( max_tokens = forms.SliderField(
TooltipLabel('输出最大Tokens', '较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定'), TooltipLabel('输出最大Tokens', '指定模型可生成的最大token个数'),
required=True, default_value=1024, required=True, default_value=1024,
_min=1, _min=1,
_max=4096, _max=4096,

View File

@ -25,7 +25,7 @@ class AzureLLMModelParams(BaseForm):
precision=2) precision=2)
max_tokens = forms.SliderField( max_tokens = forms.SliderField(
TooltipLabel('输出最大Tokens', '较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定'), TooltipLabel('输出最大Tokens', '指定模型可生成的最大token个数'),
required=True, default_value=800, required=True, default_value=800,
_min=1, _min=1,
_max=4096, _max=4096,

View File

@ -25,7 +25,7 @@ class DeepSeekLLMModelParams(BaseForm):
precision=2) precision=2)
max_tokens = forms.SliderField( max_tokens = forms.SliderField(
TooltipLabel('输出最大Tokens', '较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定'), TooltipLabel('输出最大Tokens', '指定模型可生成的最大token个数'),
required=True, default_value=800, required=True, default_value=800,
_min=1, _min=1,
_max=4096, _max=4096,

View File

@ -25,7 +25,7 @@ class GeminiLLMModelParams(BaseForm):
precision=2) precision=2)
max_tokens = forms.SliderField( max_tokens = forms.SliderField(
TooltipLabel('输出最大Tokens', '较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定'), TooltipLabel('输出最大Tokens', '指定模型可生成的最大token个数'),
required=True, default_value=800, required=True, default_value=800,
_min=1, _min=1,
_max=4096, _max=4096,

View File

@ -25,7 +25,7 @@ class KimiLLMModelParams(BaseForm):
precision=2) precision=2)
max_tokens = forms.SliderField( max_tokens = forms.SliderField(
TooltipLabel('输出最大Tokens', '较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定'), TooltipLabel('输出最大Tokens', '指定模型可生成的最大token个数'),
required=True, default_value=1024, required=True, default_value=1024,
_min=1, _min=1,
_max=4096, _max=4096,

View File

@ -23,7 +23,7 @@ class OllamaLLMModelParams(BaseForm):
precision=2) precision=2)
max_tokens = forms.SliderField( max_tokens = forms.SliderField(
TooltipLabel('输出最大Tokens', '较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定'), TooltipLabel('输出最大Tokens', '指定模型可生成的最大token个数'),
required=True, default_value=1024, required=True, default_value=1024,
_min=1, _min=1,
_max=4096, _max=4096,

View File

@ -25,7 +25,7 @@ class OpenAILLMModelParams(BaseForm):
precision=2) precision=2)
max_tokens = forms.SliderField( max_tokens = forms.SliderField(
TooltipLabel('输出最大Tokens', '较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定'), TooltipLabel('输出最大Tokens', '指定模型可生成的最大token个数'),
required=True, default_value=800, required=True, default_value=800,
_min=1, _min=1,
_max=4096, _max=4096,

View File

@ -40,8 +40,6 @@ class OpenAIChatModel(MaxKBBaseModel, ChatOpenAI):
openai_api_base=model_credential.get('api_base'), openai_api_base=model_credential.get('api_base'),
openai_api_key=model_credential.get('api_key'), openai_api_key=model_credential.get('api_key'),
**optional_params, **optional_params,
streaming=True,
stream_usage=True,
custom_get_token_ids=custom_get_token_ids custom_get_token_ids=custom_get_token_ids
) )
return azure_chat_open_ai return azure_chat_open_ai

View File

@ -25,7 +25,7 @@ class QwenModelParams(BaseForm):
precision=2) precision=2)
max_tokens = forms.SliderField( max_tokens = forms.SliderField(
TooltipLabel('输出最大Tokens', '较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定'), TooltipLabel('输出最大Tokens', '指定模型可生成的最大token个数'),
required=True, default_value=800, required=True, default_value=800,
_min=1, _min=1,
_max=2048, _max=2048,

View File

@ -19,7 +19,7 @@ class VLLMModelParams(BaseForm):
precision=2) precision=2)
max_tokens = forms.SliderField( max_tokens = forms.SliderField(
TooltipLabel('输出最大Tokens', '较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定'), TooltipLabel('输出最大Tokens', '指定模型可生成的最大token个数'),
required=True, default_value=800, required=True, default_value=800,
_min=1, _min=1,
_max=4096, _max=4096,

View File

@ -25,7 +25,7 @@ class VolcanicEngineLLMModelParams(BaseForm):
precision=2) precision=2)
max_tokens = forms.SliderField( max_tokens = forms.SliderField(
TooltipLabel('输出最大Tokens', '较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定'), TooltipLabel('输出最大Tokens', '指定模型可生成的最大token个数'),
required=True, default_value=1024, required=True, default_value=1024,
_min=1, _min=1,
_max=4096, _max=4096,

View File

@ -11,6 +11,7 @@ import base64
import gzip import gzip
import hmac import hmac
import json import json
import os
import uuid import uuid
import wave import wave
from enum import Enum from enum import Enum
@ -144,6 +145,7 @@ def parse_response(res):
result['code'] = code result['code'] = code
payload_size = int.from_bytes(payload[4:8], "big", signed=False) payload_size = int.from_bytes(payload[4:8], "big", signed=False)
payload_msg = payload[8:] payload_msg = payload[8:]
print(f"Error code: {code}, message: {payload_msg}")
if payload_msg is None: if payload_msg is None:
return result return result
if message_compression == GZIP: if message_compression == GZIP:
@ -321,14 +323,9 @@ class VolcanicEngineSpeechToText(MaxKBBaseModel, BaseSpeechToText):
return result['payload_msg']['result'][0]['text'] return result['payload_msg']['result'][0]['text']
def check_auth(self): def check_auth(self):
header = self.token_auth() cwd = os.path.dirname(os.path.abspath(__file__))
with open(f'{cwd}/iat_mp3_16k.mp3', 'rb') as f:
async def check(): self.speech_to_text(f)
async with websockets.connect(self.volcanic_api_url, extra_headers=header, max_size=1000000000,
ssl=ssl_context) as ws:
pass
asyncio.run(check())
def speech_to_text(self, file): def speech_to_text(self, file):
data = file.read() data = file.read()

View File

@ -69,14 +69,7 @@ class VolcanicEngineTextToSpeech(MaxKBBaseModel, BaseTextToSpeech):
) )
def check_auth(self): def check_auth(self):
header = self.token_auth() self.text_to_speech('你好')
async def check():
async with websockets.connect(self.volcanic_api_url, extra_headers=header, ping_interval=None,
ssl=ssl_context) as ws:
pass
asyncio.run(check())
def text_to_speech(self, text): def text_to_speech(self, text):
request_json = { request_json = {
@ -159,7 +152,7 @@ class VolcanicEngineTextToSpeech(MaxKBBaseModel, BaseTextToSpeech):
if message_compression == 1: if message_compression == 1:
error_msg = gzip.decompress(error_msg) error_msg = gzip.decompress(error_msg)
error_msg = str(error_msg, "utf-8") error_msg = str(error_msg, "utf-8")
break raise Exception(f"Error code: {code}, message: {error_msg}")
elif message_type == 0xc: elif message_type == 0xc:
msg_size = int.from_bytes(payload[:4], "big", signed=False) msg_size = int.from_bytes(payload[:4], "big", signed=False)
payload = payload[4:] payload = payload[4:]

View File

@ -25,7 +25,7 @@ class WenxinLLMModelParams(BaseForm):
precision=2) precision=2)
max_tokens = forms.SliderField( max_tokens = forms.SliderField(
TooltipLabel('输出最大Tokens', '较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定'), TooltipLabel('输出最大Tokens', '指定模型可生成的最大token个数'),
required=True, default_value=1024, required=True, default_value=1024,
_min=2, _min=2,
_max=2048, _max=2048,

View File

@ -25,7 +25,7 @@ class XunFeiLLMModelGeneralParams(BaseForm):
precision=2) precision=2)
max_tokens = forms.SliderField( max_tokens = forms.SliderField(
TooltipLabel('输出最大Tokens', '较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定'), TooltipLabel('输出最大Tokens', '指定模型可生成的最大token个数'),
required=True, default_value=4096, required=True, default_value=4096,
_min=1, _min=1,
_max=4096, _max=4096,
@ -42,7 +42,7 @@ class XunFeiLLMModelProParams(BaseForm):
precision=2) precision=2)
max_tokens = forms.SliderField( max_tokens = forms.SliderField(
TooltipLabel('输出最大Tokens', '较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定'), TooltipLabel('输出最大Tokens', '指定模型可生成的最大token个数'),
required=True, default_value=4096, required=True, default_value=4096,
_min=1, _min=1,
_max=8192, _max=8192,

View File

@ -8,6 +8,8 @@ import datetime
import hashlib import hashlib
import hmac import hmac
import json import json
import logging
import os
from datetime import datetime from datetime import datetime
from typing import Dict from typing import Dict
from urllib.parse import urlencode, urlparse from urllib.parse import urlencode, urlparse
@ -25,6 +27,7 @@ ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_context.check_hostname = False ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE ssl_context.verify_mode = ssl.CERT_NONE
max_kb = logging.getLogger("max_kb")
class XFSparkSpeechToText(MaxKBBaseModel, BaseSpeechToText): class XFSparkSpeechToText(MaxKBBaseModel, BaseSpeechToText):
spark_app_id: str spark_app_id: str
@ -89,11 +92,9 @@ class XFSparkSpeechToText(MaxKBBaseModel, BaseSpeechToText):
return url return url
def check_auth(self): def check_auth(self):
async def check(): cwd = os.path.dirname(os.path.abspath(__file__))
async with websockets.connect(self.create_url(), ssl=ssl_context) as ws: with open(f'{cwd}/iat_mp3_16k.mp3', 'rb') as f:
pass self.speech_to_text(f)
asyncio.run(check())
def speech_to_text(self, file): def speech_to_text(self, file):
async def handle(): async def handle():
@ -112,8 +113,7 @@ class XFSparkSpeechToText(MaxKBBaseModel, BaseSpeechToText):
sid = message["sid"] sid = message["sid"]
if code != 0: if code != 0:
errMsg = message["message"] errMsg = message["message"]
print("sid:%s call error:%s code is:%s" % (sid, errMsg, code)) raise Exception(f"sid: {sid} call error: {errMsg} code is: {code}")
return errMsg
else: else:
data = message["data"]["result"]["ws"] data = message["data"]["result"]["ws"]
result = "" result = ""

View File

@ -10,6 +10,7 @@ import datetime
import hashlib import hashlib
import hmac import hmac
import json import json
import logging
import os import os
from datetime import datetime from datetime import datetime
from typing import Dict from typing import Dict
@ -20,6 +21,8 @@ import websockets
from setting.models_provider.base_model_provider import MaxKBBaseModel from setting.models_provider.base_model_provider import MaxKBBaseModel
from setting.models_provider.impl.base_tts import BaseTextToSpeech from setting.models_provider.impl.base_tts import BaseTextToSpeech
max_kb = logging.getLogger("max_kb")
STATUS_FIRST_FRAME = 0 # 第一帧的标识 STATUS_FIRST_FRAME = 0 # 第一帧的标识
STATUS_CONTINUE_FRAME = 1 # 中间帧标识 STATUS_CONTINUE_FRAME = 1 # 中间帧标识
STATUS_LAST_FRAME = 2 # 最后一帧的标识 STATUS_LAST_FRAME = 2 # 最后一帧的标识
@ -92,11 +95,7 @@ class XFSparkTextToSpeech(MaxKBBaseModel, BaseTextToSpeech):
return url return url
def check_auth(self): def check_auth(self):
async def check(): self.text_to_speech("你好")
async with websockets.connect(self.create_url(), max_size=1000000000, ssl=ssl_context) as ws:
pass
asyncio.run(check())
def text_to_speech(self, text): def text_to_speech(self, text):
@ -119,13 +118,13 @@ class XFSparkTextToSpeech(MaxKBBaseModel, BaseTextToSpeech):
# print(message) # print(message)
code = message["code"] code = message["code"]
sid = message["sid"] sid = message["sid"]
audio = message["data"]["audio"]
audio = base64.b64decode(audio)
if code != 0: if code != 0:
errMsg = message["message"] errMsg = message["message"]
print("sid:%s call error:%s code is:%s" % (sid, errMsg, code)) raise Exception(f"sid: {sid} call error: {errMsg} code is: {code}")
else: else:
audio = message["data"]["audio"]
audio = base64.b64decode(audio)
audio_bytes += audio audio_bytes += audio
# 退出 # 退出
if message["data"]["status"] == 2: if message["data"]["status"] == 2:

View File

@ -19,7 +19,7 @@ class XinferenceLLMModelParams(BaseForm):
precision=2) precision=2)
max_tokens = forms.SliderField( max_tokens = forms.SliderField(
TooltipLabel('输出最大Tokens', '较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定'), TooltipLabel('输出最大Tokens', '指定模型可生成的最大token个数'),
required=True, default_value=800, required=True, default_value=800,
_min=1, _min=1,
_max=4096, _max=4096,

View File

@ -25,7 +25,7 @@ class ZhiPuLLMModelParams(BaseForm):
precision=2) precision=2)
max_tokens = forms.SliderField( max_tokens = forms.SliderField(
TooltipLabel('输出最大Tokens', '较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定'), TooltipLabel('输出最大Tokens', '指定模型可生成的最大token个数'),
required=True, default_value=1024, required=True, default_value=1024,
_min=1, _min=1,
_max=4096, _max=4096,

View File

@ -132,8 +132,8 @@ class RegisterSerializer(ApiMixin, serializers.Serializer):
max_length=20, max_length=20,
min_length=6, min_length=6,
validators=[ validators=[
validators.RegexValidator(regex=re.compile("^[a-zA-Z][a-zA-Z0-9_]{5,20}$"), validators.RegexValidator(regex=re.compile("^.{6,20}$"),
message="用户名字符数为 6-20 个字符,必须以字母开头,可使用字母、数字、下划线等") message="用户名字符数为 6-20 个字符")
]) ])
password = serializers.CharField(required=True, error_messages=ErrMessage.char("密码"), password = serializers.CharField(required=True, error_messages=ErrMessage.char("密码"),
validators=[validators.RegexValidator(regex=re.compile( validators=[validators.RegexValidator(regex=re.compile(
@ -590,8 +590,8 @@ class UserManageSerializer(serializers.Serializer):
max_length=20, max_length=20,
min_length=6, min_length=6,
validators=[ validators=[
validators.RegexValidator(regex=re.compile("^[a-zA-Z][a-zA-Z0-9_]{5,20}$"), validators.RegexValidator(regex=re.compile("^.{6,20}$"),
message="用户名字符数为 6-20 个字符,必须以字母开头,可使用字母、数字、下划线等") message="用户名字符数为 6-20 个字符")
]) ])
password = serializers.CharField(required=True, error_messages=ErrMessage.char("密码"), password = serializers.CharField(required=True, error_messages=ErrMessage.char("密码"),
validators=[validators.RegexValidator(regex=re.compile( validators=[validators.RegexValidator(regex=re.compile(

View File

@ -11,7 +11,7 @@ django = "4.2.15"
djangorestframework = "^3.15.2" djangorestframework = "^3.15.2"
drf-yasg = "1.21.7" drf-yasg = "1.21.7"
django-filter = "23.2" django-filter = "23.2"
langchain = "0.2.3" langchain = "0.2.16"
langchain_community = "0.2.4" langchain_community = "0.2.4"
langchain-huggingface = "^0.0.3" langchain-huggingface = "^0.0.3"
psycopg2-binary = "2.9.7" psycopg2-binary = "2.9.7"

View File

@ -3,6 +3,7 @@ import { get, post, del, put, exportExcel } from '@/request/index'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import type { KeyValue } from '@/api/type/common' import type { KeyValue } from '@/api/type/common'
import type { pageRequest } from '@/api/type/common' import type { pageRequest } from '@/api/type/common'
const prefix = '/dataset' const prefix = '/dataset'
/** /**
@ -28,12 +29,12 @@ const listSplitPattern: (
* *
* @param dataset_id, * @param dataset_id,
* page { * page {
"current_page": "string", "current_page": "string",
"page_size": "string", "page_size": "string",
} }
* param { * param {
"name": "string", "name": "string",
} }
*/ */
const getDocument: ( const getDocument: (
@ -60,20 +61,20 @@ const getAllDocument: (dataset_id: string, loading?: Ref<boolean>) => Promise<Re
* *
* @param * @param
* { * {
"name": "string", "name": "string",
"paragraphs": [ "paragraphs": [
{ {
"content": "string", "content": "string",
"title": "string", "title": "string",
"problem_list": [ "problem_list": [
{ {
"id": "string", "id": "string",
"content": "string" "content": "string"
} }
] ]
} }
] ]
} }
*/ */
const postDocument: ( const postDocument: (
dataset_id: string, dataset_id: string,
@ -88,10 +89,10 @@ const postDocument: (
* @param * @param
* dataset_id, document_id, * dataset_id, document_id,
* { * {
"name": "string", "name": "string",
"is_active": true, "is_active": true,
"meta": {} "meta": {}
} }
*/ */
const putDocument: ( const putDocument: (
dataset_id: string, dataset_id: string,
@ -124,6 +125,19 @@ const delMulDocument: (
) => Promise<Result<boolean>> = (dataset_id, data, loading) => { ) => Promise<Result<boolean>> = (dataset_id, data, loading) => {
return del(`${prefix}/${dataset_id}/document/_bach`, undefined, { id_list: data }, loading) return del(`${prefix}/${dataset_id}/document/_bach`, undefined, { id_list: data }, loading)
} }
const batchRefresh: (
dataset_id: string,
data: any,
loading?: Ref<boolean>
) => Promise<Result<boolean>> = (dataset_id, data, loading) => {
return put(
`${prefix}/${dataset_id}/document/batch_refresh`,
{ id_list: data },
undefined,
loading
)
}
/** /**
* *
* @param dataset_id * @param dataset_id
@ -182,12 +196,12 @@ const delMulSyncDocument: (
* Web站点文档 * Web站点文档
* @param * @param
* { * {
"source_url_list": [ "source_url_list": [
"string" "string"
], ],
"selector": "string" "selector": "string"
}
} }
}
*/ */
const postWebDocument: ( const postWebDocument: (
dataset_id: string, dataset_id: string,
@ -201,7 +215,7 @@ const postWebDocument: (
* QA文档 * QA文档
* @param * @param
* file * file
} }
*/ */
const postQADocument: ( const postQADocument: (
dataset_id: string, dataset_id: string,
@ -323,5 +337,6 @@ export default {
exportTableTemplate, exportTableTemplate,
postQADocument, postQADocument,
postTableDocument, postTableDocument,
exportDocument exportDocument,
batchRefresh
} }

View File

@ -4,12 +4,13 @@ interface ApplicationFormType {
name?: string name?: string
desc?: string desc?: string
model_id?: string model_id?: string
multiple_rounds_dialogue?: boolean dialogue_number?: number
prologue?: string prologue?: string
dataset_id_list?: string[] dataset_id_list?: string[]
dataset_setting?: any dataset_setting?: any
model_setting?: any model_setting?: any
problem_optimization?: boolean problem_optimization?: boolean
problem_optimization_prompt?: string
icon?: string | undefined icon?: string | undefined
type?: string type?: string
work_flow?: any work_flow?: any

View File

@ -1,9 +1,11 @@
interface functionLibData { interface functionLibData {
id?: String id?: String
name: String name?: String
desc: String desc?: String
code?: String code?: String
permission_type?: 'PRIVATE' | 'PUBLIC'
input_field_list?: Array<any> input_field_list?: Array<any>
is_active?: Boolean
} }
export type { functionLibData } export type { functionLibData }

View File

@ -170,7 +170,7 @@
<!-- 多路召回 --> <!-- 多路召回 -->
<template v-if="item.type == WorkflowType.RrerankerNode"> <template v-if="item.type == WorkflowType.RrerankerNode">
<div class="card-never border-r-4"> <div class="card-never border-r-4">
<h5 class="p-8-12">检索内容</h5> <h5 class="p-8-12">重排内容</h5>
<div class="p-8-12 border-t-dashed lighter"> <div class="p-8-12 border-t-dashed lighter">
<template v-if="item.document_list?.length > 0"> <template v-if="item.document_list?.length > 0">
<template <template
@ -194,7 +194,7 @@
</div> </div>
</div> </div>
<div class="card-never border-r-4 mt-8"> <div class="card-never border-r-4 mt-8">
<h5 class="p-8-12">检索结果</h5> <h5 class="p-8-12">重排结果</h5>
<div class="p-8-12 border-t-dashed lighter"> <div class="p-8-12 border-t-dashed lighter">
<template v-if="item.result_list?.length > 0"> <template v-if="item.result_list?.length > 0">
<template <template
@ -203,7 +203,7 @@
> >
<CardBox <CardBox
shadow="never" shadow="never"
:title="''" :title="`分段${paragraphIndex + 1}`"
class="paragraph-source-card cursor mb-8 paragraph-source-card-height" class="paragraph-source-card cursor mb-8 paragraph-source-card-height"
:showIcon="false" :showIcon="false"
> >

View File

@ -812,7 +812,7 @@ const startRecording = async () => {
mediaRecorder.value = new Recorder({ mediaRecorder.value = new Recorder({
type: 'mp3', type: 'mp3',
bitRate: 128, bitRate: 128,
sampleRate: 44100 sampleRate: 16000
}) })
mediaRecorder.value.open( mediaRecorder.value.open(

View File

@ -104,8 +104,10 @@ export default {
} }
}, },
prompt: { prompt: {
defaultPrompt: defaultPrompt: `已知信息:{data}
'已知信息:\n{data}\n回答要求\n- 请使用简洁且专业的语言来回答用户的问题。\n- 如果你不知道答案,请回答“没有在知识库中查找到相关信息,建议咨询相关技术支持或参考官方文档进行操作”。\n- 避免提及你是从已知信息中获得的知识。\n- 请保证答案与已知信息中描述的一致。\n- 请使用 Markdown 语法优化答案的格式。\n- 已知信息中的图片、链接地址和脚本语言请直接返回。\n- 请使用与问题相同的语言来回答。\n问题\n{question}', {question}
- 使`,
defaultPrologue: defaultPrologue:
'您好,我是 MaxKB 小助手,您可以向我提出 MaxKB 使用问题。\n- MaxKB 主要功能有什么?\n- MaxKB 支持哪些大语言模型?\n- MaxKB 支持哪些文档类型?' '您好,我是 MaxKB 小助手,您可以向我提出 MaxKB 使用问题。\n- MaxKB 主要功能有什么?\n- MaxKB 支持哪些大语言模型?\n- MaxKB 支持哪些文档类型?'
} }

View File

@ -733,3 +733,13 @@ h5 {
display: none !important; display: none !important;
} }
} }
.edit-avatar {
position: relative;
.edit-mask {
position: absolute;
left: 0;
background: rgba(0, 0, 0, 0.4);
}
}

View File

@ -332,14 +332,5 @@ onMounted(() => {
right: 16px; right: 16px;
top: 21px; top: 21px;
} }
.edit-avatar {
position: relative;
.edit-mask {
position: absolute;
left: 0;
background: rgba(0, 0, 0, 0.4);
}
}
} }
</style> </style>

View File

@ -61,10 +61,7 @@
/> />
</el-form-item> </el-form-item>
<el-form-item <el-form-item :label="$t('views.application.applicationForm.form.aiModel.label')">
:label="$t('views.application.applicationForm.form.aiModel.label')"
prop="model_id"
>
<template #label> <template #label>
<div class="flex-between"> <div class="flex-between">
<span>{{ $t('views.application.applicationForm.form.aiModel.label') }}</span> <span>{{ $t('views.application.applicationForm.form.aiModel.label') }}</span>
@ -151,47 +148,51 @@
</template> </template>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="角色设定">
<el-input
v-model="applicationForm.model_setting.system"
:rows="6"
type="textarea"
maxlength="2048"
placeholder="你是 xxx 小助手"
/>
</el-form-item>
<el-form-item <el-form-item
:label="$t('views.application.applicationForm.form.prompt.label')" :label="$t('views.application.applicationForm.form.prompt.label')"
prop="model_setting.prompt" prop="model_setting.no_references_prompt"
:rules="{
required: applicationForm.model_id,
message: '请输入提示词',
trigger: 'blur'
}"
> >
<template #label> <template #label>
<div class="flex align-center"> <div class="flex align-center">
<div class="flex-between mr-4"> <div class="flex-between mr-4">
<span <span
>{{ $t('views.application.applicationForm.form.prompt.label') }} >{{ $t('views.application.applicationForm.form.prompt.label') }}
<span class="danger">*</span></span (无引用知识库)
> <span class="danger" v-if="applicationForm.model_id">*</span>
</span>
</div> </div>
<el-tooltip effect="dark" placement="right">
<template #content
>{{
$t('views.application.applicationForm.form.prompt.tooltip', {
data: '{data}',
question: '{question}'
})
}}
</template>
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
</el-tooltip>
</div> </div>
</template> </template>
<el-input <el-input
v-model="applicationForm.model_setting.prompt" v-model="applicationForm.model_setting.no_references_prompt"
:rows="6" :rows="6"
type="textarea" type="textarea"
maxlength="2048" maxlength="2048"
:placeholder="defaultPrompt" placeholder="{question}"
/> />
</el-form-item> </el-form-item>
<el-form-item <el-form-item label="历史聊天记录" @click.prevent>
:label="$t('views.application.applicationForm.form.multipleRoundsDialogue')" <el-input-number
@click.prevent v-model="applicationForm.dialogue_number"
> :min="0"
<el-switch :value-on-clear="0"
size="small" controls-position="right"
v-model="applicationForm.multiple_rounds_dialogue" class="w-full"
></el-switch> />
</el-form-item> </el-form-item>
<el-form-item <el-form-item
label="$t('views.application.applicationForm.form.relatedKnowledgeBase')" label="$t('views.application.applicationForm.form.relatedKnowledgeBase')"
@ -260,6 +261,34 @@
</el-row> </el-row>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item
:label="$t('views.application.applicationForm.form.prompt.label')"
prop="model_setting.prompt"
:rules="{
required: applicationForm.model_id,
message: '请输入提示词',
trigger: 'blur'
}"
>
<template #label>
<div class="flex align-center">
<div class="flex-between mr-4">
<span>
{{ $t('views.application.applicationForm.form.prompt.label') }}
(引用知识库)
<span class="danger" v-if="applicationForm.model_id">*</span>
</span>
</div>
</div>
</template>
<el-input
v-model="applicationForm.model_setting.prompt"
:rows="6"
type="textarea"
maxlength="2048"
:placeholder="defaultPrompt"
/>
</el-form-item>
<el-form-item :label="$t('views.application.applicationForm.form.prologue')"> <el-form-item :label="$t('views.application.applicationForm.form.prologue')">
<MdEditor <MdEditor
class="prologue-md-editor" class="prologue-md-editor"
@ -269,25 +298,7 @@
:footers="[]" :footers="[]"
/> />
</el-form-item> </el-form-item>
<el-form-item @click.prevent>
<template #label>
<div class="flex align-center">
<span class="mr-4">{{
$t('views.application.applicationForm.form.problemOptimization.label')
}}</span>
<el-tooltip
effect="dark"
:content="
$t('views.application.applicationForm.form.problemOptimization.tooltip')
"
placement="right"
>
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
</el-tooltip>
</div>
</template>
<el-switch size="small" v-model="applicationForm.problem_optimization"></el-switch>
</el-form-item>
<el-form-item> <el-form-item>
<template #label> <template #label>
<div class="flex-between"> <div class="flex-between">
@ -305,6 +316,7 @@
</div> </div>
</template> </template>
<el-select <el-select
v-if="applicationForm.stt_model_enable"
v-model="applicationForm.stt_model_id" v-model="applicationForm.stt_model_id"
class="w-full" class="w-full"
popper-class="select-model" popper-class="select-model"
@ -371,12 +383,15 @@
<el-switch size="small" v-model="applicationForm.tts_model_enable" /> <el-switch size="small" v-model="applicationForm.tts_model_enable" />
</div> </div>
</template> </template>
<el-radio-group v-model="applicationForm.tts_type"> <el-radio-group
<el-radio label="BROWSER">浏览器播放(免费)</el-radio> v-model="applicationForm.tts_type"
<el-radio label="TTS">TTS模型</el-radio> v-if="applicationForm.tts_model_enable"
>
<el-radio value="BROWSER">浏览器播放(免费)</el-radio>
<el-radio value="TTS">TTS模型</el-radio>
</el-radio-group> </el-radio-group>
<el-select <el-select
v-if="applicationForm.tts_type === 'TTS'" v-if="applicationForm.tts_type === 'TTS' && applicationForm.tts_model_enable"
v-model="applicationForm.tts_model_id" v-model="applicationForm.tts_model_id"
class="w-full" class="w-full"
popper-class="select-model" popper-class="select-model"
@ -446,7 +461,11 @@
</h4> </h4>
<div class="dialog-bg"> <div class="dialog-bg">
<div class="flex align-center p-24"> <div class="flex align-center p-24">
<div class="mr-12"> <div
class="edit-avatar mr-12"
@mouseenter="showEditIcon = true"
@mouseleave="showEditIcon = false"
>
<AppAvatar <AppAvatar
v-if="isAppIcon(applicationForm?.icon)" v-if="isAppIcon(applicationForm?.icon)"
shape="square" shape="square"
@ -462,8 +481,16 @@
shape="square" shape="square"
:size="32" :size="32"
/> />
<AppAvatar
v-if="showEditIcon"
shape="square"
class="edit-mask"
:size="32"
@click="openEditAvatar"
>
<el-icon><EditPen /></el-icon>
</AppAvatar>
</div> </div>
<h4> <h4>
{{ {{
applicationForm?.name || $t('views.application.applicationForm.form.appName.label') applicationForm?.name || $t('views.application.applicationForm.form.appName.label')
@ -494,6 +521,7 @@
@change="openCreateModel($event)" @change="openCreateModel($event)"
></CreateModelDialog> ></CreateModelDialog>
<SelectProviderDialog ref="selectProviderRef" @change="openCreateModel($event)" /> <SelectProviderDialog ref="selectProviderRef" @change="openCreateModel($event)" />
<EditAvatarDialog ref="EditAvatarDialogRef" @refresh="refreshIcon" />
</LayoutContainer> </LayoutContainer>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -505,6 +533,8 @@ import ParamSettingDialog from './component/ParamSettingDialog.vue'
import AddDatasetDialog from './component/AddDatasetDialog.vue' import AddDatasetDialog from './component/AddDatasetDialog.vue'
import CreateModelDialog from '@/views/template/component/CreateModelDialog.vue' import CreateModelDialog from '@/views/template/component/CreateModelDialog.vue'
import SelectProviderDialog from '@/views/template/component/SelectProviderDialog.vue' import SelectProviderDialog from '@/views/template/component/SelectProviderDialog.vue'
import EditAvatarDialog from '@/views/application-overview/component/EditAvatarDialog.vue'
import applicationApi from '@/api/application' import applicationApi from '@/api/application'
import { isAppIcon } from '@/utils/application' import { isAppIcon } from '@/utils/application'
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
@ -534,6 +564,7 @@ const selectProviderRef = ref<InstanceType<typeof SelectProviderDialog>>()
const applicationFormRef = ref<FormInstance>() const applicationFormRef = ref<FormInstance>()
const AddDatasetDialogRef = ref() const AddDatasetDialogRef = ref()
const EditAvatarDialogRef = ref()
const loading = ref(false) const loading = ref(false)
const datasetLoading = ref(false) const datasetLoading = ref(false)
@ -541,7 +572,7 @@ const applicationForm = ref<ApplicationFormType>({
name: '', name: '',
desc: '', desc: '',
model_id: '', model_id: '',
multiple_rounds_dialogue: false, dialogue_number: 1,
prologue: t('views.application.prompt.defaultPrologue'), prologue: t('views.application.prompt.defaultPrologue'),
dataset_id_list: [], dataset_id_list: [],
dataset_setting: { dataset_setting: {
@ -555,10 +586,14 @@ const applicationForm = ref<ApplicationFormType>({
} }
}, },
model_setting: { model_setting: {
prompt: defaultPrompt prompt: defaultPrompt,
system: '你是 xxx 小助手',
no_references_prompt: '{question}'
}, },
model_params_setting: {}, model_params_setting: {},
problem_optimization: false, problem_optimization: false,
problem_optimization_prompt:
'()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中',
stt_model_id: '', stt_model_id: '',
tts_model_id: '', tts_model_id: '',
stt_model_enable: false, stt_model_enable: false,
@ -574,20 +609,6 @@ const rules = reactive<FormRules<ApplicationFormType>>({
message: t('views.application.applicationForm.form.appName.placeholder'), message: t('views.application.applicationForm.form.appName.placeholder'),
trigger: 'blur' trigger: 'blur'
} }
],
model_id: [
{
required: false,
message: t('views.application.applicationForm.form.aiModel.placeholder'),
trigger: 'change'
}
],
'model_setting.prompt': [
{
required: true,
message: t('views.application.applicationForm.form.prompt.placeholder'),
trigger: 'blur'
}
] ]
}) })
const modelOptions = ref<any>(null) const modelOptions = ref<any>(null)
@ -595,6 +616,7 @@ const providerOptions = ref<Array<Provider>>([])
const datasetList = ref([]) const datasetList = ref([])
const sttModelOptions = ref<any>(null) const sttModelOptions = ref<any>(null)
const ttsModelOptions = ref<any>(null) const ttsModelOptions = ref<any>(null)
const showEditIcon = ref(false)
const submit = async (formEl: FormInstance | undefined) => { const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return if (!formEl) return
@ -623,11 +645,11 @@ const openAIParamSettingDialog = () => {
} }
const openParamSettingDialog = () => { const openParamSettingDialog = () => {
ParamSettingDialogRef.value?.open(applicationForm.value.dataset_setting) ParamSettingDialogRef.value?.open(applicationForm.value)
} }
function refreshParam(data: any) { function refreshParam(data: any) {
applicationForm.value.dataset_setting = data applicationForm.value = { ...applicationForm.value, ...data }
} }
function refreshForm(data: any) { function refreshForm(data: any) {
@ -666,6 +688,8 @@ function getDetail() {
applicationForm.value.stt_model_id = res.data.stt_model applicationForm.value.stt_model_id = res.data.stt_model
applicationForm.value.tts_model_id = res.data.tts_model applicationForm.value.tts_model_id = res.data.tts_model
applicationForm.value.tts_type = res.data.tts_type applicationForm.value.tts_type = res.data.tts_type
applicationForm.value.model_setting.no_references_prompt =
res.data.model_setting.no_references_prompt || ''
}) })
} }
@ -727,6 +751,13 @@ function getProvider() {
}) })
} }
function openEditAvatar() {
EditAvatarDialogRef.value.open(applicationForm.value)
}
function refreshIcon() {
getDetail()
}
function refresh() { function refresh() {
getDataset() getDataset()
} }

View File

@ -162,14 +162,14 @@
:placeholder="defaultPrompt" :placeholder="defaultPrompt"
/> />
</el-form-item> </el-form-item>
<el-form-item <el-form-item label="历史聊天记录" @click.prevent>
:label="$t('views.application.applicationForm.form.multipleRoundsDialogue')" <el-input-number
@click.prevent v-model="applicationForm.dialogue_number"
> :min="0"
<el-switch :value-on-clear="0"
size="small" controls-position="right"
v-model="applicationForm.multiple_rounds_dialogue" class="w-full"
></el-switch> />
</el-form-item> </el-form-item>
<el-form-item <el-form-item
label="$t('views.application.applicationForm.form.relatedKnowledgeBase')" label="$t('views.application.applicationForm.form.relatedKnowledgeBase')"
@ -355,7 +355,7 @@ const applicationForm = ref<ApplicationFormType>({
name: '', name: '',
desc: '', desc: '',
model_id: '', model_id: '',
multiple_rounds_dialogue: false, dialogue_number: 0,
prologue: t('views.application.prompt.defaultPrologue'), prologue: t('views.application.prompt.defaultPrologue'),
dataset_id_list: [], dataset_id_list: [],
dataset_setting: { dataset_setting: {

View File

@ -28,10 +28,14 @@
</el-input> </el-input>
</el-form-item> </el-form-item>
</template> </template>
<div v-if="configType === 'wechat'" class="flex align-center" style="margin-bottom: 8px">
<span class="el-form-item__label">是否是订阅号</span>
<el-switch v-if="configType === 'wechat'" v-model="form[configType].is_personal" />
</div>
<h4 class="title-decoration-1 mb-16">回调地址</h4> <h4 class="title-decoration-1 mb-16">回调地址</h4>
<el-form-item label="URL" prop="callback_url"> <el-form-item label="URL" prop="callback_url">
<el-input v-model="form[configType].callback_url" placeholder="请输入回调地址"> <el-input v-model="form[configType].callback_url" placeholder="请输入回调地址" readonly>
<template #append> <template #append>
<el-button @click="copyClick(form[configType].callback_url)"> <el-button @click="copyClick(form[configType].callback_url)">
<AppIcon iconName="app-copy"></AppIcon> <AppIcon iconName="app-copy"></AppIcon>
@ -102,7 +106,14 @@ const {
} = route as any } = route as any
const form = reactive<any>({ const form = reactive<any>({
wechat: { app_id: '', app_secret: '', token: '', encoding_aes_key: '', callback_url: '' }, wechat: {
app_id: '',
app_secret: '',
token: '',
encoding_aes_key: '',
is_personal: false,
callback_url: ''
},
dingtalk: { client_id: '', client_secret: '', callback_url: '' }, dingtalk: { client_id: '', client_secret: '', callback_url: '' },
wecom: { wecom: {
app_id: '', app_id: '',

View File

@ -72,7 +72,7 @@ const applicationForm = ref<ApplicationFormType>({
name: '', name: '',
desc: '', desc: '',
model_id: '', model_id: '',
multiple_rounds_dialogue: false, dialogue_number: 0,
prologue: t('views.application.prompt.defaultPrologue'), prologue: t('views.application.prompt.defaultPrologue'),
dataset_id_list: [], dataset_id_list: [],
dataset_setting: { dataset_setting: {
@ -108,7 +108,7 @@ watch(dialogVisible, (bool) => {
name: '', name: '',
desc: '', desc: '',
model_id: '', model_id: '',
multiple_rounds_dialogue: false, dialogue_number: 0,
prologue: t('views.application.prompt.defaultPrologue'), prologue: t('views.application.prompt.defaultPrologue'),
dataset_id_list: [], dataset_id_list: [],
dataset_setting: { dataset_setting: {

View File

@ -67,7 +67,7 @@
<el-button @click.prevent="dialogVisible = false" :loading="loading"> <el-button @click.prevent="dialogVisible = false" :loading="loading">
{{ $t('views.application.applicationForm.buttons.cancel') }} {{ $t('views.application.applicationForm.buttons.cancel') }}
</el-button> </el-button>
<el-button type="primary" @click="submitValid(applicationFormRef)" :loading="loading"> <el-button type="primary" @click="submitHandle(applicationFormRef)" :loading="loading">
{{ $t('views.application.applicationForm.buttons.create') }} {{ $t('views.application.applicationForm.buttons.create') }}
</el-button> </el-button>
</span> </span>
@ -104,7 +104,7 @@ const applicationForm = ref<ApplicationFormType>({
name: '', name: '',
desc: '', desc: '',
model_id: '', model_id: '',
multiple_rounds_dialogue: false, dialogue_number: 1,
prologue: t('views.application.prompt.defaultPrologue'), prologue: t('views.application.prompt.defaultPrologue'),
dataset_id_list: [], dataset_id_list: [],
dataset_setting: { dataset_setting: {
@ -118,9 +118,19 @@ const applicationForm = ref<ApplicationFormType>({
} }
}, },
model_setting: { model_setting: {
prompt: defaultPrompt prompt: defaultPrompt,
system: '你是 xxx 小助手',
no_references_prompt: '{question}'
}, },
model_params_setting: {},
problem_optimization: false, problem_optimization: false,
problem_optimization_prompt:
'()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中',
stt_model_id: '',
tts_model_id: '',
stt_model_enable: false,
tts_model_enable: false,
tts_type: 'BROWSER',
type: 'SIMPLE' type: 'SIMPLE'
}) })
@ -147,7 +157,7 @@ watch(dialogVisible, (bool) => {
name: '', name: '',
desc: '', desc: '',
model_id: '', model_id: '',
multiple_rounds_dialogue: false, dialogue_number: 1,
prologue: t('views.application.prompt.defaultPrologue'), prologue: t('views.application.prompt.defaultPrologue'),
dataset_id_list: [], dataset_id_list: [],
dataset_setting: { dataset_setting: {
@ -161,9 +171,19 @@ watch(dialogVisible, (bool) => {
} }
}, },
model_setting: { model_setting: {
prompt: defaultPrompt prompt: defaultPrompt,
system: '你是 xxx 小助手',
no_references_prompt: '{question}'
}, },
model_params_setting: {},
problem_optimization: false, problem_optimization: false,
problem_optimization_prompt:
'()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中',
stt_model_id: '',
tts_model_id: '',
stt_model_enable: false,
tts_model_enable: false,
tts_type: 'BROWSER',
type: 'SIMPLE' type: 'SIMPLE'
} }
applicationFormRef.value?.clearValidate() applicationFormRef.value?.clearValidate()
@ -174,21 +194,6 @@ const open = () => {
dialogVisible.value = true dialogVisible.value = true
} }
const submitValid = (formEl: FormInstance | undefined) => {
if (user.isEnterprise()) {
submitHandle(formEl)
} else {
common
.asyncGetValid(ValidType.Application, ValidCount.Application, loading)
.then(async (res: any) => {
if (res?.data) {
submitHandle(formEl)
} else {
MsgAlert('提示', '社区版最多支持 5 个应用,如需拥有更多应用,请升级为专业版。')
}
})
}
}
const submitHandle = async (formEl: FormInstance | undefined) => { const submitHandle = async (formEl: FormInstance | undefined) => {
if (!formEl) return if (!formEl) return
await formEl.validate((valid) => { await formEl.validate((valid) => {

View File

@ -14,7 +14,11 @@
<el-form-item <el-form-item
:label="$t('views.application.applicationForm.dialogues.selectSearchMode')" :label="$t('views.application.applicationForm.dialogues.selectSearchMode')"
> >
<el-radio-group v-model="form.search_mode" class="card__radio" @change="changeHandle"> <el-radio-group
v-model="form.dataset_setting.search_mode"
class="card__radio"
@change="changeHandle"
>
<el-card <el-card
shadow="never" shadow="never"
class="mb-16" class="mb-16"
@ -32,7 +36,7 @@
<el-card <el-card
shadow="never" shadow="never"
class="mb-16" class="mb-16"
:class="form.search_mode === 'keywords' ? 'active' : ''" :class="form.dataset_setting.search_mode === 'keywords' ? 'active' : ''"
> >
<el-radio value="keywords" size="large"> <el-radio value="keywords" size="large">
<p class="mb-4"> <p class="mb-4">
@ -43,7 +47,10 @@
}}</el-text> }}</el-text>
</el-radio> </el-radio>
</el-card> </el-card>
<el-card shadow="never" :class="form.search_mode === 'blend' ? 'active' : ''"> <el-card
shadow="never"
:class="form.dataset_setting.search_mode === 'blend' ? 'active' : ''"
>
<el-radio value="blend" size="large"> <el-radio value="blend" size="large">
<p class="mb-4"> <p class="mb-4">
{{ $t('views.application.applicationForm.dialogues.hybridSearch') }} {{ $t('views.application.applicationForm.dialogues.hybridSearch') }}
@ -69,7 +76,7 @@
</div> </div>
</template> </template>
<el-input-number <el-input-number
v-model="form.similarity" v-model="form.dataset_setting.similarity"
:min="0" :min="0"
:max="form.search_mode === 'blend' ? 2 : 1" :max="form.search_mode === 'blend' ? 2 : 1"
:precision="3" :precision="3"
@ -98,7 +105,7 @@
<el-form-item :label="$t('views.application.applicationForm.dialogues.maxCharacters')"> <el-form-item :label="$t('views.application.applicationForm.dialogues.maxCharacters')">
<el-slider <el-slider
v-model="form.max_paragraph_char_number" v-model="form.dataset_setting.max_paragraph_char_number"
show-input show-input
:show-input-controls="false" :show-input-controls="false"
:min="500" :min="500"
@ -119,34 +126,23 @@
:hide-required-asterisk="true" :hide-required-asterisk="true"
> >
<el-radio-group <el-radio-group
v-model="form.no_references_setting.status" v-model="form.dataset_setting.no_references_setting.status"
class="radio-block mb-16" class="radio-block"
> >
<div> <div>
<el-radio value="ai_questioning"> <el-radio value="ai_questioning">
<p> <p>
{{ $t('views.application.applicationForm.dialogues.continueQuestioning') }} {{ $t('views.application.applicationForm.dialogues.continueQuestioning') }}
</p> </p>
<el-form-item
v-if="form.no_references_setting.status === 'ai_questioning'"
:label="$t('views.application.applicationForm.form.prompt.label')"
prop="ai_questioning"
>
<el-input
v-model="noReferencesform.ai_questioning"
:rows="2"
type="textarea"
maxlength="2048"
:placeholder="defaultValue['ai_questioning']"
/>
</el-form-item>
</el-radio> </el-radio>
</div> </div>
<div class="mt-8"> <div>
<el-radio value="designated_answer"> <el-radio value="designated_answer">
<p>{{ $t('views.application.applicationForm.dialogues.provideAnswer') }}</p> <p>{{ $t('views.application.applicationForm.dialogues.provideAnswer') }}</p>
<el-form-item <el-form-item
v-if="form.no_references_setting.status === 'designated_answer'" v-if="
form.dataset_setting.no_references_setting.status === 'designated_answer'
"
prop="designated_answer" prop="designated_answer"
> >
<el-input <el-input
@ -162,6 +158,29 @@
</el-radio-group> </el-radio-group>
</el-form> </el-form>
</el-form-item> </el-form-item>
<el-form-item @click.prevent v-if="!isWorkflowType">
<template #label>
<div class="flex align-center">
<span class="mr-4">{{
$t('views.application.applicationForm.form.problemOptimization.label')
}}</span>
</div>
</template>
<el-switch size="small" v-model="form.problem_optimization"></el-switch>
</el-form-item>
<el-form-item
v-if="form.problem_optimization"
:label="$t('views.application.applicationForm.form.prompt.label')"
>
<el-input
v-model="form.problem_optimization_prompt"
:rows="6"
type="textarea"
maxlength="2048"
:placeholder="defaultPrompt"
/>
</el-form-item>
</el-form> </el-form>
</div> </div>
</el-scrollbar> </el-scrollbar>
@ -195,15 +214,21 @@ const defaultValue = {
designated_answer: t('views.application.applicationForm.dialogues.designated_answer') designated_answer: t('views.application.applicationForm.dialogues.designated_answer')
} }
const defaultPrompt = `()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中`
const form = ref<any>({ const form = ref<any>({
search_mode: 'embedding', dataset_setting: {
top_n: 3, search_mode: 'embedding',
similarity: 0.6, top_n: 3,
max_paragraph_char_number: 5000, similarity: 0.6,
no_references_setting: { max_paragraph_char_number: 5000,
status: 'ai_questioning', no_references_setting: {
value: '{question}' status: 'ai_questioning',
} value: '{question}'
}
},
problem_optimization: false,
problem_optimization_prompt: defaultPrompt
}) })
const noReferencesform = ref<any>({ const noReferencesform = ref<any>({
@ -236,14 +261,18 @@ const isWorkflowType = ref(false)
watch(dialogVisible, (bool) => { watch(dialogVisible, (bool) => {
if (!bool) { if (!bool) {
form.value = { form.value = {
search_mode: 'embedding', dataset_setting: {
top_n: 3, search_mode: 'embedding',
similarity: 0.6, top_n: 3,
max_paragraph_char_number: 5000, similarity: 0.6,
no_references_setting: { max_paragraph_char_number: 5000,
status: 'ai_questioning', no_references_setting: {
value: '' status: 'ai_questioning',
} value: '{question}'
}
},
problem_optimization: false,
problem_optimization_prompt: ''
} }
noReferencesform.value = { noReferencesform.value = {
ai_questioning: defaultValue['ai_questioning'], ai_questioning: defaultValue['ai_questioning'],
@ -255,9 +284,16 @@ watch(dialogVisible, (bool) => {
const open = (data: any, type?: string) => { const open = (data: any, type?: string) => {
isWorkflowType.value = isWorkFlow(type) isWorkflowType.value = isWorkFlow(type)
form.value = { ...form.value, ...cloneDeep(data) } form.value = {
noReferencesform.value[form.value.no_references_setting.status] = dataset_setting: { ...data.dataset_setting },
form.value.no_references_setting.value problem_optimization: data.problem_optimization,
problem_optimization_prompt: data.problem_optimization_prompt
}
if (!isWorkflowType.value) {
noReferencesform.value[form.value.dataset_setting.no_references_setting.status] =
form.value.dataset_setting.no_references_setting.value
}
dialogVisible.value = true dialogVisible.value = true
} }
@ -270,8 +306,8 @@ const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return if (!formEl) return
await formEl.validate((valid, fields) => { await formEl.validate((valid, fields) => {
if (valid) { if (valid) {
form.value.no_references_setting.value = form.value.dataset_setting.no_references_setting.value =
noReferencesform.value[form.value.no_references_setting.status] noReferencesform.value[form.value.dataset_setting.no_references_setting.status]
emit('refresh', form.value) emit('refresh', form.value)
dialogVisible.value = false dialogVisible.value = false
} }
@ -281,9 +317,9 @@ const submit = async (formEl: FormInstance | undefined) => {
function changeHandle(val: string) { function changeHandle(val: string) {
if (val === 'keywords') { if (val === 'keywords') {
form.value.similarity = 0 form.value.dataset_setting.similarity = 0
} else { } else {
form.value.similarity = 0.6 form.value.dataset_setting.similarity = 0.6
} }
} }

View File

@ -131,9 +131,11 @@ import { MsgSuccess, MsgConfirm } from '@/utils/message'
import { isAppIcon } from '@/utils/application' import { isAppIcon } from '@/utils/application'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { isWorkFlow } from '@/utils/application' import { isWorkFlow } from '@/utils/application'
import useStore from '@/stores' import { ValidType, ValidCount } from '@/enums/common'
import { t } from '@/locales' import { t } from '@/locales'
const { application, user } = useStore() import useStore from '@/stores'
const { application, user, common } = useStore()
const router = useRouter() const router = useRouter()
const CopyApplicationDialogRef = ref() const CopyApplicationDialogRef = ref()
@ -168,7 +170,27 @@ function settingApplication(row: any) {
} }
function openCreateDialog() { function openCreateDialog() {
CreateApplicationDialogRef.value.open() if (user.isEnterprise()) {
CreateApplicationDialogRef.value.open()
} else {
MsgConfirm(`提示`, '社区版最多支持 5 个应用,如需拥有更多应用,请升级为专业版。', {
cancelButtonText: '确定',
confirmButtonText: '购买专业版',
confirmButtonClass: 'primary'
})
.then(() => {
window.open('https://maxkb.cn/pricing.html', '_blank')
})
.catch(() => {
common
.asyncGetValid(ValidType.Application, ValidCount.Application, loading)
.then(async (res: any) => {
if (res?.data) {
CreateApplicationDialogRef.value.open()
}
})
})
}
} }
function searchHandle() { function searchHandle() {
@ -222,7 +244,7 @@ onMounted(() => {
.status-tag { .status-tag {
position: absolute; position: absolute;
right: 16px; right: 16px;
top: 20px; top: 13px;
} }
} }
.dropdown-custom-switch { .dropdown-custom-switch {

View File

@ -73,7 +73,7 @@
<el-button @click.prevent="dialogVisible = false" :loading="loading"> <el-button @click.prevent="dialogVisible = false" :loading="loading">
{{ $t('views.application.applicationForm.buttons.cancel') }} {{ $t('views.application.applicationForm.buttons.cancel') }}
</el-button> </el-button>
<el-button type="primary" @click="submitValid" :loading="loading"> <el-button type="primary" @click="submitHandle" :loading="loading">
{{ $t('views.application.applicationForm.buttons.create') }} {{ $t('views.application.applicationForm.buttons.create') }}
</el-button> </el-button>
</span> </span>
@ -124,19 +124,6 @@ const open = () => {
dialogVisible.value = true dialogVisible.value = true
} }
const submitValid = () => {
if (user.isEnterprise()) {
submitHandle()
} else {
common.asyncGetValid(ValidType.Dataset, ValidCount.Dataset, loading).then(async (res: any) => {
if (res?.data) {
submitHandle()
} else {
MsgAlert('提示', '社区版最多支持 50 个知识库,如需拥有更多知识库,请升级为专业版。')
}
})
}
}
const submitHandle = async () => { const submitHandle = async () => {
if (await BaseFormRef.value?.validate()) { if (await BaseFormRef.value?.validate()) {
await DatasetFormRef.value.validate((valid: any) => { await DatasetFormRef.value.validate((valid: any) => {

View File

@ -107,7 +107,7 @@
</InfiniteScroll> </InfiniteScroll>
</div> </div>
<SyncWebDialog ref="SyncWebDialogRef" @refresh="refresh" /> <SyncWebDialog ref="SyncWebDialogRef" @refresh="refresh" />
<CreateDatasetDialog ref="CreateDatasetDialogRef"/> <CreateDatasetDialog ref="CreateDatasetDialogRef" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -118,6 +118,10 @@ import datasetApi from '@/api/dataset'
import { MsgSuccess, MsgConfirm } from '@/utils/message' import { MsgSuccess, MsgConfirm } from '@/utils/message'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { numberFormat } from '@/utils/utils' import { numberFormat } from '@/utils/utils'
import { ValidType, ValidCount } from '@/enums/common'
import useStore from '@/stores'
const { user, common } = useStore()
const router = useRouter() const router = useRouter()
const CreateDatasetDialogRef = ref() const CreateDatasetDialogRef = ref()
@ -133,7 +137,27 @@ const paginationConfig = reactive({
const searchValue = ref('') const searchValue = ref('')
function openCreateDialog() { function openCreateDialog() {
CreateDatasetDialogRef.value.open() if (user.isEnterprise()) {
CreateDatasetDialogRef.value.open()
} else {
MsgConfirm(`提示`, '社区版最多支持 50 个知识库,如需拥有更多知识库,请升级为专业版。', {
cancelButtonText: '确定',
confirmButtonText: '购买专业版',
confirmButtonClass: 'primary'
})
.then(() => {
window.open('https://maxkb.cn/pricing.html', '_blank')
})
.catch(() => {
common
.asyncGetValid(ValidType.Dataset, ValidCount.Dataset, loading)
.then(async (res: any) => {
if (res?.data) {
CreateDatasetDialogRef.value.open()
}
})
})
}
} }
function refresh() { function refresh() {
@ -198,7 +222,7 @@ onMounted(() => {
.delete-button { .delete-button {
position: absolute; position: absolute;
right: 12px; right: 12px;
top: 18px; top: 13px;
height: auto; height: auto;
} }
.footer-content { .footer-content {

View File

@ -23,6 +23,9 @@
<el-button @click="openDatasetDialog()" :disabled="multipleSelection.length === 0"> <el-button @click="openDatasetDialog()" :disabled="multipleSelection.length === 0">
迁移 迁移
</el-button> </el-button>
<el-button @click="batchRefresh" :disabled="multipleSelection.length === 0">
重新向量化
</el-button>
<el-button @click="openBatchEditDocument" :disabled="multipleSelection.length === 0"> <el-button @click="openBatchEditDocument" :disabled="multipleSelection.length === 0">
设置 设置
</el-button> </el-button>
@ -538,6 +541,19 @@ function deleteMulDocument() {
}) })
} }
function batchRefresh() {
const arr: string[] = []
multipleSelection.value.map((v) => {
if (v) {
arr.push(v.id)
}
})
documentApi.batchRefresh(id, arr, loading).then(() => {
MsgSuccess('批量重新向量化成功')
multipleTableRef.value?.clearSelection()
})
}
function deleteDocument(row: any) { function deleteDocument(row: any) {
MsgConfirm( MsgConfirm(
`是否删除文档:${row.name} ?`, `是否删除文档:${row.name} ?`,

View File

@ -19,7 +19,7 @@
placeholder="请输入函数名称" placeholder="请输入函数名称"
maxlength="64" maxlength="64"
show-word-limit show-word-limit
@blur="form.name = form.name.trim()" @blur="form.name = form.name?.trim()"
/> />
</el-form-item> </el-form-item>
<el-form-item label="描述"> <el-form-item label="描述">
@ -30,9 +30,35 @@
maxlength="128" maxlength="128"
show-word-limit show-word-limit
:autosize="{ minRows: 3 }" :autosize="{ minRows: 3 }"
@blur="form.desc = form.desc.trim()" @blur="form.desc = form.desc?.trim()"
/> />
</el-form-item> </el-form-item>
<el-form-item prop="permission_type">
<template #label>
<span>权限</span>
</template>
<el-radio-group v-model="form.permission_type" class="card__radio">
<el-row :gutter="16">
<template v-for="(value, key) of PermissionType" :key="key">
<el-col :span="12">
<el-card
shadow="never"
class="mb-16"
:class="form.permission_type === key ? 'active' : ''"
>
<el-radio :value="key" size="large">
<p class="mb-4">{{ value }}</p>
<el-text type="info">
{{ PermissionDesc[key] }}
</el-text>
</el-radio>
</el-card>
</el-col>
</template>
</el-row>
</el-radio-group>
</el-form-item>
</el-form> </el-form>
<div class="flex-between"> <div class="flex-between">
<h4 class="title-decoration-1 mb-16"> <h4 class="title-decoration-1 mb-16">
@ -137,7 +163,7 @@ import functionLibApi from '@/api/function-lib'
import type { FormInstance } from 'element-plus' import type { FormInstance } from 'element-plus'
import { MsgSuccess, MsgConfirm } from '@/utils/message' import { MsgSuccess, MsgConfirm } from '@/utils/message'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
import { PermissionType, PermissionDesc } from '@/enums/model'
const props = defineProps({ const props = defineProps({
title: String title: String
}) })
@ -158,7 +184,8 @@ const form = ref<functionLibData>({
name: '', name: '',
desc: '', desc: '',
code: '', code: '',
input_field_list: [] input_field_list: [],
permission_type: 'PRIVATE'
}) })
const dialogVisible = ref(false) const dialogVisible = ref(false)
@ -173,13 +200,15 @@ watch(visible, (bool) => {
name: '', name: '',
desc: '', desc: '',
code: '', code: '',
input_field_list: [] input_field_list: [],
permission_type: 'PRIVATE'
} }
} }
}) })
const rules = reactive({ const rules = reactive({
name: [{ required: true, message: '请输入函数名称', trigger: 'blur' }] name: [{ required: true, message: '请输入函数名称', trigger: 'blur' }],
permission_type: [{ required: true, message: '请选择', trigger: 'change' }]
}) })
function openCodemirrorDialog() { function openCodemirrorDialog() {

View File

@ -11,7 +11,11 @@
clearable clearable
/> />
</div> </div>
<div v-loading.fullscreen.lock="paginationConfig.current_page === 1 && loading"> <div
v-loading.fullscreen.lock="
(paginationConfig.current_page === 1 && loading) || changeStateloading
"
>
<InfiniteScroll <InfiniteScroll
:size="functionLibList.length" :size="functionLibList.length"
:total="paginationConfig.total" :total="paginationConfig.total"
@ -45,20 +49,34 @@
<img src="@/assets/icon_function_outlined.svg" style="width: 58%" alt="" /> <img src="@/assets/icon_function_outlined.svg" style="width: 58%" alt="" />
</AppAvatar> </AppAvatar>
</template> </template>
<div class="status-button">
<el-tag class="info-tag" v-if="item.permission_type === 'PUBLIC'">公用</el-tag>
<el-tag class="danger-tag" v-else-if="item.permission_type === 'PRIVATE'"
>私有</el-tag
>
</div>
<template #footer> <template #footer>
<div class="footer-content"> <div class="footer-content flex-between">
<el-tooltip effect="dark" content="复制" placement="top"> <div>
<el-button text @click.stop="copyFunctionLib(item)"> <el-tooltip effect="dark" content="复制" placement="top">
<AppIcon iconName="app-copy"></AppIcon> <el-button text @click.stop="copyFunctionLib(item)">
</el-button> <AppIcon iconName="app-copy"></AppIcon>
</el-tooltip> </el-button>
<el-divider direction="vertical" /> </el-tooltip>
<el-tooltip effect="dark" content="删除" placement="top"> <el-divider direction="vertical" />
<el-button text @click.stop="deleteFunctionLib(item)"> <el-tooltip effect="dark" content="删除" placement="top">
<el-icon><Delete /></el-icon> <el-button text @click.stop="deleteFunctionLib(item)">
</el-button> <el-icon><Delete /></el-icon>
</el-tooltip> </el-button>
</el-tooltip>
</div>
<div @click.stop>
<el-switch
v-model="item.is_active"
@change="changeState($event, item)"
size="small"
/>
</div>
</div> </div>
</template> </template>
</CardBox> </CardBox>
@ -89,6 +107,7 @@ const paginationConfig = reactive({
const searchValue = ref('') const searchValue = ref('')
const title = ref('') const title = ref('')
const changeStateloading = ref(false)
function openCreateDialog(data?: any) { function openCreateDialog(data?: any) {
title.value = data ? '编辑函数' : '创建函数' title.value = data ? '编辑函数' : '创建函数'
@ -102,6 +121,33 @@ function searchHandle() {
getList() getList()
} }
function changeState(bool: Boolean, row: any) {
if (!bool) {
MsgConfirm(
`是否禁用函数:${row.name} ?`,
`禁用后,引用了该函数的应用提问时会报错 ,请谨慎操作。`,
{
confirmButtonText: '禁用',
confirmButtonClass: 'danger'
}
)
.then(() => {
const obj = {
is_active: bool
}
functionLibApi.putFunctionLib(row.id, obj, changeStateloading).then((res) => {})
})
.catch(() => {
row.is_active = true
})
} else {
const obj = {
is_active: bool
}
functionLibApi.putFunctionLib(row.id, obj, changeStateloading).then((res) => {})
}
}
function deleteFunctionLib(row: any) { function deleteFunctionLib(row: any) {
MsgConfirm( MsgConfirm(
`是否删除函数:${row.name} ?`, `是否删除函数:${row.name} ?`,
@ -155,4 +201,13 @@ onMounted(() => {
getList() getList()
}) })
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped>
.function-lib-list-container {
.status-button {
position: absolute;
right: 12px;
top: 13px;
height: auto;
}
}
</style>

View File

@ -140,14 +140,22 @@ function createUser() {
title.value = '创建用户' title.value = '创建用户'
UserDialogRef.value.open() UserDialogRef.value.open()
} else { } else {
common.asyncGetValid(ValidType.User, ValidCount.User, loading).then((res: any) => { MsgConfirm(`提示`, '社区版最多支持 2 个用户,如需拥有更多用户,请升级为专业版。', {
if (res?.data) { cancelButtonText: '确定',
title.value = '创建用户' confirmButtonText: '购买专业版',
UserDialogRef.value.open() confirmButtonClass: 'primary'
} else {
MsgAlert('提示', '社区版最多支持 2 个用户,如需拥有更多用户,请升级为专业版。')
}
}) })
.then(() => {
window.open('https://maxkb.cn/pricing.html', '_blank')
})
.catch(() => {
common.asyncGetValid(ValidType.User, ValidCount.User, loading).then(async (res: any) => {
if (res?.data) {
title.value = '创建用户'
UserDialogRef.value.open()
}
})
})
} }
} }

View File

@ -6,7 +6,7 @@
style="overflow: visible" style="overflow: visible"
> >
<div v-resize="resizeStepContainer"> <div v-resize="resizeStepContainer">
<div class="flex-between mb-16"> <div class="flex-between">
<div <div
class="flex align-center" class="flex align-center"
:style="{ maxWidth: node_status == 200 ? 'calc(100% - 55px)' : 'calc(100% - 85px)' }" :style="{ maxWidth: node_status == 200 ? 'calc(100% - 55px)' : 'calc(100% - 85px)' }"
@ -33,6 +33,11 @@
@click.stop @click.stop
v-if="showOperate(nodeModel.type)" v-if="showOperate(nodeModel.type)"
> >
<el-button text @click="showNode = !showNode" class="mr-4">
<el-icon class="mr-8 arrow-icon" :class="showNode ? 'rotate-90' : ''"
><CaretRight
/></el-icon>
</el-button>
<el-dropdown :teleported="false" trigger="click"> <el-dropdown :teleported="false" trigger="click">
<el-button text> <el-button text>
<el-icon class="color-secondary"><MoreFilled /></el-icon> <el-icon class="color-secondary"><MoreFilled /></el-icon>
@ -46,42 +51,44 @@
</el-dropdown> </el-dropdown>
</div> </div>
</div> </div>
<el-collapse-transition>
<div @mousedown.stop @keydown.stop @click.stop> <div @mousedown.stop @keydown.stop @click.stop v-if="showNode" class="mt-16">
<el-alert <el-alert
v-if="node_status != 200" v-if="node_status != 200"
class="mb-16" class="mb-16"
title="该函数不可用" title="该函数不可用"
type="error" type="error"
show-icon show-icon
:closable="false" :closable="false"
/> />
<slot></slot> <slot></slot>
<template v-if="nodeFields.length > 0"> <template v-if="nodeFields.length > 0">
<h5 class="title-decoration-1 mb-8 mt-8">参数输出</h5> <h5 class="title-decoration-1 mb-8 mt-8">参数输出</h5>
<template v-for="(item, index) in nodeFields" :key="index"> <template v-for="(item, index) in nodeFields" :key="index">
<div <div
class="flex-between border-r-4 p-8-12 mb-8 layout-bg lighter" class="flex-between border-r-4 p-8-12 mb-8 layout-bg lighter"
@mouseenter="showicon = index" @mouseenter="showicon = index"
@mouseleave="showicon = null" @mouseleave="showicon = null"
>
<span style="max-width: 92%">{{ item.label }} {{ '{' + item.value + '}' }}</span>
<el-tooltip
effect="dark"
content="复制参数"
placement="top"
v-if="showicon === index"
> >
<el-button link @click="copyClick(item.globeLabel)" style="padding: 0"> <span style="max-width: 92%">{{ item.label }} {{ '{' + item.value + '}' }}</span>
<AppIcon iconName="app-copy"></AppIcon> <el-tooltip
</el-button> effect="dark"
</el-tooltip> content="复制参数"
</div> placement="top"
v-if="showicon === index"
>
<el-button link @click="copyClick(item.globeLabel)" style="padding: 0">
<AppIcon iconName="app-copy"></AppIcon>
</el-button>
</el-tooltip>
</div>
</template>
</template> </template>
</template> </div>
</div> </el-collapse-transition>
</div> </div>
</div> </div>
<el-collapse-transition> <el-collapse-transition>
<DropdownMenu <DropdownMenu
v-if="showAnchor" v-if="showAnchor"
@ -122,6 +129,7 @@ const height = ref<{
}) })
const showAnchor = ref<boolean>(false) const showAnchor = ref<boolean>(false)
const anchorData = ref<any>() const anchorData = ref<any>()
const showNode = ref<boolean>(true)
const node_status = computed(() => { const node_status = computed(() => {
if (props.nodeModel.properties.status) { if (props.nodeModel.properties.status) {
return props.nodeModel.properties.status return props.nodeModel.properties.status
@ -240,6 +248,9 @@ onMounted(() => {
border: 1px solid #f54a45 !important; border: 1px solid #f54a45 !important;
} }
} }
.arrow-icon {
transition: 0.2s;
}
} }
:deep(.el-card) { :deep(.el-card) {
overflow: visible; overflow: visible;

View File

@ -118,6 +118,7 @@
</template> </template>
<el-select <el-select
v-if="form_data.stt_model_enable"
v-model="form_data.stt_model_id" v-model="form_data.stt_model_id"
class="w-full" class="w-full"
popper-class="select-model" popper-class="select-model"
@ -182,12 +183,12 @@
<el-switch size="small" v-model="form_data.tts_model_enable" /> <el-switch size="small" v-model="form_data.tts_model_enable" />
</div> </div>
</template> </template>
<el-radio-group v-model="form_data.tts_type"> <el-radio-group v-model="form_data.tts_type" v-if="form_data.tts_model_enable">
<el-radio label="浏览器播放(免费)" value="BROWSER" /> <el-radio label="浏览器播放(免费)" value="BROWSER" />
<el-radio label="TTS模型" value="TTS" /> <el-radio label="TTS模型" value="TTS" />
</el-radio-group> </el-radio-group>
<el-select <el-select
v-if="form_data.tts_type === 'TTS'" v-if="form_data.tts_type === 'TTS' && form_data.tts_model_enable"
v-model="form_data.tts_model_id" v-model="form_data.tts_model_id"
class="w-full" class="w-full"
popper-class="select-model" popper-class="select-model"
@ -412,6 +413,7 @@ function openAddDialog(data?: any, index?: any) {
function deleteField(index: any) { function deleteField(index: any) {
inputFieldList.value.splice(index, 1) inputFieldList.value.splice(index, 1)
props.nodeModel.graphModel.eventCenter.emit('refreshFieldList', inputFieldList.value)
} }
function refreshFieldList(data: any) { function refreshFieldList(data: any) {
@ -428,6 +430,7 @@ function refreshFieldList(data: any) {
} }
currentIndex.value = null currentIndex.value = null
FieldFormDialogRef.value.close() FieldFormDialogRef.value.close()
props.nodeModel.graphModel.eventCenter.emit('refreshFieldList', inputFieldList.value)
} }
onMounted(() => { onMounted(() => {

View File

@ -22,6 +22,7 @@
:gutter="8" :gutter="8"
style="margin-bottom: 8px" style="margin-bottom: 8px"
v-for="(reranker_reference, index) in form_data.reranker_reference_list" v-for="(reranker_reference, index) in form_data.reranker_reference_list"
:key="index"
> >
<el-col :span="22"> <el-col :span="22">
<el-form-item <el-form-item
@ -212,7 +213,7 @@ const form = {
const providerOptions = ref<Array<Provider>>([]) const providerOptions = ref<Array<Provider>>([])
const modelOptions = ref<any>(null) const modelOptions = ref<any>(null)
const openParamSettingDialog = () => { const openParamSettingDialog = () => {
ParamSettingDialogRef.value?.open(form_data.value.dataset_setting, 'WORK_FLOW') ParamSettingDialogRef.value?.open(form_data.value, 'WORK_FLOW')
} }
const deleteCondition = (index: number) => { const deleteCondition = (index: number) => {
const list = cloneDeep(props.nodeModel.properties.node_data.reranker_reference_list) const list = cloneDeep(props.nodeModel.properties.node_data.reranker_reference_list)
@ -242,7 +243,7 @@ const form_data = computed({
} }
}) })
function refreshParam(data: any) { function refreshParam(data: any) {
set(props.nodeModel.properties.node_data, 'reranker_setting', data) set(props.nodeModel.properties.node_data, 'reranker_setting', data.dataset_setting)
} }
function getModel() { function getModel() {
if (id) { if (id) {

View File

@ -159,11 +159,11 @@ const datasetList = ref<any>([])
const datasetLoading = ref(false) const datasetLoading = ref(false)
function refreshParam(data: any) { function refreshParam(data: any) {
set(props.nodeModel.properties.node_data, 'dataset_setting', data) set(props.nodeModel.properties.node_data, 'dataset_setting', data.dataset_setting)
} }
const openParamSettingDialog = () => { const openParamSettingDialog = () => {
ParamSettingDialogRef.value?.open(form_data.value.dataset_setting, 'WORK_FLOW') ParamSettingDialogRef.value?.open(form_data.value, 'WORK_FLOW')
} }
function removeDataset(id: any) { function removeDataset(id: any) {

View File

@ -2,25 +2,18 @@
<NodeContainer :nodeModel="nodeModel"> <NodeContainer :nodeModel="nodeModel">
<h5 class="title-decoration-1 mb-8">全局变量</h5> <h5 class="title-decoration-1 mb-8">全局变量</h5>
<div <div
v-for="item in nodeModel.properties.config.globalFields"
class="flex-between border-r-4 p-8-12 mb-8 layout-bg lighter" class="flex-between border-r-4 p-8-12 mb-8 layout-bg lighter"
@mouseenter="showicon = true" @mouseenter="showicon = true"
@mouseleave="showicon = false" @mouseleave="showicon = false"
> >
<span>当前时间 {time}</span> <span>{{ item.label }} {{ '{' + item.value + '}' }}</span>
<el-tooltip effect="dark" content="复制参数" placement="top" v-if="showicon === true"> <el-tooltip effect="dark" content="复制参数" placement="top" v-if="showicon === true">
<el-button link @click="copyClick(globeLabel)" style="padding: 0"> <el-button
<AppIcon iconName="app-copy"></AppIcon> link
</el-button> @click="copyClick('{{' + '全局变量.' + item.value + '}}')"
</el-tooltip> style="padding: 0"
</div> >
<div v-for="(item, index) in inputFieldList" :key="index"
class="flex-between border-r-4 p-8-12 mb-8 layout-bg lighter"
@mouseenter="showicon = true"
@mouseleave="showicon = false"
>
<span>{{ item.name }} {{ '{' + item.variable + '}' }}</span>
<el-tooltip effect="dark" content="复制参数" placement="top" v-if="showicon === true">
<el-button link @click="copyClick('{{' + '全局变量.' + item.variable + '}}')" style="padding: 0">
<AppIcon iconName="app-copy"></AppIcon> <AppIcon iconName="app-copy"></AppIcon>
</el-button> </el-button>
</el-tooltip> </el-tooltip>
@ -28,34 +21,36 @@
</NodeContainer> </NodeContainer>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { set } from 'lodash' import { cloneDeep, set } from 'lodash'
import NodeContainer from '@/workflow/common/NodeContainer.vue' import NodeContainer from '@/workflow/common/NodeContainer.vue'
import { copyClick } from '@/utils/clipboard' import { copyClick } from '@/utils/clipboard'
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
const props = defineProps<{ nodeModel: any }>() const props = defineProps<{ nodeModel: any }>()
const globeLabel = '{{全局变量.time}}'
const showicon = ref(false) const showicon = ref(false)
const globalFields = [
{ label: '当前时间', value: 'time' },
{ label: '历史聊天记录', value: 'history_context' },
{ label: '对话id', value: 'chat_id' }
]
const inputFieldList = ref<any[]>([]) const inputFieldList = ref<any[]>([])
onMounted(() => { const getRefreshFieldList = () => {
props.nodeModel.graphModel.nodes return props.nodeModel.graphModel.nodes
.filter((v: any) => v.id === 'base-node') .filter((v: any) => v.id === 'base-node')
.map((v: any) => { .map((v: any) => cloneDeep(v.properties.input_field_list))
// eslint-disable-next-line vue/no-mutating-props .reduce((x: any, y: any) => [...x, ...y], [])
props.nodeModel.properties.config.globalFields = [ .map((i: any) => ({ label: i.name, value: i.variable }))
{ }
label: '当前时间', const refreshFieldList = () => {
value: 'time' const refreshFieldList = getRefreshFieldList()
}, ...v.properties.input_field_list.map((i: any) => { set(props.nodeModel.properties.config, 'globalFields', [...globalFields, ...refreshFieldList])
return { label: i.name, value: i.variable } }
}) props.nodeModel.graphModel.eventCenter.on('refreshFieldList', refreshFieldList)
]
inputFieldList.value = v.properties.input_field_list onMounted(() => {
}) refreshFieldList()
}) })
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>