mirror of
https://github.com/langgenius/dify.git
synced 2026-01-08 07:14:14 +00:00
Merge remote-tracking branch 'origin/feat/workflow' into feat/workflow
# Conflicts: # api/core/workflow/nodes/question_classifier/question_classifier_node.py
This commit is contained in:
@@ -21,6 +21,8 @@ class AdvancedPromptTransform(PromptTransform):
|
||||
"""
|
||||
Advanced Prompt Transform for Workflow LLM Node.
|
||||
"""
|
||||
def __init__(self, with_variable_tmpl: bool = False) -> None:
|
||||
self.with_variable_tmpl = with_variable_tmpl
|
||||
|
||||
def get_prompt(self, prompt_template: Union[list[ChatModelMessage], CompletionModelPromptTemplate],
|
||||
inputs: dict,
|
||||
@@ -74,7 +76,7 @@ class AdvancedPromptTransform(PromptTransform):
|
||||
|
||||
prompt_messages = []
|
||||
|
||||
prompt_template = PromptTemplateParser(template=raw_prompt)
|
||||
prompt_template = PromptTemplateParser(template=raw_prompt, with_variable_tmpl=self.with_variable_tmpl)
|
||||
prompt_inputs = {k: inputs[k] for k in prompt_template.variable_keys if k in inputs}
|
||||
|
||||
prompt_inputs = self._set_context_variable(context, prompt_template, prompt_inputs)
|
||||
@@ -128,7 +130,7 @@ class AdvancedPromptTransform(PromptTransform):
|
||||
for prompt_item in raw_prompt_list:
|
||||
raw_prompt = prompt_item.text
|
||||
|
||||
prompt_template = PromptTemplateParser(template=raw_prompt)
|
||||
prompt_template = PromptTemplateParser(template=raw_prompt, with_variable_tmpl=self.with_variable_tmpl)
|
||||
prompt_inputs = {k: inputs[k] for k in prompt_template.variable_keys if k in inputs}
|
||||
|
||||
prompt_inputs = self._set_context_variable(context, prompt_template, prompt_inputs)
|
||||
@@ -211,7 +213,7 @@ class AdvancedPromptTransform(PromptTransform):
|
||||
if '#histories#' in prompt_template.variable_keys:
|
||||
if memory:
|
||||
inputs = {'#histories#': '', **prompt_inputs}
|
||||
prompt_template = PromptTemplateParser(raw_prompt)
|
||||
prompt_template = PromptTemplateParser(template=raw_prompt, with_variable_tmpl=self.with_variable_tmpl)
|
||||
prompt_inputs = {k: inputs[k] for k in prompt_template.variable_keys if k in inputs}
|
||||
tmp_human_message = UserPromptMessage(
|
||||
content=prompt_template.format(prompt_inputs)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import re
|
||||
|
||||
REGEX = re.compile(r"\{\{([a-zA-Z_][a-zA-Z0-9_]{0,29}|#histories#|#query#|#context#)\}\}")
|
||||
WITH_VARIABLE_TMPL_REGEX = re.compile(
|
||||
r"\{\{([a-zA-Z_][a-zA-Z0-9_]{0,29}|#[a-zA-Z0-9_]{1,50}\.[a-zA-Z0-9_\.]{1,100}#|#histories#|#query#|#context#)\}\}"
|
||||
)
|
||||
|
||||
|
||||
class PromptTemplateParser:
|
||||
@@ -15,13 +18,15 @@ class PromptTemplateParser:
|
||||
`{{#histories#}}` `{{#query#}}` `{{#context#}}`. No other `{{##}}` template variables are allowed.
|
||||
"""
|
||||
|
||||
def __init__(self, template: str):
|
||||
def __init__(self, template: str, with_variable_tmpl: bool = False):
|
||||
self.template = template
|
||||
self.with_variable_tmpl = with_variable_tmpl
|
||||
self.regex = WITH_VARIABLE_TMPL_REGEX if with_variable_tmpl else REGEX
|
||||
self.variable_keys = self.extract()
|
||||
|
||||
def extract(self) -> list:
|
||||
# Regular expression to match the template rules
|
||||
return re.findall(REGEX, self.template)
|
||||
return re.findall(self.regex, self.template)
|
||||
|
||||
def format(self, inputs: dict, remove_template_variables: bool = True) -> str:
|
||||
def replacer(match):
|
||||
@@ -29,12 +34,12 @@ class PromptTemplateParser:
|
||||
value = inputs.get(key, match.group(0)) # return original matched string if key not found
|
||||
|
||||
if remove_template_variables:
|
||||
return PromptTemplateParser.remove_template_variables(value)
|
||||
return PromptTemplateParser.remove_template_variables(value, self.with_variable_tmpl)
|
||||
return value
|
||||
|
||||
prompt = re.sub(REGEX, replacer, self.template)
|
||||
prompt = re.sub(self.regex, replacer, self.template)
|
||||
return re.sub(r'<\|.*?\|>', '', prompt)
|
||||
|
||||
@classmethod
|
||||
def remove_template_variables(cls, text: str):
|
||||
return re.sub(REGEX, r'{\1}', text)
|
||||
def remove_template_variables(cls, text: str, with_variable_tmpl: bool = False):
|
||||
return re.sub(WITH_VARIABLE_TMPL_REGEX if with_variable_tmpl else REGEX, r'{\1}', text)
|
||||
|
||||
@@ -13,6 +13,7 @@ from core.workflow.nodes.answer.entities import (
|
||||
VarGenerateRouteChunk,
|
||||
)
|
||||
from core.workflow.nodes.base_node import BaseNode
|
||||
from core.workflow.utils.variable_template_parser import VariableTemplateParser
|
||||
from models.workflow import WorkflowNodeExecutionStatus
|
||||
|
||||
|
||||
@@ -66,32 +67,8 @@ class AnswerNode(BaseNode):
|
||||
part = cast(TextGenerateRouteChunk, part)
|
||||
answer += part.text
|
||||
|
||||
# re-fetch variable values
|
||||
variable_values = {}
|
||||
for variable_selector in node_data.variables:
|
||||
value = variable_pool.get_variable_value(
|
||||
variable_selector=variable_selector.value_selector
|
||||
)
|
||||
|
||||
if isinstance(value, str | int | float):
|
||||
value = str(value)
|
||||
elif isinstance(value, FileVar):
|
||||
value = value.to_dict()
|
||||
elif isinstance(value, list):
|
||||
new_value = []
|
||||
for item in value:
|
||||
if isinstance(item, FileVar):
|
||||
new_value.append(item.to_dict())
|
||||
else:
|
||||
new_value.append(item)
|
||||
|
||||
value = new_value
|
||||
|
||||
variable_values[variable_selector.variable] = value
|
||||
|
||||
return NodeRunResult(
|
||||
status=WorkflowNodeExecutionStatus.SUCCEEDED,
|
||||
inputs=variable_values,
|
||||
outputs={
|
||||
"answer": answer
|
||||
}
|
||||
@@ -116,15 +93,18 @@ class AnswerNode(BaseNode):
|
||||
:param node_data: node data object
|
||||
:return:
|
||||
"""
|
||||
variable_template_parser = VariableTemplateParser(template=node_data.answer)
|
||||
variable_selectors = variable_template_parser.extract_variable_selectors()
|
||||
|
||||
value_selector_mapping = {
|
||||
variable_selector.variable: variable_selector.value_selector
|
||||
for variable_selector in node_data.variables
|
||||
for variable_selector in variable_selectors
|
||||
}
|
||||
|
||||
variable_keys = list(value_selector_mapping.keys())
|
||||
|
||||
# format answer template
|
||||
template_parser = PromptTemplateParser(node_data.answer)
|
||||
template_parser = PromptTemplateParser(template=node_data.answer, with_variable_tmpl=True)
|
||||
template_variable_keys = template_parser.variable_keys
|
||||
|
||||
# Take the intersection of variable_keys and template_variable_keys
|
||||
@@ -164,8 +144,11 @@ class AnswerNode(BaseNode):
|
||||
"""
|
||||
node_data = cast(cls._node_data_cls, node_data)
|
||||
|
||||
variable_template_parser = VariableTemplateParser(template=node_data.answer)
|
||||
variable_selectors = variable_template_parser.extract_variable_selectors()
|
||||
|
||||
variable_mapping = {}
|
||||
for variable_selector in node_data.variables:
|
||||
for variable_selector in variable_selectors:
|
||||
variable_mapping[variable_selector.variable] = variable_selector.value_selector
|
||||
|
||||
return variable_mapping
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from core.workflow.entities.base_node_data_entities import BaseNodeData
|
||||
from core.workflow.entities.variable_entities import VariableSelector
|
||||
|
||||
|
||||
class AnswerNodeData(BaseNodeData):
|
||||
"""
|
||||
Answer Node Data.
|
||||
"""
|
||||
variables: list[VariableSelector] = []
|
||||
answer: str
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ from pydantic import BaseModel
|
||||
|
||||
from core.prompt.entities.advanced_prompt_entities import ChatModelMessage, CompletionModelPromptTemplate, MemoryConfig
|
||||
from core.workflow.entities.base_node_data_entities import BaseNodeData
|
||||
from core.workflow.entities.variable_entities import VariableSelector
|
||||
|
||||
|
||||
class ModelConfig(BaseModel):
|
||||
@@ -44,7 +43,6 @@ class LLMNodeData(BaseNodeData):
|
||||
LLM Node Data.
|
||||
"""
|
||||
model: ModelConfig
|
||||
variables: list[VariableSelector] = []
|
||||
prompt_template: Union[list[ChatModelMessage], CompletionModelPromptTemplate]
|
||||
memory: Optional[MemoryConfig] = None
|
||||
context: ContextConfig
|
||||
|
||||
@@ -15,13 +15,14 @@ from core.model_runtime.entities.model_entities import ModelType
|
||||
from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
|
||||
from core.model_runtime.utils.encoders import jsonable_encoder
|
||||
from core.prompt.advanced_prompt_transform import AdvancedPromptTransform
|
||||
from core.prompt.entities.advanced_prompt_entities import MemoryConfig
|
||||
from core.prompt.entities.advanced_prompt_entities import CompletionModelPromptTemplate, MemoryConfig
|
||||
from core.prompt.utils.prompt_message_util import PromptMessageUtil
|
||||
from core.workflow.entities.base_node_data_entities import BaseNodeData
|
||||
from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult, NodeType, SystemVariable
|
||||
from core.workflow.entities.variable_pool import VariablePool
|
||||
from core.workflow.nodes.base_node import BaseNode
|
||||
from core.workflow.nodes.llm.entities import LLMNodeData, ModelConfig
|
||||
from core.workflow.utils.variable_template_parser import VariableTemplateParser
|
||||
from extensions.ext_database import db
|
||||
from models.model import Conversation
|
||||
from models.provider import Provider, ProviderType
|
||||
@@ -48,9 +49,7 @@ class LLMNode(BaseNode):
|
||||
# fetch variables and fetch values from variable pool
|
||||
inputs = self._fetch_inputs(node_data, variable_pool)
|
||||
|
||||
node_inputs = {
|
||||
**inputs
|
||||
}
|
||||
node_inputs = {}
|
||||
|
||||
# fetch files
|
||||
files: list[FileVar] = self._fetch_files(node_data, variable_pool)
|
||||
@@ -192,10 +191,21 @@ class LLMNode(BaseNode):
|
||||
:return:
|
||||
"""
|
||||
inputs = {}
|
||||
for variable_selector in node_data.variables:
|
||||
prompt_template = node_data.prompt_template
|
||||
|
||||
variable_selectors = []
|
||||
if isinstance(prompt_template, list):
|
||||
for prompt in prompt_template:
|
||||
variable_template_parser = VariableTemplateParser(template=prompt.text)
|
||||
variable_selectors.extend(variable_template_parser.extract_variable_selectors())
|
||||
elif isinstance(prompt_template, CompletionModelPromptTemplate):
|
||||
variable_template_parser = VariableTemplateParser(template=prompt_template.text)
|
||||
variable_selectors = variable_template_parser.extract_variable_selectors()
|
||||
|
||||
for variable_selector in variable_selectors:
|
||||
variable_value = variable_pool.get_variable_value(variable_selector.value_selector)
|
||||
if variable_value is None:
|
||||
raise ValueError(f'Variable {variable_selector.value_selector} not found')
|
||||
raise ValueError(f'Variable {variable_selector.variable} not found')
|
||||
|
||||
inputs[variable_selector.variable] = variable_value
|
||||
|
||||
@@ -411,7 +421,7 @@ class LLMNode(BaseNode):
|
||||
:param model_config: model config
|
||||
:return:
|
||||
"""
|
||||
prompt_transform = AdvancedPromptTransform()
|
||||
prompt_transform = AdvancedPromptTransform(with_variable_tmpl=True)
|
||||
prompt_messages = prompt_transform.get_prompt(
|
||||
prompt_template=node_data.prompt_template,
|
||||
inputs=inputs,
|
||||
@@ -486,9 +496,6 @@ class LLMNode(BaseNode):
|
||||
node_data = cast(cls._node_data_cls, node_data)
|
||||
|
||||
variable_mapping = {}
|
||||
for variable_selector in node_data.variables:
|
||||
variable_mapping[variable_selector.variable] = variable_selector.value_selector
|
||||
|
||||
if node_data.context.enabled:
|
||||
variable_mapping['#context#'] = node_data.context.variable_selector
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ class QuestionClassifierNode(LLMNode):
|
||||
:param model_config: model config
|
||||
:return:
|
||||
"""
|
||||
prompt_transform = AdvancedPromptTransform()
|
||||
prompt_transform = AdvancedPromptTransform(with_variable_tmpl=True)
|
||||
prompt_template = self._get_prompt_template(node_data, query, memory)
|
||||
prompt_messages = prompt_transform.get_prompt(
|
||||
prompt_template=prompt_template,
|
||||
|
||||
0
api/core/workflow/utils/__init__.py
Normal file
0
api/core/workflow/utils/__init__.py
Normal file
58
api/core/workflow/utils/variable_template_parser.py
Normal file
58
api/core/workflow/utils/variable_template_parser.py
Normal file
@@ -0,0 +1,58 @@
|
||||
import re
|
||||
|
||||
from core.workflow.entities.variable_entities import VariableSelector
|
||||
|
||||
REGEX = re.compile(r"\{\{(#[a-zA-Z0-9_]{1,50}(\.[a-zA-Z_][a-zA-Z0-9_]{0,29}){1,10}#)\}\}")
|
||||
|
||||
|
||||
class VariableTemplateParser:
|
||||
"""
|
||||
Rules:
|
||||
|
||||
1. Template variables must be enclosed in `{{}}`.
|
||||
2. The template variable Key can only be: #node_id.var1.var2#.
|
||||
3. The template variable Key cannot contain new lines or spaces, and must comply with rule 2.
|
||||
"""
|
||||
|
||||
def __init__(self, template: str):
|
||||
self.template = template
|
||||
self.variable_keys = self.extract()
|
||||
|
||||
def extract(self) -> list:
|
||||
# Regular expression to match the template rules
|
||||
matches = re.findall(REGEX, self.template)
|
||||
|
||||
first_group_matches = [match[0] for match in matches]
|
||||
|
||||
return list(set(first_group_matches))
|
||||
|
||||
def extract_variable_selectors(self) -> list[VariableSelector]:
|
||||
variable_selectors = []
|
||||
for variable_key in self.variable_keys:
|
||||
remove_hash = variable_key.replace('#', '')
|
||||
split_result = remove_hash.split('.')
|
||||
if len(split_result) < 2:
|
||||
continue
|
||||
|
||||
variable_selectors.append(VariableSelector(
|
||||
variable=variable_key,
|
||||
value_selector=split_result
|
||||
))
|
||||
|
||||
return variable_selectors
|
||||
|
||||
def format(self, inputs: dict, remove_template_variables: bool = True) -> str:
|
||||
def replacer(match):
|
||||
key = match.group(1)
|
||||
value = inputs.get(key, match.group(0)) # return original matched string if key not found
|
||||
|
||||
if remove_template_variables:
|
||||
return VariableTemplateParser.remove_template_variables(value)
|
||||
return value
|
||||
|
||||
prompt = re.sub(REGEX, replacer, self.template)
|
||||
return re.sub(r'<\|.*?\|>', '', prompt)
|
||||
|
||||
@classmethod
|
||||
def remove_template_variables(cls, text: str):
|
||||
return re.sub(REGEX, r'{\1}', text)
|
||||
@@ -291,7 +291,7 @@ class WorkflowConverter:
|
||||
if app_model.mode == AppMode.CHAT.value:
|
||||
http_request_variables.append({
|
||||
"variable": "_query",
|
||||
"value_selector": ["start", "sys.query"]
|
||||
"value_selector": ["sys", ".query"]
|
||||
})
|
||||
|
||||
request_body = {
|
||||
@@ -375,7 +375,7 @@ class WorkflowConverter:
|
||||
"""
|
||||
retrieve_config = dataset_config.retrieve_config
|
||||
if new_app_mode == AppMode.ADVANCED_CHAT:
|
||||
query_variable_selector = ["start", "sys.query"]
|
||||
query_variable_selector = ["sys", "query"]
|
||||
elif retrieve_config.query_variable:
|
||||
# fetch query variable
|
||||
query_variable_selector = ["start", retrieve_config.query_variable]
|
||||
@@ -449,19 +449,31 @@ class WorkflowConverter:
|
||||
has_context=knowledge_retrieval_node is not None,
|
||||
query_in_prompt=False
|
||||
)
|
||||
|
||||
template = prompt_template_config['prompt_template'].template
|
||||
for v in start_node['data']['variables']:
|
||||
template = template.replace('{{' + v['variable'] + '}}', '{{#start.' + v['variable'] + '#}}')
|
||||
|
||||
prompts = [
|
||||
{
|
||||
"role": 'user',
|
||||
"text": prompt_template_config['prompt_template'].template
|
||||
"text": template
|
||||
}
|
||||
]
|
||||
else:
|
||||
advanced_chat_prompt_template = prompt_template.advanced_chat_prompt_template
|
||||
prompts = [{
|
||||
"role": m.role.value,
|
||||
"text": m.text
|
||||
} for m in advanced_chat_prompt_template.messages] \
|
||||
if advanced_chat_prompt_template else []
|
||||
|
||||
prompts = []
|
||||
for m in advanced_chat_prompt_template.messages:
|
||||
if advanced_chat_prompt_template:
|
||||
text = m.text
|
||||
for v in start_node['data']['variables']:
|
||||
text = text.replace('{{' + v['variable'] + '}}', '{{#start.' + v['variable'] + '#}}')
|
||||
|
||||
prompts.append({
|
||||
"role": m.role.value,
|
||||
"text": text
|
||||
})
|
||||
# Completion Model
|
||||
else:
|
||||
if prompt_template.prompt_type == PromptTemplateEntity.PromptType.SIMPLE:
|
||||
@@ -475,8 +487,13 @@ class WorkflowConverter:
|
||||
has_context=knowledge_retrieval_node is not None,
|
||||
query_in_prompt=False
|
||||
)
|
||||
|
||||
template = prompt_template_config['prompt_template'].template
|
||||
for v in start_node['data']['variables']:
|
||||
template = template.replace('{{' + v['variable'] + '}}', '{{#start.' + v['variable'] + '#}}')
|
||||
|
||||
prompts = {
|
||||
"text": prompt_template_config['prompt_template'].template
|
||||
"text": template
|
||||
}
|
||||
|
||||
prompt_rules = prompt_template_config['prompt_rules']
|
||||
@@ -486,9 +503,16 @@ class WorkflowConverter:
|
||||
}
|
||||
else:
|
||||
advanced_completion_prompt_template = prompt_template.advanced_completion_prompt_template
|
||||
if advanced_completion_prompt_template:
|
||||
text = advanced_completion_prompt_template.prompt
|
||||
for v in start_node['data']['variables']:
|
||||
text = text.replace('{{' + v['variable'] + '}}', '{{#start.' + v['variable'] + '#}}')
|
||||
else:
|
||||
text = ""
|
||||
|
||||
prompts = {
|
||||
"text": advanced_completion_prompt_template.prompt,
|
||||
} if advanced_completion_prompt_template else {"text": ""}
|
||||
"text": text,
|
||||
}
|
||||
|
||||
if advanced_completion_prompt_template.role_prefix:
|
||||
role_prefix = {
|
||||
@@ -519,10 +543,6 @@ class WorkflowConverter:
|
||||
"mode": model_config.mode,
|
||||
"completion_params": completion_params
|
||||
},
|
||||
"variables": [{
|
||||
"variable": v['variable'],
|
||||
"value_selector": ["start", v['variable']]
|
||||
} for v in start_node['data']['variables']],
|
||||
"prompt_template": prompts,
|
||||
"memory": memory,
|
||||
"context": {
|
||||
@@ -532,7 +552,7 @@ class WorkflowConverter:
|
||||
},
|
||||
"vision": {
|
||||
"enabled": file_upload is not None,
|
||||
"variable_selector": ["start", "sys.files"] if file_upload is not None else None,
|
||||
"variable_selector": ["sys", "files"] if file_upload is not None else None,
|
||||
"configs": {
|
||||
"detail": file_upload.image_config['detail']
|
||||
} if file_upload is not None else None
|
||||
@@ -571,11 +591,7 @@ class WorkflowConverter:
|
||||
"data": {
|
||||
"title": "ANSWER",
|
||||
"type": NodeType.ANSWER.value,
|
||||
"variables": [{
|
||||
"variable": "text",
|
||||
"value_selector": ["llm", "text"]
|
||||
}],
|
||||
"answer": "{{text}}"
|
||||
"answer": "{{#llm.text#}}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,32 +40,17 @@ def test_execute_llm(setup_openai_mock):
|
||||
'mode': 'chat',
|
||||
'completion_params': {}
|
||||
},
|
||||
'variables': [
|
||||
{
|
||||
'variable': 'weather',
|
||||
'value_selector': ['abc', 'output'],
|
||||
},
|
||||
{
|
||||
'variable': 'query',
|
||||
'value_selector': ['sys', 'query']
|
||||
}
|
||||
],
|
||||
'prompt_template': [
|
||||
{
|
||||
'role': 'system',
|
||||
'text': 'you are a helpful assistant.\ntoday\'s weather is {{weather}}.'
|
||||
'text': 'you are a helpful assistant.\ntoday\'s weather is {{#abc.output#}}.'
|
||||
},
|
||||
{
|
||||
'role': 'user',
|
||||
'text': '{{query}}'
|
||||
'text': '{{#sys.query#}}'
|
||||
}
|
||||
],
|
||||
'memory': {
|
||||
'window': {
|
||||
'enabled': True,
|
||||
'size': 2
|
||||
}
|
||||
},
|
||||
'memory': None,
|
||||
'context': {
|
||||
'enabled': False
|
||||
},
|
||||
|
||||
@@ -4,7 +4,6 @@ from core.workflow.entities.node_entities import SystemVariable
|
||||
from core.workflow.entities.variable_pool import VariablePool
|
||||
from core.workflow.nodes.answer.answer_node import AnswerNode
|
||||
from core.workflow.nodes.base_node import UserFrom
|
||||
from core.workflow.nodes.if_else.if_else_node import IfElseNode
|
||||
from extensions.ext_database import db
|
||||
from models.workflow import WorkflowNodeExecutionStatus
|
||||
|
||||
@@ -21,17 +20,7 @@ def test_execute_answer():
|
||||
'data': {
|
||||
'title': '123',
|
||||
'type': 'answer',
|
||||
'variables': [
|
||||
{
|
||||
'value_selector': ['llm', 'text'],
|
||||
'variable': 'text'
|
||||
},
|
||||
{
|
||||
'value_selector': ['start', 'weather'],
|
||||
'variable': 'weather'
|
||||
},
|
||||
],
|
||||
'answer': 'Today\'s weather is {{weather}}\n{{text}}\n{{img}}\nFin.'
|
||||
'answer': 'Today\'s weather is {{#start.weather#}}\n{{#llm.text#}}\n{{img}}\nFin.'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@ from services.workflow.workflow_converter import WorkflowConverter
|
||||
def default_variables():
|
||||
return [
|
||||
VariableEntity(
|
||||
variable="text-input",
|
||||
variable="text_input",
|
||||
label="text-input",
|
||||
type=VariableEntity.Type.TEXT_INPUT
|
||||
),
|
||||
@@ -43,7 +43,7 @@ def test__convert_to_start_node(default_variables):
|
||||
# assert
|
||||
assert isinstance(result["data"]["variables"][0]["type"], str)
|
||||
assert result["data"]["variables"][0]["type"] == "text-input"
|
||||
assert result["data"]["variables"][0]["variable"] == "text-input"
|
||||
assert result["data"]["variables"][0]["variable"] == "text_input"
|
||||
assert result["data"]["variables"][1]["variable"] == "paragraph"
|
||||
assert result["data"]["variables"][2]["variable"] == "select"
|
||||
|
||||
@@ -191,7 +191,7 @@ def test__convert_to_http_request_node_for_workflow_app(default_variables):
|
||||
|
||||
|
||||
def test__convert_to_knowledge_retrieval_node_for_chatbot():
|
||||
new_app_mode = AppMode.CHAT
|
||||
new_app_mode = AppMode.ADVANCED_CHAT
|
||||
|
||||
dataset_config = DatasetEntity(
|
||||
dataset_ids=["dataset_id_1", "dataset_id_2"],
|
||||
@@ -221,7 +221,7 @@ def test__convert_to_knowledge_retrieval_node_for_chatbot():
|
||||
)
|
||||
|
||||
assert node["data"]["type"] == "knowledge-retrieval"
|
||||
assert node["data"]["query_variable_selector"] == ["start", "sys.query"]
|
||||
assert node["data"]["query_variable_selector"] == ["sys", "query"]
|
||||
assert node["data"]["dataset_ids"] == dataset_config.dataset_ids
|
||||
assert (node["data"]["retrieval_mode"]
|
||||
== dataset_config.retrieve_config.retrieve_strategy.value)
|
||||
@@ -276,7 +276,7 @@ def test__convert_to_knowledge_retrieval_node_for_workflow_app():
|
||||
|
||||
|
||||
def test__convert_to_llm_node_for_chatbot_simple_chat_model(default_variables):
|
||||
new_app_mode = AppMode.CHAT
|
||||
new_app_mode = AppMode.ADVANCED_CHAT
|
||||
model = "gpt-4"
|
||||
model_mode = LLMMode.CHAT
|
||||
|
||||
@@ -298,7 +298,7 @@ def test__convert_to_llm_node_for_chatbot_simple_chat_model(default_variables):
|
||||
|
||||
prompt_template = PromptTemplateEntity(
|
||||
prompt_type=PromptTemplateEntity.PromptType.SIMPLE,
|
||||
simple_prompt_template="You are a helpful assistant {{text-input}}, {{paragraph}}, {{select}}."
|
||||
simple_prompt_template="You are a helpful assistant {{text_input}}, {{paragraph}}, {{select}}."
|
||||
)
|
||||
|
||||
llm_node = workflow_converter._convert_to_llm_node(
|
||||
@@ -311,16 +311,15 @@ def test__convert_to_llm_node_for_chatbot_simple_chat_model(default_variables):
|
||||
assert llm_node["data"]["type"] == "llm"
|
||||
assert llm_node["data"]["model"]['name'] == model
|
||||
assert llm_node["data"]['model']["mode"] == model_mode.value
|
||||
assert llm_node["data"]["variables"] == [{
|
||||
"variable": v.variable,
|
||||
"value_selector": ["start", v.variable]
|
||||
} for v in default_variables]
|
||||
assert llm_node["data"]["prompts"][0]['text'] == prompt_template.simple_prompt_template + '\n'
|
||||
template = prompt_template.simple_prompt_template
|
||||
for v in default_variables:
|
||||
template = template.replace('{{' + v.variable + '}}', '{{#start.' + v.variable + '#}}')
|
||||
assert llm_node["data"]["prompt_template"][0]['text'] == template + '\n'
|
||||
assert llm_node["data"]['context']['enabled'] is False
|
||||
|
||||
|
||||
def test__convert_to_llm_node_for_chatbot_simple_completion_model(default_variables):
|
||||
new_app_mode = AppMode.CHAT
|
||||
new_app_mode = AppMode.ADVANCED_CHAT
|
||||
model = "gpt-3.5-turbo-instruct"
|
||||
model_mode = LLMMode.COMPLETION
|
||||
|
||||
@@ -342,7 +341,7 @@ def test__convert_to_llm_node_for_chatbot_simple_completion_model(default_variab
|
||||
|
||||
prompt_template = PromptTemplateEntity(
|
||||
prompt_type=PromptTemplateEntity.PromptType.SIMPLE,
|
||||
simple_prompt_template="You are a helpful assistant {{text-input}}, {{paragraph}}, {{select}}."
|
||||
simple_prompt_template="You are a helpful assistant {{text_input}}, {{paragraph}}, {{select}}."
|
||||
)
|
||||
|
||||
llm_node = workflow_converter._convert_to_llm_node(
|
||||
@@ -355,16 +354,15 @@ def test__convert_to_llm_node_for_chatbot_simple_completion_model(default_variab
|
||||
assert llm_node["data"]["type"] == "llm"
|
||||
assert llm_node["data"]["model"]['name'] == model
|
||||
assert llm_node["data"]['model']["mode"] == model_mode.value
|
||||
assert llm_node["data"]["variables"] == [{
|
||||
"variable": v.variable,
|
||||
"value_selector": ["start", v.variable]
|
||||
} for v in default_variables]
|
||||
assert llm_node["data"]["prompts"]['text'] == prompt_template.simple_prompt_template + '\n'
|
||||
template = prompt_template.simple_prompt_template
|
||||
for v in default_variables:
|
||||
template = template.replace('{{' + v.variable + '}}', '{{#start.' + v.variable + '#}}')
|
||||
assert llm_node["data"]["prompt_template"]['text'] == template + '\n'
|
||||
assert llm_node["data"]['context']['enabled'] is False
|
||||
|
||||
|
||||
def test__convert_to_llm_node_for_chatbot_advanced_chat_model(default_variables):
|
||||
new_app_mode = AppMode.CHAT
|
||||
new_app_mode = AppMode.ADVANCED_CHAT
|
||||
model = "gpt-4"
|
||||
model_mode = LLMMode.CHAT
|
||||
|
||||
@@ -404,17 +402,16 @@ def test__convert_to_llm_node_for_chatbot_advanced_chat_model(default_variables)
|
||||
assert llm_node["data"]["type"] == "llm"
|
||||
assert llm_node["data"]["model"]['name'] == model
|
||||
assert llm_node["data"]['model']["mode"] == model_mode.value
|
||||
assert llm_node["data"]["variables"] == [{
|
||||
"variable": v.variable,
|
||||
"value_selector": ["start", v.variable]
|
||||
} for v in default_variables]
|
||||
assert isinstance(llm_node["data"]["prompts"], list)
|
||||
assert len(llm_node["data"]["prompts"]) == len(prompt_template.advanced_chat_prompt_template.messages)
|
||||
assert llm_node["data"]["prompts"][0]['text'] == prompt_template.advanced_chat_prompt_template.messages[0].text
|
||||
assert isinstance(llm_node["data"]["prompt_template"], list)
|
||||
assert len(llm_node["data"]["prompt_template"]) == len(prompt_template.advanced_chat_prompt_template.messages)
|
||||
template = prompt_template.advanced_chat_prompt_template.messages[0].text
|
||||
for v in default_variables:
|
||||
template = template.replace('{{' + v.variable + '}}', '{{#start.' + v.variable + '#}}')
|
||||
assert llm_node["data"]["prompt_template"][0]['text'] == template
|
||||
|
||||
|
||||
def test__convert_to_llm_node_for_workflow_advanced_completion_model(default_variables):
|
||||
new_app_mode = AppMode.CHAT
|
||||
new_app_mode = AppMode.ADVANCED_CHAT
|
||||
model = "gpt-3.5-turbo-instruct"
|
||||
model_mode = LLMMode.COMPLETION
|
||||
|
||||
@@ -456,9 +453,8 @@ def test__convert_to_llm_node_for_workflow_advanced_completion_model(default_var
|
||||
assert llm_node["data"]["type"] == "llm"
|
||||
assert llm_node["data"]["model"]['name'] == model
|
||||
assert llm_node["data"]['model']["mode"] == model_mode.value
|
||||
assert llm_node["data"]["variables"] == [{
|
||||
"variable": v.variable,
|
||||
"value_selector": ["start", v.variable]
|
||||
} for v in default_variables]
|
||||
assert isinstance(llm_node["data"]["prompts"], dict)
|
||||
assert llm_node["data"]["prompts"]['text'] == prompt_template.advanced_completion_prompt_template.prompt
|
||||
assert isinstance(llm_node["data"]["prompt_template"], dict)
|
||||
template = prompt_template.advanced_completion_prompt_template.prompt
|
||||
for v in default_variables:
|
||||
template = template.replace('{{' + v.variable + '}}', '{{#start.' + v.variable + '#}}')
|
||||
assert llm_node["data"]["prompt_template"]['text'] == template
|
||||
|
||||
@@ -215,7 +215,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
e.preventDefault()
|
||||
getRedirection(isCurrentWorkspaceManager, app, push)
|
||||
}}
|
||||
className='group flex col-span-1 bg-white border-2 border-solid border-transparent rounded-lg shadow-sm min-h-[160px] flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg'
|
||||
className='group flex col-span-1 bg-white border-2 border-solid border-transparent rounded-xl shadow-sm min-h-[160px] flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg'
|
||||
>
|
||||
<div className='flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'>
|
||||
<div className='relative shrink-0'>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="checklist-square">
|
||||
<path id="Vector" d="M9.7823 11.9146C9.32278 11.6082 8.70191 11.7324 8.39554 12.1919C8.08918 12.6514 8.21333 13.2723 8.67285 13.5787L9.7823 11.9146ZM10.9151 13.8717L10.3603 14.7037C10.8019 14.9982 11.3966 14.8963 11.7151 14.4717L10.9151 13.8717ZM14.5226 10.7284C14.8539 10.2865 14.7644 9.65973 14.3225 9.32836C13.8807 8.99699 13.2539 9.08653 12.9225 9.52836L14.5226 10.7284ZM19.3333 11C18.781 11 18.3333 11.4477 18.3333 12C18.3333 12.5523 18.781 13 19.3333 13V11ZM22 13C22.5523 13 23 12.5523 23 12C23 11.4477 22.5523 11 22 11V13ZM19.3333 19C18.781 19 18.3333 19.4477 18.3333 20C18.3333 20.5523 18.781 21 19.3333 21V19ZM22 21C22.5523 21 23 20.5523 23 20C23 19.4477 22.5523 19 22 19V21ZM9.86913 19.9163C9.4096 19.6099 8.78873 19.7341 8.48238 20.1937C8.17602 20.6532 8.3002 21.274 8.75973 21.5804L9.86913 19.9163ZM11.0019 21.8734L10.4472 22.7054C10.8888 22.9998 11.4835 22.8979 11.8019 22.4734L11.0019 21.8734ZM14.6094 18.7301C14.9408 18.2883 14.8512 17.6615 14.4094 17.3301C13.9676 16.9987 13.3408 17.0883 13.0094 17.5301L14.6094 18.7301ZM6.18404 27.564L5.73005 28.455H5.73005L6.18404 27.564ZM4.43597 25.816L3.54497 26.27H3.54497L4.43597 25.816ZM27.564 25.816L28.455 26.27L27.564 25.816ZM25.816 27.564L26.27 28.455L25.816 27.564ZM25.816 4.43597L26.27 3.54497V3.54497L25.816 4.43597ZM27.564 6.18404L28.455 5.73005V5.73005L27.564 6.18404ZM6.18404 4.43597L5.73005 3.54497L6.18404 4.43597ZM4.43597 6.18404L3.54497 5.73005L4.43597 6.18404ZM8.67285 13.5787L10.3603 14.7037L11.4698 13.0397L9.7823 11.9146L8.67285 13.5787ZM11.7151 14.4717L14.5226 10.7284L12.9225 9.52836L10.1151 13.2717L11.7151 14.4717ZM19.3333 13H22V11H19.3333V13ZM19.3333 21H22V19H19.3333V21ZM8.75973 21.5804L10.4472 22.7054L11.5566 21.0413L9.86913 19.9163L8.75973 21.5804ZM11.8019 22.4734L14.6094 18.7301L13.0094 17.5301L10.2019 21.2733L11.8019 22.4734ZM10.4 5H21.6V3H10.4V5ZM27 10.4V21.6H29V10.4H27ZM21.6 27H10.4V29H21.6V27ZM5 21.6V10.4H3V21.6H5ZM10.4 27C9.26339 27 8.47108 26.9992 7.85424 26.9488C7.24907 26.8994 6.90138 26.8072 6.63803 26.673L5.73005 28.455C6.32234 28.7568 6.96253 28.8826 7.69138 28.9422C8.40855 29.0008 9.2964 29 10.4 29V27ZM3 21.6C3 22.7036 2.99922 23.5914 3.05782 24.3086C3.11737 25.0375 3.24318 25.6777 3.54497 26.27L5.32698 25.362C5.19279 25.0986 5.10062 24.7509 5.05118 24.1458C5.00078 23.5289 5 22.7366 5 21.6H3ZM6.63803 26.673C6.07354 26.3854 5.6146 25.9265 5.32698 25.362L3.54497 26.27C4.02433 27.2108 4.78924 27.9757 5.73005 28.455L6.63803 26.673ZM27 21.6C27 22.7366 26.9992 23.5289 26.9488 24.1458C26.8994 24.7509 26.8072 25.0986 26.673 25.362L28.455 26.27C28.7568 25.6777 28.8826 25.0375 28.9422 24.3086C29.0008 23.5914 29 22.7036 29 21.6H27ZM21.6 29C22.7036 29 23.5914 29.0008 24.3086 28.9422C25.0375 28.8826 25.6777 28.7568 26.27 28.455L25.362 26.673C25.0986 26.8072 24.7509 26.8994 24.1458 26.9488C23.5289 26.9992 22.7366 27 21.6 27V29ZM26.673 25.362C26.3854 25.9265 25.9265 26.3854 25.362 26.673L26.27 28.455C27.2108 27.9757 27.9757 27.2108 28.455 26.27L26.673 25.362ZM21.6 5C22.7366 5 23.5289 5.00078 24.1458 5.05118C24.7509 5.10062 25.0986 5.19279 25.362 5.32698L26.27 3.54497C25.6777 3.24318 25.0375 3.11737 24.3086 3.05782C23.5914 2.99922 22.7036 3 21.6 3V5ZM29 10.4C29 9.2964 29.0008 8.40855 28.9422 7.69138C28.8826 6.96253 28.7568 6.32234 28.455 5.73005L26.673 6.63803C26.8072 6.90138 26.8994 7.24907 26.9488 7.85424C26.9992 8.47108 27 9.26339 27 10.4H29ZM25.362 5.32698C25.9265 5.6146 26.3854 6.07354 26.673 6.63803L28.455 5.73005C27.9757 4.78924 27.2108 4.02433 26.27 3.54497L25.362 5.32698ZM10.4 3C9.2964 3 8.40855 2.99922 7.69138 3.05782C6.96253 3.11737 6.32234 3.24318 5.73005 3.54497L6.63803 5.32698C6.90138 5.19279 7.24907 5.10062 7.85424 5.05118C8.47108 5.00078 9.26339 5 10.4 5V3ZM5 10.4C5 9.26339 5.00078 8.47108 5.05118 7.85424C5.10062 7.24907 5.19279 6.90138 5.32698 6.63803L3.54497 5.73005C3.24318 6.32234 3.11737 6.96253 3.05782 7.69138C2.99922 8.40855 3 9.2964 3 10.4H5ZM5.73005 3.54497C4.78924 4.02433 4.02433 4.78924 3.54497 5.73005L5.32698 6.63803C5.6146 6.07354 6.07354 5.6146 6.63803 5.32698L5.73005 3.54497Z" fill="#D0D5DD"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "32",
|
||||
"height": "32",
|
||||
"viewBox": "0 0 32 32",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "checklist-square"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Vector",
|
||||
"d": "M9.7823 11.9146C9.32278 11.6082 8.70191 11.7324 8.39554 12.1919C8.08918 12.6514 8.21333 13.2723 8.67285 13.5787L9.7823 11.9146ZM10.9151 13.8717L10.3603 14.7037C10.8019 14.9982 11.3966 14.8963 11.7151 14.4717L10.9151 13.8717ZM14.5226 10.7284C14.8539 10.2865 14.7644 9.65973 14.3225 9.32836C13.8807 8.99699 13.2539 9.08653 12.9225 9.52836L14.5226 10.7284ZM19.3333 11C18.781 11 18.3333 11.4477 18.3333 12C18.3333 12.5523 18.781 13 19.3333 13V11ZM22 13C22.5523 13 23 12.5523 23 12C23 11.4477 22.5523 11 22 11V13ZM19.3333 19C18.781 19 18.3333 19.4477 18.3333 20C18.3333 20.5523 18.781 21 19.3333 21V19ZM22 21C22.5523 21 23 20.5523 23 20C23 19.4477 22.5523 19 22 19V21ZM9.86913 19.9163C9.4096 19.6099 8.78873 19.7341 8.48238 20.1937C8.17602 20.6532 8.3002 21.274 8.75973 21.5804L9.86913 19.9163ZM11.0019 21.8734L10.4472 22.7054C10.8888 22.9998 11.4835 22.8979 11.8019 22.4734L11.0019 21.8734ZM14.6094 18.7301C14.9408 18.2883 14.8512 17.6615 14.4094 17.3301C13.9676 16.9987 13.3408 17.0883 13.0094 17.5301L14.6094 18.7301ZM6.18404 27.564L5.73005 28.455H5.73005L6.18404 27.564ZM4.43597 25.816L3.54497 26.27H3.54497L4.43597 25.816ZM27.564 25.816L28.455 26.27L27.564 25.816ZM25.816 27.564L26.27 28.455L25.816 27.564ZM25.816 4.43597L26.27 3.54497V3.54497L25.816 4.43597ZM27.564 6.18404L28.455 5.73005V5.73005L27.564 6.18404ZM6.18404 4.43597L5.73005 3.54497L6.18404 4.43597ZM4.43597 6.18404L3.54497 5.73005L4.43597 6.18404ZM8.67285 13.5787L10.3603 14.7037L11.4698 13.0397L9.7823 11.9146L8.67285 13.5787ZM11.7151 14.4717L14.5226 10.7284L12.9225 9.52836L10.1151 13.2717L11.7151 14.4717ZM19.3333 13H22V11H19.3333V13ZM19.3333 21H22V19H19.3333V21ZM8.75973 21.5804L10.4472 22.7054L11.5566 21.0413L9.86913 19.9163L8.75973 21.5804ZM11.8019 22.4734L14.6094 18.7301L13.0094 17.5301L10.2019 21.2733L11.8019 22.4734ZM10.4 5H21.6V3H10.4V5ZM27 10.4V21.6H29V10.4H27ZM21.6 27H10.4V29H21.6V27ZM5 21.6V10.4H3V21.6H5ZM10.4 27C9.26339 27 8.47108 26.9992 7.85424 26.9488C7.24907 26.8994 6.90138 26.8072 6.63803 26.673L5.73005 28.455C6.32234 28.7568 6.96253 28.8826 7.69138 28.9422C8.40855 29.0008 9.2964 29 10.4 29V27ZM3 21.6C3 22.7036 2.99922 23.5914 3.05782 24.3086C3.11737 25.0375 3.24318 25.6777 3.54497 26.27L5.32698 25.362C5.19279 25.0986 5.10062 24.7509 5.05118 24.1458C5.00078 23.5289 5 22.7366 5 21.6H3ZM6.63803 26.673C6.07354 26.3854 5.6146 25.9265 5.32698 25.362L3.54497 26.27C4.02433 27.2108 4.78924 27.9757 5.73005 28.455L6.63803 26.673ZM27 21.6C27 22.7366 26.9992 23.5289 26.9488 24.1458C26.8994 24.7509 26.8072 25.0986 26.673 25.362L28.455 26.27C28.7568 25.6777 28.8826 25.0375 28.9422 24.3086C29.0008 23.5914 29 22.7036 29 21.6H27ZM21.6 29C22.7036 29 23.5914 29.0008 24.3086 28.9422C25.0375 28.8826 25.6777 28.7568 26.27 28.455L25.362 26.673C25.0986 26.8072 24.7509 26.8994 24.1458 26.9488C23.5289 26.9992 22.7366 27 21.6 27V29ZM26.673 25.362C26.3854 25.9265 25.9265 26.3854 25.362 26.673L26.27 28.455C27.2108 27.9757 27.9757 27.2108 28.455 26.27L26.673 25.362ZM21.6 5C22.7366 5 23.5289 5.00078 24.1458 5.05118C24.7509 5.10062 25.0986 5.19279 25.362 5.32698L26.27 3.54497C25.6777 3.24318 25.0375 3.11737 24.3086 3.05782C23.5914 2.99922 22.7036 3 21.6 3V5ZM29 10.4C29 9.2964 29.0008 8.40855 28.9422 7.69138C28.8826 6.96253 28.7568 6.32234 28.455 5.73005L26.673 6.63803C26.8072 6.90138 26.8994 7.24907 26.9488 7.85424C26.9992 8.47108 27 9.26339 27 10.4H29ZM25.362 5.32698C25.9265 5.6146 26.3854 6.07354 26.673 6.63803L28.455 5.73005C27.9757 4.78924 27.2108 4.02433 26.27 3.54497L25.362 5.32698ZM10.4 3C9.2964 3 8.40855 2.99922 7.69138 3.05782C6.96253 3.11737 6.32234 3.24318 5.73005 3.54497L6.63803 5.32698C6.90138 5.19279 7.24907 5.10062 7.85424 5.05118C8.47108 5.00078 9.26339 5 10.4 5V3ZM5 10.4C5 9.26339 5.00078 8.47108 5.05118 7.85424C5.10062 7.24907 5.19279 6.90138 5.32698 6.63803L3.54497 5.73005C3.24318 6.32234 3.11737 6.96253 3.05782 7.69138C2.99922 8.40855 3 9.2964 3 10.4H5ZM5.73005 3.54497C4.78924 4.02433 4.02433 4.78924 3.54497 5.73005L5.32698 6.63803C5.6146 6.07354 6.07354 5.6146 6.63803 5.32698L5.73005 3.54497Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "ChecklistSquare"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './ChecklistSquare.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'ChecklistSquare'
|
||||
|
||||
export default Icon
|
||||
@@ -3,6 +3,7 @@ export { default as Bookmark } from './Bookmark'
|
||||
export { default as CheckCircle } from './CheckCircle'
|
||||
export { default as CheckDone01 } from './CheckDone01'
|
||||
export { default as Check } from './Check'
|
||||
export { default as ChecklistSquare } from './ChecklistSquare'
|
||||
export { default as Checklist } from './Checklist'
|
||||
export { default as DotsGrid } from './DotsGrid'
|
||||
export { default as DotsHorizontal } from './DotsHorizontal'
|
||||
|
||||
@@ -13,7 +13,7 @@ import { CustomTextNode } from '../custom-text/node'
|
||||
import { $createWorkflowVariableBlockNode } from './node'
|
||||
import { WorkflowVariableBlockNode } from './index'
|
||||
|
||||
const REGEX = /\{\{#(\d+|sys)(\.[a-zA-Z_][a-zA-Z0-9_]{0,29})+#\}\}/gi
|
||||
const REGEX = /\{\{(#[a-zA-Z0-9_]{1,50}(\.[a-zA-Z_][a-zA-Z0-9_]{0,29}){1,10}#)\}\}/gi
|
||||
|
||||
const WorkflowVariableBlockReplacementBlock = ({
|
||||
getWorkflowNode = () => undefined,
|
||||
|
||||
@@ -1,16 +1,77 @@
|
||||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
getIncomers,
|
||||
getOutgoers,
|
||||
useEdges,
|
||||
useNodes,
|
||||
} from 'reactflow'
|
||||
import BlockIcon from '../block-icon'
|
||||
import {
|
||||
useNodesExtraData,
|
||||
useNodesInteractions,
|
||||
} from '../hooks'
|
||||
import type { CommonNodeType } from '../types'
|
||||
import { BlockEnum } from '../types'
|
||||
import { useStore } from '../store'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { Checklist } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import {
|
||||
Checklist,
|
||||
ChecklistSquare,
|
||||
XClose,
|
||||
} from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
|
||||
|
||||
const WorkflowChecklist = () => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const nodes = useNodes<CommonNodeType>()
|
||||
const edges = useEdges()
|
||||
const nodesExtraData = useNodesExtraData()
|
||||
const { handleNodeSelect } = useNodesInteractions()
|
||||
const buildInTools = useStore(s => s.buildInTools)
|
||||
const customTools = useStore(s => s.customTools)
|
||||
|
||||
const needWarningNodes = useMemo(() => {
|
||||
const list = []
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i]
|
||||
const incomers = getIncomers(node, nodes, edges)
|
||||
const outgoers = getOutgoers(node, nodes, edges)
|
||||
const { errorMessage } = nodesExtraData[node.data.type].checkValid(node.data, t)
|
||||
let toolIcon
|
||||
|
||||
if (node.data.type === BlockEnum.Tool) {
|
||||
if (node.data.provider_type === 'builtin')
|
||||
toolIcon = buildInTools.find(tool => tool.id === node.data.provider_id)?.icon
|
||||
|
||||
if (node.data.provider_type === 'custom')
|
||||
toolIcon = customTools.find(tool => tool.id === node.data.provider_id)?.icon
|
||||
}
|
||||
|
||||
if (errorMessage || ((!incomers.length && !outgoers.length))) {
|
||||
list.push({
|
||||
id: node.id,
|
||||
type: node.data.type,
|
||||
title: node.data.title,
|
||||
toolIcon,
|
||||
unConnected: !incomers.length && !outgoers.length,
|
||||
errorMessage,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
}, [t, nodes, edges, nodesExtraData, buildInTools, customTools])
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
@@ -23,7 +84,7 @@ const WorkflowChecklist = () => {
|
||||
onOpenChange={setOpen}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
<div className='flex items-center justify-center p-0.5 w-8 h-8 rounded-lg border-[0.5px] border-gray-200 bg-white shadow-xs'>
|
||||
<div className='relative flex items-center justify-center p-0.5 w-8 h-8 rounded-lg border-[0.5px] border-gray-200 bg-white shadow-xs'>
|
||||
<div
|
||||
className={`
|
||||
group flex items-center justify-center w-full h-full rounded-md cursor-pointer
|
||||
@@ -38,9 +99,90 @@ const WorkflowChecklist = () => {
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
!!needWarningNodes.length && (
|
||||
<div className='absolute -right-1.5 -top-1.5 flex items-center justify-center min-w-[18px] h-[18px] rounded-full border border-gray-100 text-white text-[11px] font-semibold bg-[#F79009]'>
|
||||
{needWarningNodes.length}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent></PortalToFollowElemContent>
|
||||
<PortalToFollowElemContent className='z-[12]'>
|
||||
<div
|
||||
className='w-[420px] rounded-2xl bg-white border-[0.5px] border-black/5 shadow-lg overflow-y-auto'
|
||||
style={{
|
||||
maxHeight: 'calc(2 / 3 * 100vh)',
|
||||
}}
|
||||
>
|
||||
<div className='sticky top-0 bg-white flex items-center pl-4 pr-3 pt-3 h-[44px] text-md font-semibold text-gray-900 z-[1]'>
|
||||
<div className='grow'>{t('workflow.panel.checklist')}{needWarningNodes.length ? `(${needWarningNodes.length})` : ''}</div>
|
||||
<div
|
||||
className='shrink-0 flex items-center justify-center w-6 h-6 cursor-pointer'
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
<XClose className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
</div>
|
||||
<div className='py-2'>
|
||||
{
|
||||
!!needWarningNodes.length && (
|
||||
<>
|
||||
<div className='px-4 text-xs text-gray-400'>{t('workflow.panel.checklistTip')}</div>
|
||||
<div className='px-4 py-2'>
|
||||
{
|
||||
needWarningNodes.map(node => (
|
||||
<div
|
||||
key={node.id}
|
||||
className='mb-2 last-of-type:mb-0 border-[0.5px] border-gray-200 bg-white shadow-xs rounded-lg cursor-pointer'
|
||||
onClick={() => handleNodeSelect(node.id)}
|
||||
>
|
||||
<div className='flex items-center p-2 h-9 text-xs font-medium text-gray-700'>
|
||||
<BlockIcon
|
||||
type={node.type}
|
||||
className='mr-1.5'
|
||||
toolIcon={node.toolIcon}
|
||||
/>
|
||||
{node.title}
|
||||
</div>
|
||||
{
|
||||
node.unConnected && (
|
||||
<div className='px-3 py-2 border-t-[0.5px] border-t-black/[0.02] bg-gray-25 rounded-b-lg'>
|
||||
<div className='flex text-xs leading-[18px] text-gray-500'>
|
||||
<AlertTriangle className='mt-[3px] mr-2 w-3 h-3 text-[#F79009]' />
|
||||
{t('workflow.common.needConnecttip')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
node.errorMessage && (
|
||||
<div className='px-3 py-2 border-t-[0.5px] border-t-black/[0.02] bg-gray-25 rounded-b-lg'>
|
||||
<div className='flex text-xs leading-[18px] text-gray-500'>
|
||||
<AlertTriangle className='mt-[3px] mr-2 w-3 h-3 text-[#F79009]' />
|
||||
{node.errorMessage}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
!needWarningNodes.length && (
|
||||
<div className='mx-4 mb-3 py-4 rounded-lg bg-gray-50 text-gray-400 text-center'>
|
||||
<ChecklistSquare className='mx-auto mb-[5px] w-8 h-8 text-gray-300' />
|
||||
{t('workflow.panel.checklistResolved')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -119,7 +119,14 @@ const Header: FC = () => {
|
||||
{t('workflow.common.features')}
|
||||
</Button>
|
||||
<Publish />
|
||||
<Checklist />
|
||||
{
|
||||
!nodesReadOnly && (
|
||||
<>
|
||||
<div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Checklist />
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -150,7 +157,6 @@ const Header: FC = () => {
|
||||
>
|
||||
{t('workflow.common.restore')}
|
||||
</Button>
|
||||
<Checklist />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||
type Props = {
|
||||
title: string | JSX.Element
|
||||
value: string
|
||||
variables: string[]
|
||||
onChange: (value: string) => void
|
||||
readOnly?: boolean
|
||||
showRemove?: boolean
|
||||
@@ -78,8 +77,8 @@ const Editor: FC<Props> = ({
|
||||
|
||||
return (
|
||||
<div className={cn(wrapClassName)}>
|
||||
<div ref={ref} className={cn(isFocus && s.gradientBorder, isExpand && 'h-full', '!rounded-[9px]')}>
|
||||
<div className={cn(isFocus ? 'bg-white' : 'bg-gray-100', isExpand && 'h-full flex flex-col', 'rounded-lg')}>
|
||||
<div ref={ref} className={cn(isFocus ? s.gradientBorder : 'bg-gray-100', isExpand && 'h-full', '!rounded-[9px] p-0.5')}>
|
||||
<div className={cn(isFocus ? 'bg-gray-50' : 'bg-gray-100', isExpand && 'h-full flex flex-col', 'rounded-lg')}>
|
||||
<div className='pt-1 pl-3 pr-2 flex justify-between h-6 items-center'>
|
||||
<div className='leading-4 text-xs font-semibold text-gray-700 uppercase'>{title}</div>
|
||||
<div className='flex items-center'>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import cn from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Method } from '../types'
|
||||
@@ -38,7 +38,6 @@ const ApiInput: FC<Props> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const [isFocus, setIsFocus] = useState(false)
|
||||
const availableVarList = useAvailableVarList(nodeId, {
|
||||
onlyLeafNodeVar: false,
|
||||
@@ -47,10 +46,6 @@ const ApiInput: FC<Props> = ({
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (isFocus)
|
||||
inputRef.current?.focus()
|
||||
}, [isFocus])
|
||||
return (
|
||||
<div className='flex items-start space-x-1'>
|
||||
<Selector
|
||||
|
||||
@@ -8,11 +8,14 @@ import { BodyType } from '../../types'
|
||||
import useKeyValueList from '../../hooks/use-key-value-list'
|
||||
import KeyValue from '../key-value'
|
||||
import TextEditor from '../../../_base/components/editor/text-editor'
|
||||
import CodeEditor from '../../../_base/components/editor/code-editor'
|
||||
import { CodeLanguage } from '../../../code/types'
|
||||
import useAvailableVarList from '../../../_base/hooks/use-available-var-list'
|
||||
import InputWithVar from '@/app/components/workflow/nodes/_base/components/prompt/editor'
|
||||
import type { Var } from '@/app/components/workflow/types'
|
||||
import { VarType } from '@/app/components/workflow/types'
|
||||
|
||||
type Props = {
|
||||
readonly: boolean
|
||||
nodeId: string
|
||||
payload: Body
|
||||
onChange: (payload: Body) => void
|
||||
}
|
||||
@@ -34,10 +37,17 @@ const bodyTextMap = {
|
||||
|
||||
const EditBody: FC<Props> = ({
|
||||
readonly,
|
||||
nodeId,
|
||||
payload,
|
||||
onChange,
|
||||
}) => {
|
||||
const { type } = payload
|
||||
const availableVarList = useAvailableVarList(nodeId, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: (varPayload: Var) => {
|
||||
return [VarType.string, VarType.number].includes(varPayload.type)
|
||||
},
|
||||
})
|
||||
|
||||
const handleTypeChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newType = e.target.value as BodyType
|
||||
@@ -54,8 +64,6 @@ const EditBody: FC<Props> = ({
|
||||
list: body,
|
||||
setList: setBody,
|
||||
addItem: addBody,
|
||||
isKeyValueEdit: isBodyKeyValueEdit,
|
||||
toggleIsKeyValueEdit: toggleIsBodyKeyValueEdit,
|
||||
} = useKeyValueList(payload.data, (value) => {
|
||||
const newBody = produce(payload, (draft: Body) => {
|
||||
draft.data = value
|
||||
@@ -111,11 +119,10 @@ const EditBody: FC<Props> = ({
|
||||
{(type === BodyType.formData || type === BodyType.xWwwFormUrlencoded) && (
|
||||
<KeyValue
|
||||
readonly={readonly}
|
||||
nodeId={nodeId}
|
||||
list={body}
|
||||
onChange={setBody}
|
||||
onAdd={addBody}
|
||||
isKeyValueEdit={isBodyKeyValueEdit}
|
||||
toggleKeyValueEdit={toggleIsBodyKeyValueEdit}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -130,11 +137,19 @@ const EditBody: FC<Props> = ({
|
||||
)}
|
||||
|
||||
{type === BodyType.json && (
|
||||
<CodeEditor
|
||||
// <CodeEditor
|
||||
// readOnly={readonly}
|
||||
// title={<div className='uppercase'>JSON</div>}
|
||||
// value={payload.data} onChange={handleBodyValueChange}
|
||||
// language={CodeLanguage.json}
|
||||
// />
|
||||
<InputWithVar
|
||||
title='JSON'
|
||||
value={payload.data}
|
||||
onChange={handleBodyValueChange}
|
||||
justVar
|
||||
nodesOutputVars={availableVarList}
|
||||
readOnly={readonly}
|
||||
title={<div className='uppercase'>JSON</div>}
|
||||
value={payload.data} onChange={handleBodyValueChange}
|
||||
language={CodeLanguage.json}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,65 +1,59 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import React from 'react'
|
||||
import type { KeyValue } from '../../types'
|
||||
import KeyValueEdit from './key-value-edit'
|
||||
import BulkEdit from './bulk-edit'
|
||||
|
||||
type Props = {
|
||||
readonly: boolean
|
||||
nodeId: string
|
||||
list: KeyValue[]
|
||||
onChange: (newList: KeyValue[]) => void
|
||||
onAdd: () => void
|
||||
isKeyValueEdit: boolean
|
||||
toggleKeyValueEdit: () => void
|
||||
// toggleKeyValueEdit: () => void
|
||||
}
|
||||
|
||||
const KeyValueList: FC<Props> = ({
|
||||
readonly,
|
||||
nodeId,
|
||||
list,
|
||||
onChange,
|
||||
onAdd,
|
||||
isKeyValueEdit,
|
||||
toggleKeyValueEdit,
|
||||
// toggleKeyValueEdit,
|
||||
}) => {
|
||||
const handleBulkValueChange = useCallback((value: string) => {
|
||||
const newList = value.split('\n').map((item) => {
|
||||
const [key, value] = item.split(':')
|
||||
return {
|
||||
key: key ? key.trim() : '',
|
||||
value: value ? value.trim() : '',
|
||||
}
|
||||
})
|
||||
onChange(newList)
|
||||
}, [onChange])
|
||||
// const handleBulkValueChange = useCallback((value: string) => {
|
||||
// const newList = value.split('\n').map((item) => {
|
||||
// const [key, value] = item.split(':')
|
||||
// return {
|
||||
// key: key ? key.trim() : '',
|
||||
// value: value ? value.trim() : '',
|
||||
// }
|
||||
// })
|
||||
// onChange(newList)
|
||||
// }, [onChange])
|
||||
|
||||
const bulkList = (() => {
|
||||
const res = list.map((item) => {
|
||||
if (!item.key && !item.value)
|
||||
return ''
|
||||
if (!item.value)
|
||||
return item.key
|
||||
return `${item.key}:${item.value}`
|
||||
}).join('\n')
|
||||
return res
|
||||
})()
|
||||
return (
|
||||
<>
|
||||
{isKeyValueEdit
|
||||
? <KeyValueEdit
|
||||
readonly={readonly}
|
||||
list={list}
|
||||
onChange={onChange}
|
||||
onAdd={onAdd}
|
||||
onSwitchToBulkEdit={toggleKeyValueEdit}
|
||||
/>
|
||||
: <BulkEdit
|
||||
value={bulkList}
|
||||
onChange={handleBulkValueChange}
|
||||
onSwitchToKeyValueEdit={toggleKeyValueEdit}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
)
|
||||
// const bulkList = (() => {
|
||||
// const res = list.map((item) => {
|
||||
// if (!item.key && !item.value)
|
||||
// return ''
|
||||
// if (!item.value)
|
||||
// return item.key
|
||||
// return `${item.key}:${item.value}`
|
||||
// }).join('\n')
|
||||
// return res
|
||||
// })()
|
||||
return <KeyValueEdit
|
||||
readonly={readonly}
|
||||
nodeId={nodeId}
|
||||
list={list}
|
||||
onChange={onChange}
|
||||
onAdd={onAdd}
|
||||
// onSwitchToBulkEdit={toggleKeyValueEdit}
|
||||
/>
|
||||
// : <BulkEdit
|
||||
// value={bulkList}
|
||||
// onChange={handleBulkValueChange}
|
||||
// onSwitchToKeyValueEdit={toggleKeyValueEdit}
|
||||
// />
|
||||
}
|
||||
export default React.memo(KeyValueList)
|
||||
|
||||
@@ -5,25 +5,27 @@ import produce from 'immer'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { KeyValue } from '../../../types'
|
||||
import KeyValueItem from './item'
|
||||
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||
import { EditList } from '@/app/components/base/icons/src/vender/solid/communication'
|
||||
// import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||
// import { EditList } from '@/app/components/base/icons/src/vender/solid/communication'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.http'
|
||||
|
||||
type Props = {
|
||||
readonly: boolean
|
||||
nodeId: string
|
||||
list: KeyValue[]
|
||||
onChange: (newList: KeyValue[]) => void
|
||||
onAdd: () => void
|
||||
onSwitchToBulkEdit: () => void
|
||||
// onSwitchToBulkEdit: () => void
|
||||
}
|
||||
|
||||
const KeyValueList: FC<Props> = ({
|
||||
readonly,
|
||||
nodeId,
|
||||
list,
|
||||
onChange,
|
||||
onAdd,
|
||||
onSwitchToBulkEdit,
|
||||
// onSwitchToBulkEdit,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -51,7 +53,7 @@ const KeyValueList: FC<Props> = ({
|
||||
<div className='w-1/2 h-full pl-3 border-r border-gray-200'>{t(`${i18nPrefix}.key`)}</div>
|
||||
<div className='flex w-1/2 h-full pl-3 pr-1 items-center justify-between'>
|
||||
<div>{t(`${i18nPrefix}.value`)}</div>
|
||||
{!readonly && (
|
||||
{/* {!readonly && (
|
||||
<TooltipPlus
|
||||
popupContent={t(`${i18nPrefix}.bulkEdit`)}
|
||||
>
|
||||
@@ -61,13 +63,14 @@ const KeyValueList: FC<Props> = ({
|
||||
>
|
||||
<EditList className='w-3 h-3' />
|
||||
</div>
|
||||
</TooltipPlus>)}
|
||||
</TooltipPlus>)} */}
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
list.map((item, index) => (
|
||||
<KeyValueItem
|
||||
key={index}
|
||||
nodeId={nodeId}
|
||||
payload={item}
|
||||
onChange={handleChange(index)}
|
||||
onRemove={handleRemove(index)}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import cn from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useAvailableVarList from '../../../../_base/hooks/use-available-var-list'
|
||||
import RemoveButton from '@/app/components/workflow/nodes/_base/components/remove-button'
|
||||
import SupportVarInput from '@/app/components/workflow/nodes/_base/components/support-var-input'
|
||||
|
||||
import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var'
|
||||
import type { Var } from '@/app/components/workflow/types'
|
||||
import { VarType } from '@/app/components/workflow/types'
|
||||
type Props = {
|
||||
className?: string
|
||||
nodeId: string
|
||||
value: string
|
||||
onChange: (newValue: string) => void
|
||||
hasRemove: boolean
|
||||
@@ -18,6 +21,7 @@ type Props = {
|
||||
|
||||
const InputItem: FC<Props> = ({
|
||||
className,
|
||||
nodeId,
|
||||
value,
|
||||
onChange,
|
||||
hasRemove,
|
||||
@@ -25,15 +29,17 @@ const InputItem: FC<Props> = ({
|
||||
placeholder,
|
||||
readOnly,
|
||||
}) => {
|
||||
const hasValue = !!value
|
||||
const [isEdit, {
|
||||
setTrue: setIsEditTrue,
|
||||
setFalse: setIsEditFalse,
|
||||
}] = useBoolean(false)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onChange(e.target.value)
|
||||
}, [onChange])
|
||||
const hasValue = !!value
|
||||
|
||||
const [isFocus, setIsFocus] = useState(false)
|
||||
const availableVarList = useAvailableVarList(nodeId, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: (varPayload: Var) => {
|
||||
return [VarType.string, VarType.number].includes(varPayload.type)
|
||||
},
|
||||
})
|
||||
|
||||
const handleRemove = useCallback((e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
@@ -41,34 +47,47 @@ const InputItem: FC<Props> = ({
|
||||
}, [onRemove])
|
||||
|
||||
return (
|
||||
<div className={cn(className, !isEdit && 'hover:bg-gray-50 hover:cursor-text', 'relative flex h-full items-center pl-2')}>
|
||||
{(isEdit && !readOnly)
|
||||
<div className={cn(className, 'hover:bg-gray-50 hover:cursor-text', 'relative flex h-full items-center')}>
|
||||
{(!readOnly)
|
||||
? (
|
||||
<input
|
||||
type='text'
|
||||
className='w-full h-[18px] leading-[18px] pl-0.5 text-gray-900 text-xs font-normal placeholder:text-gray-300 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200'
|
||||
// <input
|
||||
// type='text'
|
||||
// className='w-full h-[18px] leading-[18px] pl-0.5 text-gray-900 text-xs font-normal placeholder:text-gray-300 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200'
|
||||
// value={value}
|
||||
// onChange={handleChange}
|
||||
// onBlur={setIsEditFalse}
|
||||
// autoFocus
|
||||
// placeholder={placeholder}
|
||||
// readOnly={readOnly}
|
||||
// />
|
||||
<Input
|
||||
className={cn(isFocus ? 'bg-gray-100' : 'bg-width', 'w-0 grow px-3 py-1')}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
onBlur={setIsEditFalse}
|
||||
autoFocus
|
||||
placeholder={placeholder}
|
||||
onChange={onChange}
|
||||
readOnly={readOnly}
|
||||
nodesOutputVars={availableVarList}
|
||||
onFocusChange={setIsFocus}
|
||||
placeholder={t('workflow.nodes.http.insertVarPlaceholder')!}
|
||||
placeholderClassName='!leading-[21px]'
|
||||
/>
|
||||
)
|
||||
: <div
|
||||
className="pl-0.5 w-full h-[18px] leading-[18px]"
|
||||
onClick={setIsEditTrue}
|
||||
>
|
||||
{!hasValue && <div className='text-gray-300 text-xs font-normal'>{placeholder}</div>}
|
||||
{hasValue && (
|
||||
<SupportVarInput
|
||||
wrapClassName='w-0 grow truncate flex items-center'
|
||||
textClassName='text-gray-900 text-xs font-normal'
|
||||
<Input
|
||||
className={cn(isFocus ? 'shadow-xs bg-gray-50 border-gray-300' : 'bg-gray-100 border-gray-100', 'w-0 grow rounded-lg px-3 py-[6px] border')}
|
||||
value={value}
|
||||
readonly
|
||||
onChange={onChange}
|
||||
readOnly={readOnly}
|
||||
nodesOutputVars={availableVarList}
|
||||
onFocusChange={setIsFocus}
|
||||
placeholder={t('workflow.nodes.http.insertVarPlaceholder')!}
|
||||
placeholderClassName='!leading-[21px]'
|
||||
/>
|
||||
)}
|
||||
{hasRemove && !isEdit && (
|
||||
{hasRemove && !isFocus && (
|
||||
<RemoveButton
|
||||
className='group-hover:block hidden absolute right-1 top-0.5'
|
||||
onClick={handleRemove}
|
||||
|
||||
@@ -11,6 +11,7 @@ const i18nPrefix = 'workflow.nodes.http'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
nodeId: string
|
||||
readonly: boolean
|
||||
canRemove: boolean
|
||||
payload: KeyValue
|
||||
@@ -22,6 +23,7 @@ type Props = {
|
||||
|
||||
const KeyValueItem: FC<Props> = ({
|
||||
className,
|
||||
nodeId,
|
||||
readonly,
|
||||
canRemove,
|
||||
payload,
|
||||
@@ -45,10 +47,10 @@ const KeyValueItem: FC<Props> = ({
|
||||
|
||||
return (
|
||||
// group class name is for hover row show remove button
|
||||
<div className={cn(className, 'group flex items-center h-7 border-t border-gray-200')}>
|
||||
<div className={cn(className, 'group flex items-start h-min-7 border-t border-gray-200')}>
|
||||
<div className='w-1/2 h-full border-r border-gray-200'>
|
||||
<InputItem
|
||||
className='pr-2.5'
|
||||
nodeId={nodeId}
|
||||
value={payload.key}
|
||||
onChange={handleChange('key')}
|
||||
hasRemove={false}
|
||||
@@ -58,7 +60,7 @@ const KeyValueItem: FC<Props> = ({
|
||||
</div>
|
||||
<div className='w-1/2 h-full'>
|
||||
<InputItem
|
||||
className='pr-1'
|
||||
nodeId={nodeId}
|
||||
value={payload.value}
|
||||
onChange={handleChange('value')}
|
||||
hasRemove={!readonly && canRemove}
|
||||
|
||||
@@ -8,9 +8,7 @@ import KeyValue from './components/key-value'
|
||||
import EditBody from './components/edit-body'
|
||||
import AuthorizationModal from './components/authorization'
|
||||
import type { HttpNodeType } from './types'
|
||||
import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list'
|
||||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||
import AddButton from '@/app/components/base/button/add-button'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||
import { Settings01 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
@@ -29,21 +27,14 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
const {
|
||||
readOnly,
|
||||
inputs,
|
||||
handleVarListChange,
|
||||
handleAddVariable,
|
||||
filterVar,
|
||||
handleMethodChange,
|
||||
handleUrlChange,
|
||||
headers,
|
||||
setHeaders,
|
||||
addHeader,
|
||||
isHeaderKeyValueEdit,
|
||||
toggleIsHeaderKeyValueEdit,
|
||||
params,
|
||||
setParams,
|
||||
addParam,
|
||||
isParamKeyValueEdit,
|
||||
toggleIsParamKeyValueEdit,
|
||||
setBody,
|
||||
isShowAuthorization,
|
||||
showAuthorization,
|
||||
@@ -64,20 +55,6 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
return (
|
||||
<div className='mt-2'>
|
||||
<div className='px-4 pb-4 space-y-4'>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.inputVars`)}
|
||||
operations={
|
||||
!readOnly ? <AddButton onClick={handleAddVariable} /> : undefined
|
||||
}
|
||||
>
|
||||
<VarList
|
||||
nodeId={id}
|
||||
readonly={readOnly}
|
||||
list={inputs.variables}
|
||||
onChange={handleVarListChange}
|
||||
filterVar={filterVar}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.api`)}
|
||||
operations={
|
||||
@@ -106,30 +83,29 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
title={t(`${i18nPrefix}.headers`)}
|
||||
>
|
||||
<KeyValue
|
||||
nodeId={id}
|
||||
list={headers}
|
||||
onChange={setHeaders}
|
||||
onAdd={addHeader}
|
||||
readonly={readOnly}
|
||||
isKeyValueEdit={isHeaderKeyValueEdit}
|
||||
toggleKeyValueEdit={toggleIsHeaderKeyValueEdit}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.params`)}
|
||||
>
|
||||
<KeyValue
|
||||
nodeId={id}
|
||||
list={params}
|
||||
onChange={setParams}
|
||||
onAdd={addParam}
|
||||
readonly={readOnly}
|
||||
isKeyValueEdit={isParamKeyValueEdit}
|
||||
toggleKeyValueEdit={toggleIsParamKeyValueEdit}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.body`)}
|
||||
>
|
||||
<EditBody
|
||||
nodeId={id}
|
||||
readonly={readOnly}
|
||||
payload={inputs.body}
|
||||
onChange={setBody}
|
||||
|
||||
@@ -8,7 +8,6 @@ import type { CommonNodeType } from '../types'
|
||||
import { Panel as NodePanel } from '../nodes'
|
||||
import { useStore } from '../store'
|
||||
import { useIsChatMode } from '../hooks'
|
||||
import WorkflowInfo from './workflow-info'
|
||||
import DebugAndPreview from './debug-and-preview'
|
||||
import RunHistory from './run-history'
|
||||
import Record from './record'
|
||||
@@ -28,13 +27,11 @@ const Panel: FC = () => {
|
||||
const historyWorkflowData = useStore(s => s.historyWorkflowData)
|
||||
const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal } = useAppStore()
|
||||
const {
|
||||
showWorkflowInfoPanel,
|
||||
showNodePanel,
|
||||
showDebugAndPreviewPanel,
|
||||
showWorkflowPreview,
|
||||
} = useMemo(() => {
|
||||
return {
|
||||
showWorkflowInfoPanel: !selectedNode && !workflowRunningData && !historyWorkflowData,
|
||||
showNodePanel: !!selectedNode && !workflowRunningData && !historyWorkflowData,
|
||||
showDebugAndPreviewPanel: isChatMode && workflowRunningData && !historyWorkflowData,
|
||||
showWorkflowPreview: !isChatMode && workflowRunningData && !historyWorkflowData,
|
||||
@@ -91,11 +88,6 @@ const Panel: FC = () => {
|
||||
<NodePanel {...selectedNode!} />
|
||||
)
|
||||
}
|
||||
{
|
||||
showWorkflowInfoPanel && (
|
||||
<WorkflowInfo />
|
||||
)
|
||||
}
|
||||
{
|
||||
showRunHistory && (
|
||||
<RunHistory />
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
getIncomers,
|
||||
getOutgoers,
|
||||
useEdges,
|
||||
useNodes,
|
||||
} from 'reactflow'
|
||||
import BlockIcon from '../block-icon'
|
||||
import { useNodesExtraData } from '../hooks'
|
||||
import type { CommonNodeType } from '../types'
|
||||
import { BlockEnum } from '../types'
|
||||
import { useStore } from '../store'
|
||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
|
||||
import { FileCheck02 } from '@/app/components/base/icons/src/vender/line/files'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
|
||||
const WorkflowInfo = () => {
|
||||
const { t } = useTranslation()
|
||||
const appDetail = useAppStore(state => state.appDetail)
|
||||
const nodes = useNodes<CommonNodeType>()
|
||||
const edges = useEdges()
|
||||
const nodesExtraData = useNodesExtraData()
|
||||
const buildInTools = useStore(s => s.buildInTools)
|
||||
const customTools = useStore(s => s.customTools)
|
||||
const needConnectNodes = nodes.filter((node) => {
|
||||
const incomers = getIncomers(node, nodes, edges)
|
||||
const outgoers = getOutgoers(node, nodes, edges)
|
||||
|
||||
return !incomers.length && !outgoers.length
|
||||
})
|
||||
|
||||
const needWarningNodes = useMemo(() => {
|
||||
const list = []
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i]
|
||||
const incomers = getIncomers(node, nodes, edges)
|
||||
const outgoers = getOutgoers(node, nodes, edges)
|
||||
const { errorMessage } = nodesExtraData[node.data.type].checkValid(node.data, t)
|
||||
let toolIcon
|
||||
|
||||
if (node.data.type === BlockEnum.Tool) {
|
||||
if (node.data.provider_type === 'builtin')
|
||||
toolIcon = buildInTools.find(tool => tool.id === node.data.provider_id)?.icon
|
||||
|
||||
if (node.data.provider_type === 'custom')
|
||||
toolIcon = customTools.find(tool => tool.id === node.data.provider_id)?.icon
|
||||
}
|
||||
|
||||
if (errorMessage || ((!incomers.length && !outgoers.length))) {
|
||||
list.push({
|
||||
id: node.id,
|
||||
type: node.data.type,
|
||||
title: node.data.title,
|
||||
toolIcon,
|
||||
unConnected: !incomers.length && !outgoers.length,
|
||||
errorMessage,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
}, [t, nodes, edges, nodesExtraData, buildInTools, customTools])
|
||||
|
||||
if (!appDetail)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className='w-[420px] h-full bg-white shadow-lg border-[0.5px] border-gray-200 rounded-2xl overflow-y-auto'>
|
||||
<div className='sticky top-0 bg-white border-b-[0.5px] border-black/5'>
|
||||
<div className='flex pt-4 px-4 pb-1'>
|
||||
<AppIcon
|
||||
className='mr-3'
|
||||
size='large'
|
||||
icon={appDetail.icon}
|
||||
background={appDetail.icon_background}
|
||||
/>
|
||||
<div className='mt-2 text-base font-semibold text-gray-900'>
|
||||
{appDetail.name}
|
||||
</div>
|
||||
</div>
|
||||
<div className='px-4 py-[13px] text-xs leading-[18px] text-gray-500'>
|
||||
{appDetail.description}
|
||||
</div>
|
||||
<div className='flex items-center px-4 h-[42px] text-[13px] font-semibold text-gray-700'>
|
||||
<FileCheck02 className='mr-1 w-4 h-4' />
|
||||
{t('workflow.panel.checklist')}({needConnectNodes.length})
|
||||
</div>
|
||||
</div>
|
||||
<div className='py-2'>
|
||||
<div className='px-4 py-2 text-xs text-gray-400'>
|
||||
{t('workflow.panel.checklistTip')}
|
||||
</div>
|
||||
<div className='px-4 py-2'>
|
||||
{
|
||||
needWarningNodes.map(node => (
|
||||
<div
|
||||
key={node.id}
|
||||
className='mb-2 border-[0.5px] border-gray-200 bg-white shadow-xs rounded-lg'
|
||||
>
|
||||
<div className='flex items-center p-2 h-9 text-xs font-medium text-gray-700'>
|
||||
<BlockIcon
|
||||
type={node.type}
|
||||
className='mr-1.5'
|
||||
toolIcon={node.toolIcon}
|
||||
/>
|
||||
{node.title}
|
||||
</div>
|
||||
{
|
||||
node.unConnected && (
|
||||
<div className='px-3 py-2 border-t-[0.5px] border-t-black/[0.02] bg-gray-25 rounded-b-lg'>
|
||||
<div className='flex text-xs leading-[18px] text-gray-500'>
|
||||
<AlertTriangle className='mt-[3px] mr-2 w-3 h-3 text-[#F79009]' />
|
||||
{t('workflow.common.needConnecttip')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
node.errorMessage && (
|
||||
<div className='px-3 py-2 border-t-[0.5px] border-t-black/[0.02] bg-gray-25 rounded-b-lg'>
|
||||
<div className='flex text-xs leading-[18px] text-gray-500'>
|
||||
<AlertTriangle className='mt-[3px] mr-2 w-3 h-3 text-[#F79009]' />
|
||||
{node.errorMessage}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(WorkflowInfo)
|
||||
@@ -110,6 +110,7 @@ const translation = {
|
||||
runThisStep: 'Run this step',
|
||||
checklist: 'Checklist',
|
||||
checklistTip: 'Make sure all issues are resolved before publishing',
|
||||
checklistResolved: 'All issues are resolved',
|
||||
organizeBlocks: 'Organize blocks',
|
||||
change: 'Change',
|
||||
},
|
||||
@@ -231,6 +232,7 @@ const translation = {
|
||||
'api-key-title': 'API Key',
|
||||
'header': 'Header',
|
||||
},
|
||||
insertVarPlaceholder: 'type \'/\' to insert variable',
|
||||
},
|
||||
code: {
|
||||
inputVars: 'Input Variables',
|
||||
|
||||
@@ -110,6 +110,7 @@ const translation = {
|
||||
runThisStep: '运行此步骤',
|
||||
checklist: '检查清单',
|
||||
checklistTip: '发布前确保所有问题均已解决',
|
||||
checklistResolved: '所有问题均已解决',
|
||||
organizeBlocks: '整理节点',
|
||||
change: '更改',
|
||||
},
|
||||
@@ -231,6 +232,7 @@ const translation = {
|
||||
'api-key-title': 'API Key',
|
||||
'header': 'Header',
|
||||
},
|
||||
insertVarPlaceholder: '键入 \'/\' 键快速插入变量',
|
||||
},
|
||||
code: {
|
||||
inputVars: '输入变量',
|
||||
|
||||
Reference in New Issue
Block a user