mirror of
https://github.com/langgenius/dify.git
synced 2026-01-31 04:34:36 +00:00
Compare commits
7 Commits
refactor/d
...
fix/input-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
987809396d | ||
|
|
7518606932 | ||
|
|
3997749867 | ||
|
|
f90fa2b186 | ||
|
|
b7e752078c | ||
|
|
5a7dfd15b8 | ||
|
|
89abea26f9 |
@@ -93,9 +93,9 @@ class AppExecutionConfig(BaseSettings):
|
||||
default=0,
|
||||
)
|
||||
|
||||
HITL_GLOBAL_TIMEOUT_SECONDS: PositiveInt = Field(
|
||||
HUMAN_INPUT_GLOBAL_TIMEOUT_SECONDS: PositiveInt = Field(
|
||||
description="Maximum seconds a workflow run can stay paused waiting for human input before global timeout.",
|
||||
default=int(timedelta(days=3).total_seconds()),
|
||||
default=int(timedelta(days=7).total_seconds()),
|
||||
ge=1,
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field
|
||||
@@ -12,10 +11,12 @@ from controllers.console.app.error import (
|
||||
ProviderQuotaExceededError,
|
||||
)
|
||||
from controllers.console.wraps import account_initialization_required, setup_required
|
||||
from core.app.app_config.entities import ModelConfig
|
||||
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
|
||||
from core.helper.code_executor.code_node_provider import CodeNodeProvider
|
||||
from core.helper.code_executor.javascript.javascript_code_provider import JavascriptCodeProvider
|
||||
from core.helper.code_executor.python3.python3_code_provider import Python3CodeProvider
|
||||
from core.llm_generator.entities import RuleCodeGeneratePayload, RuleGeneratePayload, RuleStructuredOutputPayload
|
||||
from core.llm_generator.llm_generator import LLMGenerator
|
||||
from core.model_runtime.errors.invoke import InvokeError
|
||||
from extensions.ext_database import db
|
||||
@@ -26,28 +27,13 @@ from services.workflow_service import WorkflowService
|
||||
DEFAULT_REF_TEMPLATE_SWAGGER_2_0 = "#/definitions/{model}"
|
||||
|
||||
|
||||
class RuleGeneratePayload(BaseModel):
|
||||
instruction: str = Field(..., description="Rule generation instruction")
|
||||
model_config_data: dict[str, Any] = Field(..., alias="model_config", description="Model configuration")
|
||||
no_variable: bool = Field(default=False, description="Whether to exclude variables")
|
||||
|
||||
|
||||
class RuleCodeGeneratePayload(RuleGeneratePayload):
|
||||
code_language: str = Field(default="javascript", description="Programming language for code generation")
|
||||
|
||||
|
||||
class RuleStructuredOutputPayload(BaseModel):
|
||||
instruction: str = Field(..., description="Structured output generation instruction")
|
||||
model_config_data: dict[str, Any] = Field(..., alias="model_config", description="Model configuration")
|
||||
|
||||
|
||||
class InstructionGeneratePayload(BaseModel):
|
||||
flow_id: str = Field(..., description="Workflow/Flow ID")
|
||||
node_id: str = Field(default="", description="Node ID for workflow context")
|
||||
current: str = Field(default="", description="Current instruction text")
|
||||
language: str = Field(default="javascript", description="Programming language (javascript/python)")
|
||||
instruction: str = Field(..., description="Instruction for generation")
|
||||
model_config_data: dict[str, Any] = Field(..., alias="model_config", description="Model configuration")
|
||||
model_config_data: ModelConfig = Field(..., alias="model_config", description="Model configuration")
|
||||
ideal_output: str = Field(default="", description="Expected ideal output")
|
||||
|
||||
|
||||
@@ -64,6 +50,7 @@ reg(RuleCodeGeneratePayload)
|
||||
reg(RuleStructuredOutputPayload)
|
||||
reg(InstructionGeneratePayload)
|
||||
reg(InstructionTemplatePayload)
|
||||
reg(ModelConfig)
|
||||
|
||||
|
||||
@console_ns.route("/rule-generate")
|
||||
@@ -82,12 +69,7 @@ class RuleGenerateApi(Resource):
|
||||
_, current_tenant_id = current_account_with_tenant()
|
||||
|
||||
try:
|
||||
rules = LLMGenerator.generate_rule_config(
|
||||
tenant_id=current_tenant_id,
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
no_variable=args.no_variable,
|
||||
)
|
||||
rules = LLMGenerator.generate_rule_config(tenant_id=current_tenant_id, args=args)
|
||||
except ProviderTokenNotInitError as ex:
|
||||
raise ProviderNotInitializeError(ex.description)
|
||||
except QuotaExceededError:
|
||||
@@ -118,9 +100,7 @@ class RuleCodeGenerateApi(Resource):
|
||||
try:
|
||||
code_result = LLMGenerator.generate_code(
|
||||
tenant_id=current_tenant_id,
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
code_language=args.code_language,
|
||||
args=args,
|
||||
)
|
||||
except ProviderTokenNotInitError as ex:
|
||||
raise ProviderNotInitializeError(ex.description)
|
||||
@@ -152,8 +132,7 @@ class RuleStructuredOutputGenerateApi(Resource):
|
||||
try:
|
||||
structured_output = LLMGenerator.generate_structured_output(
|
||||
tenant_id=current_tenant_id,
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
args=args,
|
||||
)
|
||||
except ProviderTokenNotInitError as ex:
|
||||
raise ProviderNotInitializeError(ex.description)
|
||||
@@ -204,23 +183,29 @@ class InstructionGenerateApi(Resource):
|
||||
case "llm":
|
||||
return LLMGenerator.generate_rule_config(
|
||||
current_tenant_id,
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
no_variable=True,
|
||||
args=RuleGeneratePayload(
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
no_variable=True,
|
||||
),
|
||||
)
|
||||
case "agent":
|
||||
return LLMGenerator.generate_rule_config(
|
||||
current_tenant_id,
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
no_variable=True,
|
||||
args=RuleGeneratePayload(
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
no_variable=True,
|
||||
),
|
||||
)
|
||||
case "code":
|
||||
return LLMGenerator.generate_code(
|
||||
tenant_id=current_tenant_id,
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
code_language=args.language,
|
||||
args=RuleCodeGeneratePayload(
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
code_language=args.language,
|
||||
),
|
||||
)
|
||||
case _:
|
||||
return {"error": f"invalid node type: {node_type}"}
|
||||
|
||||
20
api/core/llm_generator/entities.py
Normal file
20
api/core/llm_generator/entities.py
Normal file
@@ -0,0 +1,20 @@
|
||||
"""Shared payload models for LLM generator helpers and controllers."""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from core.app.app_config.entities import ModelConfig
|
||||
|
||||
|
||||
class RuleGeneratePayload(BaseModel):
|
||||
instruction: str = Field(..., description="Rule generation instruction")
|
||||
model_config_data: ModelConfig = Field(..., alias="model_config", description="Model configuration")
|
||||
no_variable: bool = Field(default=False, description="Whether to exclude variables")
|
||||
|
||||
|
||||
class RuleCodeGeneratePayload(RuleGeneratePayload):
|
||||
code_language: str = Field(default="javascript", description="Programming language for code generation")
|
||||
|
||||
|
||||
class RuleStructuredOutputPayload(BaseModel):
|
||||
instruction: str = Field(..., description="Structured output generation instruction")
|
||||
model_config_data: ModelConfig = Field(..., alias="model_config", description="Model configuration")
|
||||
@@ -6,6 +6,8 @@ from typing import Protocol, cast
|
||||
|
||||
import json_repair
|
||||
|
||||
from core.app.app_config.entities import ModelConfig
|
||||
from core.llm_generator.entities import RuleCodeGeneratePayload, RuleGeneratePayload, RuleStructuredOutputPayload
|
||||
from core.llm_generator.output_parser.rule_config_generator import RuleConfigGeneratorOutputParser
|
||||
from core.llm_generator.output_parser.suggested_questions_after_answer import SuggestedQuestionsAfterAnswerOutputParser
|
||||
from core.llm_generator.prompts import (
|
||||
@@ -151,19 +153,19 @@ class LLMGenerator:
|
||||
return questions
|
||||
|
||||
@classmethod
|
||||
def generate_rule_config(cls, tenant_id: str, instruction: str, model_config: dict, no_variable: bool):
|
||||
def generate_rule_config(cls, tenant_id: str, args: RuleGeneratePayload):
|
||||
output_parser = RuleConfigGeneratorOutputParser()
|
||||
|
||||
error = ""
|
||||
error_step = ""
|
||||
rule_config = {"prompt": "", "variables": [], "opening_statement": "", "error": ""}
|
||||
model_parameters = model_config.get("completion_params", {})
|
||||
if no_variable:
|
||||
model_parameters = args.model_config_data.completion_params
|
||||
if args.no_variable:
|
||||
prompt_template = PromptTemplateParser(WORKFLOW_RULE_CONFIG_PROMPT_GENERATE_TEMPLATE)
|
||||
|
||||
prompt_generate = prompt_template.format(
|
||||
inputs={
|
||||
"TASK_DESCRIPTION": instruction,
|
||||
"TASK_DESCRIPTION": args.instruction,
|
||||
},
|
||||
remove_template_variables=False,
|
||||
)
|
||||
@@ -175,8 +177,8 @@ class LLMGenerator:
|
||||
model_instance = model_manager.get_model_instance(
|
||||
tenant_id=tenant_id,
|
||||
model_type=ModelType.LLM,
|
||||
provider=model_config.get("provider", ""),
|
||||
model=model_config.get("name", ""),
|
||||
provider=args.model_config_data.provider,
|
||||
model=args.model_config_data.name,
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -190,7 +192,7 @@ class LLMGenerator:
|
||||
error = str(e)
|
||||
error_step = "generate rule config"
|
||||
except Exception as e:
|
||||
logger.exception("Failed to generate rule config, model: %s", model_config.get("name"))
|
||||
logger.exception("Failed to generate rule config, model: %s", args.model_config_data.name)
|
||||
rule_config["error"] = str(e)
|
||||
|
||||
rule_config["error"] = f"Failed to {error_step}. Error: {error}" if error else ""
|
||||
@@ -209,7 +211,7 @@ class LLMGenerator:
|
||||
# format the prompt_generate_prompt
|
||||
prompt_generate_prompt = prompt_template.format(
|
||||
inputs={
|
||||
"TASK_DESCRIPTION": instruction,
|
||||
"TASK_DESCRIPTION": args.instruction,
|
||||
},
|
||||
remove_template_variables=False,
|
||||
)
|
||||
@@ -220,8 +222,8 @@ class LLMGenerator:
|
||||
model_instance = model_manager.get_model_instance(
|
||||
tenant_id=tenant_id,
|
||||
model_type=ModelType.LLM,
|
||||
provider=model_config.get("provider", ""),
|
||||
model=model_config.get("name", ""),
|
||||
provider=args.model_config_data.provider,
|
||||
model=args.model_config_data.name,
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -250,7 +252,7 @@ class LLMGenerator:
|
||||
# the second step to generate the task_parameter and task_statement
|
||||
statement_generate_prompt = statement_template.format(
|
||||
inputs={
|
||||
"TASK_DESCRIPTION": instruction,
|
||||
"TASK_DESCRIPTION": args.instruction,
|
||||
"INPUT_TEXT": prompt_content.message.get_text_content(),
|
||||
},
|
||||
remove_template_variables=False,
|
||||
@@ -276,7 +278,7 @@ class LLMGenerator:
|
||||
error_step = "generate conversation opener"
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("Failed to generate rule config, model: %s", model_config.get("name"))
|
||||
logger.exception("Failed to generate rule config, model: %s", args.model_config_data.name)
|
||||
rule_config["error"] = str(e)
|
||||
|
||||
rule_config["error"] = f"Failed to {error_step}. Error: {error}" if error else ""
|
||||
@@ -284,16 +286,20 @@ class LLMGenerator:
|
||||
return rule_config
|
||||
|
||||
@classmethod
|
||||
def generate_code(cls, tenant_id: str, instruction: str, model_config: dict, code_language: str = "javascript"):
|
||||
if code_language == "python":
|
||||
def generate_code(
|
||||
cls,
|
||||
tenant_id: str,
|
||||
args: RuleCodeGeneratePayload,
|
||||
):
|
||||
if args.code_language == "python":
|
||||
prompt_template = PromptTemplateParser(PYTHON_CODE_GENERATOR_PROMPT_TEMPLATE)
|
||||
else:
|
||||
prompt_template = PromptTemplateParser(JAVASCRIPT_CODE_GENERATOR_PROMPT_TEMPLATE)
|
||||
|
||||
prompt = prompt_template.format(
|
||||
inputs={
|
||||
"INSTRUCTION": instruction,
|
||||
"CODE_LANGUAGE": code_language,
|
||||
"INSTRUCTION": args.instruction,
|
||||
"CODE_LANGUAGE": args.code_language,
|
||||
},
|
||||
remove_template_variables=False,
|
||||
)
|
||||
@@ -302,28 +308,28 @@ class LLMGenerator:
|
||||
model_instance = model_manager.get_model_instance(
|
||||
tenant_id=tenant_id,
|
||||
model_type=ModelType.LLM,
|
||||
provider=model_config.get("provider", ""),
|
||||
model=model_config.get("name", ""),
|
||||
provider=args.model_config_data.provider,
|
||||
model=args.model_config_data.name,
|
||||
)
|
||||
|
||||
prompt_messages = [UserPromptMessage(content=prompt)]
|
||||
model_parameters = model_config.get("completion_params", {})
|
||||
model_parameters = args.model_config_data.completion_params
|
||||
try:
|
||||
response: LLMResult = model_instance.invoke_llm(
|
||||
prompt_messages=list(prompt_messages), model_parameters=model_parameters, stream=False
|
||||
)
|
||||
|
||||
generated_code = response.message.get_text_content()
|
||||
return {"code": generated_code, "language": code_language, "error": ""}
|
||||
return {"code": generated_code, "language": args.code_language, "error": ""}
|
||||
|
||||
except InvokeError as e:
|
||||
error = str(e)
|
||||
return {"code": "", "language": code_language, "error": f"Failed to generate code. Error: {error}"}
|
||||
return {"code": "", "language": args.code_language, "error": f"Failed to generate code. Error: {error}"}
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
"Failed to invoke LLM model, model: %s, language: %s", model_config.get("name"), code_language
|
||||
"Failed to invoke LLM model, model: %s, language: %s", args.model_config_data.name, args.code_language
|
||||
)
|
||||
return {"code": "", "language": code_language, "error": f"An unexpected error occurred: {str(e)}"}
|
||||
return {"code": "", "language": args.code_language, "error": f"An unexpected error occurred: {str(e)}"}
|
||||
|
||||
@classmethod
|
||||
def generate_qa_document(cls, tenant_id: str, query, document_language: str):
|
||||
@@ -353,20 +359,20 @@ class LLMGenerator:
|
||||
return answer.strip()
|
||||
|
||||
@classmethod
|
||||
def generate_structured_output(cls, tenant_id: str, instruction: str, model_config: dict):
|
||||
def generate_structured_output(cls, tenant_id: str, args: RuleStructuredOutputPayload):
|
||||
model_manager = ModelManager()
|
||||
model_instance = model_manager.get_model_instance(
|
||||
tenant_id=tenant_id,
|
||||
model_type=ModelType.LLM,
|
||||
provider=model_config.get("provider", ""),
|
||||
model=model_config.get("name", ""),
|
||||
provider=args.model_config_data.provider,
|
||||
model=args.model_config_data.name,
|
||||
)
|
||||
|
||||
prompt_messages = [
|
||||
SystemPromptMessage(content=SYSTEM_STRUCTURED_OUTPUT_GENERATE),
|
||||
UserPromptMessage(content=instruction),
|
||||
UserPromptMessage(content=args.instruction),
|
||||
]
|
||||
model_parameters = model_config.get("model_parameters", {})
|
||||
model_parameters = args.model_config_data.completion_params
|
||||
|
||||
try:
|
||||
response: LLMResult = model_instance.invoke_llm(
|
||||
@@ -390,12 +396,17 @@ class LLMGenerator:
|
||||
error = str(e)
|
||||
return {"output": "", "error": f"Failed to generate JSON Schema. Error: {error}"}
|
||||
except Exception as e:
|
||||
logger.exception("Failed to invoke LLM model, model: %s", model_config.get("name"))
|
||||
logger.exception("Failed to invoke LLM model, model: %s", args.model_config_data.name)
|
||||
return {"output": "", "error": f"An unexpected error occurred: {str(e)}"}
|
||||
|
||||
@staticmethod
|
||||
def instruction_modify_legacy(
|
||||
tenant_id: str, flow_id: str, current: str, instruction: str, model_config: dict, ideal_output: str | None
|
||||
tenant_id: str,
|
||||
flow_id: str,
|
||||
current: str,
|
||||
instruction: str,
|
||||
model_config: ModelConfig,
|
||||
ideal_output: str | None,
|
||||
):
|
||||
last_run: Message | None = (
|
||||
db.session.query(Message).where(Message.app_id == flow_id).order_by(Message.created_at.desc()).first()
|
||||
@@ -434,7 +445,7 @@ class LLMGenerator:
|
||||
node_id: str,
|
||||
current: str,
|
||||
instruction: str,
|
||||
model_config: dict,
|
||||
model_config: ModelConfig,
|
||||
ideal_output: str | None,
|
||||
workflow_service: WorkflowServiceInterface,
|
||||
):
|
||||
@@ -505,7 +516,7 @@ class LLMGenerator:
|
||||
@staticmethod
|
||||
def __instruction_modify_common(
|
||||
tenant_id: str,
|
||||
model_config: dict,
|
||||
model_config: ModelConfig,
|
||||
last_run: dict | None,
|
||||
current: str | None,
|
||||
error_message: str | None,
|
||||
@@ -526,8 +537,8 @@ class LLMGenerator:
|
||||
model_instance = ModelManager().get_model_instance(
|
||||
tenant_id=tenant_id,
|
||||
model_type=ModelType.LLM,
|
||||
provider=model_config.get("provider", ""),
|
||||
model=model_config.get("name", ""),
|
||||
provider=model_config.provider,
|
||||
model=model_config.name,
|
||||
)
|
||||
match node_type:
|
||||
case "llm" | "agent":
|
||||
@@ -570,7 +581,5 @@ class LLMGenerator:
|
||||
error = str(e)
|
||||
return {"error": f"Failed to generate code. Error: {error}"}
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
"Failed to invoke LLM model, model: %s", json.dumps(model_config.get("name")), exc_info=True
|
||||
)
|
||||
logger.exception("Failed to invoke LLM model, model: %s", json.dumps(model_config.name), exc_info=True)
|
||||
return {"error": f"An unexpected error occurred: {str(e)}"}
|
||||
|
||||
@@ -92,6 +92,10 @@ def _build_llm_result_from_first_chunk(
|
||||
Build a single `LLMResult` from the first returned chunk.
|
||||
|
||||
This is used for `stream=False` because the plugin side may still implement the response via a chunked stream.
|
||||
|
||||
Note:
|
||||
This function always drains the `chunks` iterator after reading the first chunk to ensure any underlying
|
||||
streaming resources are released (e.g., HTTP connections owned by the plugin runtime).
|
||||
"""
|
||||
content = ""
|
||||
content_list: list[PromptMessageContentUnionTypes] = []
|
||||
@@ -99,18 +103,25 @@ def _build_llm_result_from_first_chunk(
|
||||
system_fingerprint: str | None = None
|
||||
tools_calls: list[AssistantPromptMessage.ToolCall] = []
|
||||
|
||||
first_chunk = next(chunks, None)
|
||||
if first_chunk is not None:
|
||||
if isinstance(first_chunk.delta.message.content, str):
|
||||
content += first_chunk.delta.message.content
|
||||
elif isinstance(first_chunk.delta.message.content, list):
|
||||
content_list.extend(first_chunk.delta.message.content)
|
||||
try:
|
||||
first_chunk = next(chunks, None)
|
||||
if first_chunk is not None:
|
||||
if isinstance(first_chunk.delta.message.content, str):
|
||||
content += first_chunk.delta.message.content
|
||||
elif isinstance(first_chunk.delta.message.content, list):
|
||||
content_list.extend(first_chunk.delta.message.content)
|
||||
|
||||
if first_chunk.delta.message.tool_calls:
|
||||
_increase_tool_call(first_chunk.delta.message.tool_calls, tools_calls)
|
||||
if first_chunk.delta.message.tool_calls:
|
||||
_increase_tool_call(first_chunk.delta.message.tool_calls, tools_calls)
|
||||
|
||||
usage = first_chunk.delta.usage or LLMUsage.empty_usage()
|
||||
system_fingerprint = first_chunk.system_fingerprint
|
||||
usage = first_chunk.delta.usage or LLMUsage.empty_usage()
|
||||
system_fingerprint = first_chunk.system_fingerprint
|
||||
finally:
|
||||
try:
|
||||
for _ in chunks:
|
||||
pass
|
||||
except Exception:
|
||||
logger.debug("Failed to drain non-stream plugin chunk iterator.", exc_info=True)
|
||||
|
||||
return LLMResult(
|
||||
model=model,
|
||||
|
||||
@@ -12,6 +12,7 @@ from core.app.apps.chat.app_generator import ChatAppGenerator
|
||||
from core.app.apps.completion.app_generator import CompletionAppGenerator
|
||||
from core.app.apps.workflow.app_generator import WorkflowAppGenerator
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
from core.app.layers.pause_state_persist_layer import PauseStateLayerConfig
|
||||
from core.plugin.backwards_invocation.base import BaseBackwardsInvocation
|
||||
from extensions.ext_database import db
|
||||
from models import Account
|
||||
@@ -102,6 +103,11 @@ class PluginAppBackwardsInvocation(BaseBackwardsInvocation):
|
||||
if not workflow:
|
||||
raise ValueError("unexpected app type")
|
||||
|
||||
pause_config = PauseStateLayerConfig(
|
||||
session_factory=db.engine,
|
||||
state_owner_user_id=workflow.created_by,
|
||||
)
|
||||
|
||||
return AdvancedChatAppGenerator().generate(
|
||||
app_model=app,
|
||||
workflow=workflow,
|
||||
@@ -115,6 +121,7 @@ class PluginAppBackwardsInvocation(BaseBackwardsInvocation):
|
||||
invoke_from=InvokeFrom.SERVICE_API,
|
||||
workflow_run_id=str(uuid.uuid4()),
|
||||
streaming=stream,
|
||||
pause_state_config=pause_config,
|
||||
)
|
||||
elif app.mode == AppMode.AGENT_CHAT:
|
||||
return AgentChatAppGenerator().generate(
|
||||
@@ -161,6 +168,11 @@ class PluginAppBackwardsInvocation(BaseBackwardsInvocation):
|
||||
if not workflow:
|
||||
raise ValueError("unexpected app type")
|
||||
|
||||
pause_config = PauseStateLayerConfig(
|
||||
session_factory=db.engine,
|
||||
state_owner_user_id=workflow.created_by,
|
||||
)
|
||||
|
||||
return WorkflowAppGenerator().generate(
|
||||
app_model=app,
|
||||
workflow=workflow,
|
||||
@@ -169,6 +181,7 @@ class PluginAppBackwardsInvocation(BaseBackwardsInvocation):
|
||||
invoke_from=InvokeFrom.SERVICE_API,
|
||||
streaming=stream,
|
||||
call_depth=1,
|
||||
pause_state_config=pause_config,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -98,6 +98,10 @@ class WorkflowTool(Tool):
|
||||
invoke_from=self.runtime.invoke_from,
|
||||
streaming=False,
|
||||
call_depth=self.workflow_call_depth + 1,
|
||||
# NOTE(QuantumGhost): We explicitly set `pause_state_config` to `None`
|
||||
# because workflow pausing mechanisms (such as HumanInput) are not
|
||||
# supported within WorkflowTool execution context.
|
||||
pause_state_config=None,
|
||||
)
|
||||
assert isinstance(result, dict)
|
||||
data = result.get("data", {})
|
||||
|
||||
@@ -40,7 +40,7 @@ dependencies = [
|
||||
"numpy~=1.26.4",
|
||||
"openpyxl~=3.1.5",
|
||||
"opik~=1.8.72",
|
||||
"litellm==1.77.1", # Pinned to avoid madoka dependency issue
|
||||
"litellm==1.77.1", # Pinned to avoid madoka dependency issue
|
||||
"opentelemetry-api==1.27.0",
|
||||
"opentelemetry-distro==0.48b0",
|
||||
"opentelemetry-exporter-otlp==1.27.0",
|
||||
@@ -230,3 +230,23 @@ vdb = [
|
||||
"mo-vector~=0.1.13",
|
||||
"mysql-connector-python>=9.3.0",
|
||||
]
|
||||
|
||||
[tool.mypy]
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
# targeted ignores for current type-check errors
|
||||
# TODO(QuantumGhost): suppress type errors in HITL related code.
|
||||
# fix the type error later
|
||||
module = [
|
||||
"configs.middleware.cache.redis_pubsub_config",
|
||||
"extensions.ext_redis",
|
||||
"tasks.workflow_execution_tasks",
|
||||
"core.workflow.nodes.base.node",
|
||||
"services.human_input_delivery_test_service",
|
||||
"core.app.apps.advanced_chat.app_generator",
|
||||
"controllers.console.human_input_form",
|
||||
"controllers.console.app.workflow_run",
|
||||
"repositories.sqlalchemy_api_workflow_node_execution_repository",
|
||||
"extensions.logstore.repositories.logstore_api_workflow_run_repository",
|
||||
]
|
||||
ignore_errors = true
|
||||
|
||||
@@ -16,6 +16,8 @@ from core.app.apps.workflow.app_generator import WorkflowAppGenerator
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
from core.app.features.rate_limiting import RateLimit
|
||||
from core.app.features.rate_limiting.rate_limit import rate_limit_context
|
||||
from core.app.layers.pause_state_persist_layer import PauseStateLayerConfig
|
||||
from core.db import session_factory
|
||||
from enums.quota_type import QuotaType, unlimited
|
||||
from extensions.otel import AppGenerateHandler, trace_span
|
||||
from models.model import Account, App, AppMode, EndUser
|
||||
@@ -189,6 +191,10 @@ class AppGenerateService:
|
||||
request_id,
|
||||
)
|
||||
|
||||
pause_config = PauseStateLayerConfig(
|
||||
session_factory=session_factory.get_session_maker(),
|
||||
state_owner_user_id=workflow.created_by,
|
||||
)
|
||||
return rate_limit.generate(
|
||||
WorkflowAppGenerator.convert_to_event_stream(
|
||||
WorkflowAppGenerator().generate(
|
||||
@@ -200,6 +206,7 @@ class AppGenerateService:
|
||||
streaming=False,
|
||||
root_node_id=root_node_id,
|
||||
call_depth=0,
|
||||
pause_state_config=pause_config,
|
||||
),
|
||||
),
|
||||
request_id,
|
||||
|
||||
@@ -239,7 +239,7 @@ class HumanInputService:
|
||||
logger.warning("App mode %s does not support resume for workflow run %s", app.mode, workflow_run_id)
|
||||
|
||||
def _is_globally_expired(self, form: Form, *, now: datetime | None = None) -> bool:
|
||||
global_timeout_seconds = dify_config.HITL_GLOBAL_TIMEOUT_SECONDS
|
||||
global_timeout_seconds = dify_config.HUMAN_INPUT_GLOBAL_TIMEOUT_SECONDS
|
||||
if global_timeout_seconds <= 0:
|
||||
return False
|
||||
if form.workflow_run_id is None:
|
||||
|
||||
@@ -61,7 +61,7 @@ def check_and_handle_human_input_timeouts(limit: int = 100) -> None:
|
||||
form_repo = HumanInputFormSubmissionRepository(session_factory)
|
||||
service = HumanInputService(session_factory, form_repository=form_repo)
|
||||
now = naive_utc_now()
|
||||
global_timeout_seconds = dify_config.HITL_GLOBAL_TIMEOUT_SECONDS
|
||||
global_timeout_seconds = dify_config.HUMAN_INPUT_GLOBAL_TIMEOUT_SECONDS
|
||||
|
||||
with session_factory() as session:
|
||||
global_deadline = now - timedelta(seconds=global_timeout_seconds) if global_timeout_seconds > 0 else None
|
||||
|
||||
@@ -101,3 +101,26 @@ def test__normalize_non_stream_plugin_result__empty_iterator_defaults():
|
||||
assert result.message.tool_calls == []
|
||||
assert result.usage == LLMUsage.empty_usage()
|
||||
assert result.system_fingerprint is None
|
||||
|
||||
|
||||
def test__normalize_non_stream_plugin_result__closes_chunk_iterator():
|
||||
prompt_messages = [UserPromptMessage(content="hi")]
|
||||
|
||||
chunk = _make_chunk(content="hello", usage=LLMUsage.empty_usage())
|
||||
closed: list[bool] = []
|
||||
|
||||
def _chunk_iter():
|
||||
try:
|
||||
yield chunk
|
||||
yield _make_chunk(content="ignored", usage=LLMUsage.empty_usage())
|
||||
finally:
|
||||
closed.append(True)
|
||||
|
||||
result = _normalize_non_stream_plugin_result(
|
||||
model="test-model",
|
||||
prompt_messages=prompt_messages,
|
||||
result=_chunk_iter(),
|
||||
)
|
||||
|
||||
assert result.message.content == "hello"
|
||||
assert closed == [True]
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from core.app.layers.pause_state_persist_layer import PauseStateLayerConfig
|
||||
from core.plugin.backwards_invocation.app import PluginAppBackwardsInvocation
|
||||
from models.model import AppMode
|
||||
|
||||
|
||||
def test_invoke_chat_app_advanced_chat_injects_pause_state_config(mocker):
|
||||
workflow = MagicMock()
|
||||
workflow.created_by = "owner-id"
|
||||
|
||||
app = MagicMock()
|
||||
app.mode = AppMode.ADVANCED_CHAT
|
||||
app.workflow = workflow
|
||||
|
||||
mocker.patch(
|
||||
"core.plugin.backwards_invocation.app.db",
|
||||
SimpleNamespace(engine=MagicMock()),
|
||||
)
|
||||
generator_spy = mocker.patch(
|
||||
"core.plugin.backwards_invocation.app.AdvancedChatAppGenerator.generate",
|
||||
return_value={"result": "ok"},
|
||||
)
|
||||
|
||||
result = PluginAppBackwardsInvocation.invoke_chat_app(
|
||||
app=app,
|
||||
user=MagicMock(),
|
||||
conversation_id="conv-1",
|
||||
query="hello",
|
||||
stream=False,
|
||||
inputs={"k": "v"},
|
||||
files=[],
|
||||
)
|
||||
|
||||
assert result == {"result": "ok"}
|
||||
call_kwargs = generator_spy.call_args.kwargs
|
||||
pause_state_config = call_kwargs.get("pause_state_config")
|
||||
assert isinstance(pause_state_config, PauseStateLayerConfig)
|
||||
assert pause_state_config.state_owner_user_id == "owner-id"
|
||||
|
||||
|
||||
def test_invoke_workflow_app_injects_pause_state_config(mocker):
|
||||
workflow = MagicMock()
|
||||
workflow.created_by = "owner-id"
|
||||
|
||||
app = MagicMock()
|
||||
app.mode = AppMode.WORKFLOW
|
||||
app.workflow = workflow
|
||||
|
||||
mocker.patch(
|
||||
"core.plugin.backwards_invocation.app.db",
|
||||
SimpleNamespace(engine=MagicMock()),
|
||||
)
|
||||
generator_spy = mocker.patch(
|
||||
"core.plugin.backwards_invocation.app.WorkflowAppGenerator.generate",
|
||||
return_value={"result": "ok"},
|
||||
)
|
||||
|
||||
result = PluginAppBackwardsInvocation.invoke_workflow_app(
|
||||
app=app,
|
||||
user=MagicMock(),
|
||||
stream=False,
|
||||
inputs={"k": "v"},
|
||||
files=[],
|
||||
)
|
||||
|
||||
assert result == {"result": "ok"}
|
||||
call_kwargs = generator_spy.call_args.kwargs
|
||||
pause_state_config = call_kwargs.get("pause_state_config")
|
||||
assert isinstance(pause_state_config, PauseStateLayerConfig)
|
||||
assert pause_state_config.state_owner_user_id == "owner-id"
|
||||
@@ -55,6 +55,43 @@ def test_workflow_tool_should_raise_tool_invoke_error_when_result_has_error_fiel
|
||||
assert exc_info.value.args == ("oops",)
|
||||
|
||||
|
||||
def test_workflow_tool_does_not_use_pause_state_config(monkeypatch: pytest.MonkeyPatch):
|
||||
entity = ToolEntity(
|
||||
identity=ToolIdentity(author="test", name="test tool", label=I18nObject(en_US="test tool"), provider="test"),
|
||||
parameters=[],
|
||||
description=None,
|
||||
has_runtime_parameters=False,
|
||||
)
|
||||
runtime = ToolRuntime(tenant_id="test_tool", invoke_from=InvokeFrom.EXPLORE)
|
||||
tool = WorkflowTool(
|
||||
workflow_app_id="",
|
||||
workflow_as_tool_id="",
|
||||
version="1",
|
||||
workflow_entities={},
|
||||
workflow_call_depth=1,
|
||||
entity=entity,
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(tool, "_get_app", lambda *args, **kwargs: None)
|
||||
monkeypatch.setattr(tool, "_get_workflow", lambda *args, **kwargs: None)
|
||||
|
||||
from unittest.mock import MagicMock, Mock
|
||||
|
||||
mock_user = Mock()
|
||||
monkeypatch.setattr(tool, "_resolve_user", lambda *args, **kwargs: mock_user)
|
||||
|
||||
generate_mock = MagicMock(return_value={"data": {}})
|
||||
monkeypatch.setattr("core.app.apps.workflow.app_generator.WorkflowAppGenerator.generate", generate_mock)
|
||||
monkeypatch.setattr("libs.login.current_user", lambda *args, **kwargs: None)
|
||||
|
||||
list(tool.invoke("test_user", {}))
|
||||
|
||||
call_kwargs = generate_mock.call_args.kwargs
|
||||
assert "pause_state_config" in call_kwargs
|
||||
assert call_kwargs["pause_state_config"] is None
|
||||
|
||||
|
||||
def test_workflow_tool_should_generate_variable_messages_for_outputs(monkeypatch: pytest.MonkeyPatch):
|
||||
"""Test that WorkflowTool should generate variable messages when there are outputs"""
|
||||
entity = ToolEntity(
|
||||
|
||||
65
api/tests/unit_tests/services/test_app_generate_service.py
Normal file
65
api/tests/unit_tests/services/test_app_generate_service.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import services.app_generate_service as app_generate_service_module
|
||||
from models.model import AppMode
|
||||
from services.app_generate_service import AppGenerateService
|
||||
|
||||
|
||||
class _DummyRateLimit:
|
||||
def __init__(self, client_id: str, max_active_requests: int) -> None:
|
||||
self.client_id = client_id
|
||||
self.max_active_requests = max_active_requests
|
||||
|
||||
@staticmethod
|
||||
def gen_request_key() -> str:
|
||||
return "dummy-request-id"
|
||||
|
||||
def enter(self, request_id: str | None = None) -> str:
|
||||
return request_id or "dummy-request-id"
|
||||
|
||||
def exit(self, request_id: str) -> None:
|
||||
return None
|
||||
|
||||
def generate(self, generator, request_id: str):
|
||||
return generator
|
||||
|
||||
|
||||
def test_workflow_blocking_injects_pause_state_config(mocker, monkeypatch):
|
||||
monkeypatch.setattr(app_generate_service_module.dify_config, "BILLING_ENABLED", False)
|
||||
mocker.patch("services.app_generate_service.RateLimit", _DummyRateLimit)
|
||||
|
||||
workflow = MagicMock()
|
||||
workflow.id = "workflow-id"
|
||||
workflow.created_by = "owner-id"
|
||||
|
||||
mocker.patch.object(AppGenerateService, "_get_workflow", return_value=workflow)
|
||||
|
||||
generator_spy = mocker.patch(
|
||||
"services.app_generate_service.WorkflowAppGenerator.generate",
|
||||
return_value={"result": "ok"},
|
||||
)
|
||||
|
||||
app_model = MagicMock()
|
||||
app_model.mode = AppMode.WORKFLOW
|
||||
app_model.id = "app-id"
|
||||
app_model.tenant_id = "tenant-id"
|
||||
app_model.max_active_requests = 0
|
||||
app_model.is_agent = False
|
||||
|
||||
user = MagicMock()
|
||||
user.id = "user-id"
|
||||
|
||||
result = AppGenerateService.generate(
|
||||
app_model=app_model,
|
||||
user=user,
|
||||
args={"inputs": {"k": "v"}},
|
||||
invoke_from=MagicMock(),
|
||||
streaming=False,
|
||||
)
|
||||
|
||||
assert result == {"result": "ok"}
|
||||
|
||||
call_kwargs = generator_spy.call_args.kwargs
|
||||
pause_state_config = call_kwargs.get("pause_state_config")
|
||||
assert pause_state_config is not None
|
||||
assert pause_state_config.state_owner_user_id == "owner-id"
|
||||
@@ -100,7 +100,7 @@ def test_ensure_form_active_respects_global_timeout(monkeypatch, sample_form_rec
|
||||
created_at=datetime.utcnow() - timedelta(hours=2),
|
||||
expiration_time=datetime.utcnow() + timedelta(hours=2),
|
||||
)
|
||||
monkeypatch.setattr(human_input_service_module.dify_config, "HITL_GLOBAL_TIMEOUT_SECONDS", 3600)
|
||||
monkeypatch.setattr(human_input_service_module.dify_config, "HUMAN_INPUT_GLOBAL_TIMEOUT_SECONDS", 3600)
|
||||
|
||||
with pytest.raises(FormExpiredError):
|
||||
service.ensure_form_active(Form(expired_record))
|
||||
|
||||
@@ -115,7 +115,7 @@ def test_is_global_timeout_uses_created_at():
|
||||
def test_check_and_handle_human_input_timeouts_marks_and_routes(monkeypatch: pytest.MonkeyPatch):
|
||||
now = datetime(2025, 1, 1, 12, 0, 0)
|
||||
monkeypatch.setattr(task_module, "naive_utc_now", lambda: now)
|
||||
monkeypatch.setattr(task_module.dify_config, "HITL_GLOBAL_TIMEOUT_SECONDS", 3600)
|
||||
monkeypatch.setattr(task_module.dify_config, "HUMAN_INPUT_GLOBAL_TIMEOUT_SECONDS", 3600)
|
||||
monkeypatch.setattr(task_module, "db", SimpleNamespace(engine=object()))
|
||||
|
||||
forms = [
|
||||
@@ -193,7 +193,7 @@ def test_check_and_handle_human_input_timeouts_marks_and_routes(monkeypatch: pyt
|
||||
def test_check_and_handle_human_input_timeouts_omits_global_filter_when_disabled(monkeypatch: pytest.MonkeyPatch):
|
||||
now = datetime(2025, 1, 1, 12, 0, 0)
|
||||
monkeypatch.setattr(task_module, "naive_utc_now", lambda: now)
|
||||
monkeypatch.setattr(task_module.dify_config, "HITL_GLOBAL_TIMEOUT_SECONDS", 0)
|
||||
monkeypatch.setattr(task_module.dify_config, "HUMAN_INPUT_GLOBAL_TIMEOUT_SECONDS", 0)
|
||||
monkeypatch.setattr(task_module, "db", SimpleNamespace(engine=object()))
|
||||
|
||||
capture: dict[str, Any] = {}
|
||||
|
||||
@@ -43,4 +43,3 @@ exclude = [
|
||||
"controllers/web/workflow_events.py",
|
||||
"tasks/app_generate/workflow_execute_task.py",
|
||||
]
|
||||
|
||||
|
||||
@@ -145,10 +145,7 @@ export default function MailAndPasswordAuth({ isEmailSetup }: MailAndPasswordAut
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
id="password"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter')
|
||||
handleEmailPasswordLogin()
|
||||
}}
|
||||
onPressEnter={() => handleEmailPasswordLogin()}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
autoComplete="current-password"
|
||||
placeholder={t('passwordPlaceholder', { ns: 'login' }) || ''}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { VariantProps } from 'class-variance-authority'
|
||||
import type { ChangeEventHandler, CSSProperties, FocusEventHandler } from 'react'
|
||||
import type { ChangeEventHandler, CSSProperties, FocusEventHandler, KeyboardEventHandler } from 'react'
|
||||
import { RiCloseCircleFill, RiErrorWarningLine, RiSearchLine } from '@remixicon/react'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
@@ -33,6 +33,7 @@ export type InputProps = {
|
||||
wrapperClassName?: string
|
||||
styleCss?: CSSProperties
|
||||
unit?: string
|
||||
onPressEnter?: KeyboardEventHandler<HTMLInputElement>
|
||||
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> & VariantProps<typeof inputVariants>
|
||||
|
||||
const removeLeadingZeros = (value: string) => value.replace(/^(-?)0+(?=\d)/, '$1')
|
||||
@@ -52,10 +53,30 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(({
|
||||
placeholder,
|
||||
onChange = noop,
|
||||
onBlur = noop,
|
||||
onKeyDown,
|
||||
onCompositionStart,
|
||||
onCompositionEnd,
|
||||
onPressEnter,
|
||||
unit,
|
||||
...props
|
||||
}, ref) => {
|
||||
const { t } = useTranslation()
|
||||
const isComposingRef = React.useRef(false)
|
||||
const handleCompositionStart: React.CompositionEventHandler<HTMLInputElement> = (e) => {
|
||||
isComposingRef.current = true
|
||||
onCompositionStart?.(e)
|
||||
}
|
||||
const handleCompositionEnd: React.CompositionEventHandler<HTMLInputElement> = (e) => {
|
||||
setTimeout(() => {
|
||||
isComposingRef.current = false
|
||||
}, 50)
|
||||
onCompositionEnd?.(e)
|
||||
}
|
||||
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (e) => {
|
||||
if (onPressEnter && e.key === 'Enter' && !e.nativeEvent.isComposing && !isComposingRef.current)
|
||||
onPressEnter(e)
|
||||
onKeyDown?.(e)
|
||||
}
|
||||
const handleNumberChange: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
if (value === 0) {
|
||||
// remove leading zeros
|
||||
@@ -108,6 +129,9 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(({
|
||||
onBlur={props.type === 'number' ? handleNumberBlur : onBlur}
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
onKeyDown={handleKeyDown}
|
||||
onCompositionStart={handleCompositionStart}
|
||||
onCompositionEnd={handleCompositionEnd}
|
||||
/>
|
||||
{!!(showClearIcon && value && !disabled && !destructive) && (
|
||||
<div
|
||||
|
||||
@@ -71,12 +71,12 @@ const CustomizedPagination: FC<Props> = ({
|
||||
setShowInput(false)
|
||||
}
|
||||
|
||||
const handleInputPressEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
e.preventDefault()
|
||||
handleInputConfirm()
|
||||
}
|
||||
const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault()
|
||||
handleInputConfirm()
|
||||
}
|
||||
else if (e.key === 'Escape') {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault()
|
||||
setInputValue(current + 1)
|
||||
setShowInput(false)
|
||||
@@ -132,6 +132,7 @@ const CustomizedPagination: FC<Props> = ({
|
||||
autoFocus
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
onPressEnter={handleInputPressEnter}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
onBlur={handleInputBlur}
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { FC } from 'react'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
import { Theme } from '@/types/app'
|
||||
import { cn } from '@/utils/classnames'
|
||||
@@ -12,12 +13,13 @@ const i18nPrefix = 'sidebar.noApps'
|
||||
const NoApps: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const { theme } = useTheme()
|
||||
const docLink = useDocLink()
|
||||
return (
|
||||
<div className="rounded-xl bg-background-default-subtle p-4">
|
||||
<div className={cn('h-[35px] w-[86px] bg-contain bg-center bg-no-repeat', theme === Theme.dark ? s.dark : s.light)}></div>
|
||||
<div className="system-sm-semibold mt-2 text-text-secondary">{t(`${i18nPrefix}.title`, { ns: 'explore' })}</div>
|
||||
<div className="system-xs-regular my-1 text-text-tertiary">{t(`${i18nPrefix}.description`, { ns: 'explore' })}</div>
|
||||
<a className="system-xs-regular text-text-accent" target="_blank" rel="noopener noreferrer" href="https://docs.dify.ai/en/guides/application-publishing/launch-your-webapp-quickly/README">{t(`${i18nPrefix}.learnMore`, { ns: 'explore' })}</a>
|
||||
<a className="system-xs-regular text-text-accent" target="_blank" rel="noopener noreferrer" href={docLink('/use-dify/publish/README')}>{t(`${i18nPrefix}.learnMore`, { ns: 'explore' })}</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -319,22 +319,17 @@ const GotoAnything: FC<Props> = ({
|
||||
if (!e.target.value.startsWith('@') && !e.target.value.startsWith('/'))
|
||||
clearSelection()
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
const query = searchQuery.trim()
|
||||
// Check if it's a complete slash command
|
||||
if (query.startsWith('/')) {
|
||||
const commandName = query.substring(1).split(' ')[0]
|
||||
const handler = slashCommandRegistry.findCommand(commandName)
|
||||
|
||||
// If it's a direct mode command, execute immediately
|
||||
const isAvailable = handler?.isAvailable?.() ?? true
|
||||
if (handler?.mode === 'direct' && handler.execute && isAvailable) {
|
||||
e.preventDefault()
|
||||
handler.execute()
|
||||
setShow(false)
|
||||
setSearchQuery('')
|
||||
}
|
||||
onPressEnter={(e) => {
|
||||
const query = searchQuery.trim()
|
||||
if (query.startsWith('/')) {
|
||||
const commandName = query.substring(1).split(' ')[0]
|
||||
const handler = slashCommandRegistry.findCommand(commandName)
|
||||
const isAvailable = handler?.isAvailable?.() ?? true
|
||||
if (handler?.mode === 'direct' && handler.execute && isAvailable) {
|
||||
e.preventDefault()
|
||||
handler.execute()
|
||||
setShow(false)
|
||||
setSearchQuery('')
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -221,7 +221,7 @@ const buildOutputVars = (schema: Record<string, any>, schemaTypeDefinitions?: Sc
|
||||
const metaData = genNodeMetaData({
|
||||
sort: 1,
|
||||
type: BlockEnum.TriggerPlugin,
|
||||
helpLinkUri: 'plugin-trigger',
|
||||
helpLinkUri: 'trigger/plugin-trigger',
|
||||
isStart: true,
|
||||
})
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ const validateVisualConfig = (payload: ScheduleTriggerNodeType, t: any): string
|
||||
const metaData = genNodeMetaData({
|
||||
sort: 2,
|
||||
type: BlockEnum.TriggerSchedule,
|
||||
helpLinkUri: 'schedule-trigger',
|
||||
helpLinkUri: 'trigger/schedule-trigger',
|
||||
isStart: true,
|
||||
})
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { createWebhookRawVariable } from './utils/raw-variable'
|
||||
const metaData = genNodeMetaData({
|
||||
sort: 3,
|
||||
type: BlockEnum.TriggerWebhook,
|
||||
helpLinkUri: 'webhook-trigger',
|
||||
helpLinkUri: 'trigger/webhook-trigger',
|
||||
isStart: true,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { BlockEnum } from '@/app/components/workflow/types'
|
||||
import type { UseDifyNodesPath } from '@/types/doc-paths'
|
||||
import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types'
|
||||
|
||||
export type GenNodeMetaDataParams = {
|
||||
@@ -7,7 +8,7 @@ export type GenNodeMetaDataParams = {
|
||||
type: BlockEnum
|
||||
title?: string
|
||||
author?: string
|
||||
helpLinkUri?: string
|
||||
helpLinkUri?: UseDifyNodesPath
|
||||
isRequired?: boolean
|
||||
isUndeletable?: boolean
|
||||
isStart?: boolean
|
||||
|
||||
@@ -139,10 +139,7 @@ export default function MailAndPasswordAuth({ isInvite, isEmailSetup, allowRegis
|
||||
id="password"
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter')
|
||||
handleEmailPasswordLogin()
|
||||
}}
|
||||
onPressEnter={() => handleEmailPasswordLogin()}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
autoComplete="current-password"
|
||||
placeholder={t('passwordPlaceholder', { ns: 'login' }) || ''}
|
||||
|
||||
@@ -103,12 +103,10 @@ export default function InviteSettingsPage() {
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
placeholder={t('namePlaceholder', { ns: 'login' }) || ''}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
handleActivate()
|
||||
}
|
||||
onPressEnter={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
handleActivate()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Locale } from '@/i18n-config/language'
|
||||
import type { DocPathWithoutLang } from '@/types/doc-paths'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { useCallback } from 'react'
|
||||
import { getDocLanguage, getLanguage, getPricingPageLanguage } from '@/i18n-config/language'
|
||||
import { apiReferencePathTranslations } from '@/types/doc-paths'
|
||||
|
||||
@@ -27,21 +28,24 @@ export const useDocLink = (baseUrl?: string): ((path?: DocPathWithoutLang, pathM
|
||||
let baseDocUrl = baseUrl || defaultDocBaseUrl
|
||||
baseDocUrl = (baseDocUrl.endsWith('/')) ? baseDocUrl.slice(0, -1) : baseDocUrl
|
||||
const locale = useLocale()
|
||||
const docLanguage = getDocLanguage(locale)
|
||||
return (path?: DocPathWithoutLang, pathMap?: DocPathMap): string => {
|
||||
const pathUrl = path || ''
|
||||
let targetPath = (pathMap) ? pathMap[locale] || pathUrl : pathUrl
|
||||
let languagePrefix = `/${docLanguage}`
|
||||
return useCallback(
|
||||
(path?: DocPathWithoutLang, pathMap?: DocPathMap): string => {
|
||||
const docLanguage = getDocLanguage(locale)
|
||||
const pathUrl = path || ''
|
||||
let targetPath = (pathMap) ? pathMap[locale] || pathUrl : pathUrl
|
||||
let languagePrefix = `/${docLanguage}`
|
||||
|
||||
// Translate API reference paths for non-English locales
|
||||
if (targetPath.startsWith('/api-reference/') && docLanguage !== 'en') {
|
||||
const translatedPath = apiReferencePathTranslations[targetPath]?.[docLanguage as 'zh' | 'ja']
|
||||
if (translatedPath) {
|
||||
targetPath = translatedPath
|
||||
languagePrefix = ''
|
||||
// Translate API reference paths for non-English locales
|
||||
if (targetPath.startsWith('/api-reference/') && docLanguage !== 'en') {
|
||||
const translatedPath = apiReferencePathTranslations[targetPath]?.[docLanguage as 'zh' | 'ja']
|
||||
if (translatedPath) {
|
||||
targetPath = translatedPath
|
||||
languagePrefix = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return `${baseDocUrl}${languagePrefix}${targetPath}`
|
||||
}
|
||||
return `${baseDocUrl}${languagePrefix}${targetPath}`
|
||||
},
|
||||
[baseDocUrl, locale],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -182,11 +182,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/annotation/add-annotation-modal/edit-item/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/annotation/batch-add-annotation-modal/csv-downloader.spec.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
@@ -196,9 +191,6 @@
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
},
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
@@ -206,9 +198,6 @@
|
||||
"app/components/app/annotation/edit-annotation-modal/edit-item/index.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
},
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/annotation/edit-annotation-modal/index.spec.tsx": {
|
||||
@@ -265,11 +254,6 @@
|
||||
"count": 6
|
||||
}
|
||||
},
|
||||
"app/components/app/configuration/base/var-highlight/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/configuration/config-prompt/advanced-prompt-input.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
@@ -440,11 +424,6 @@
|
||||
"count": 6
|
||||
}
|
||||
},
|
||||
"app/components/app/configuration/debug/debug-with-multiple-model/context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/configuration/debug/debug-with-multiple-model/index.spec.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 5
|
||||
@@ -527,11 +506,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/create-app-dialog/app-list/sidebar.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/create-app-modal/index.spec.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 7
|
||||
@@ -548,14 +522,6 @@
|
||||
"app/components/app/create-from-dsl-modal/index.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 2
|
||||
},
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/log/filter.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/log/index.tsx": {
|
||||
@@ -624,9 +590,6 @@
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 3
|
||||
},
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 4
|
||||
}
|
||||
@@ -636,11 +599,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/app/workflow-log/filter.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/workflow-log/list.spec.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@@ -692,11 +650,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/action-button/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/agent-log-modal/detail.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@@ -725,11 +678,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/amplitude/AmplitudeProvider.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/amplitude/utils.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
@@ -778,9 +726,6 @@
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
},
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
},
|
||||
"react/no-nested-component-definitions": {
|
||||
"count": 1
|
||||
}
|
||||
@@ -790,21 +735,11 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/button/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/button/sync-button.stories.tsx": {
|
||||
"no-console": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/carousel/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/chat/chat-with-history/chat-wrapper.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 7
|
||||
@@ -892,11 +827,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/chat/chat/context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/chat/chat/hooks.ts": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 2
|
||||
@@ -1000,18 +930,10 @@
|
||||
}
|
||||
},
|
||||
"app/components/base/error-boundary/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 3
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/features/context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/features/new-feature-panel/annotation-reply/index.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
@@ -1077,11 +999,6 @@
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/base/file-uploader/store.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"app/components/base/file-uploader/utils.spec.ts": {
|
||||
"test/no-identical-title": {
|
||||
"count": 1
|
||||
@@ -1178,11 +1095,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/ga/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/icons/utils.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
@@ -1234,16 +1146,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/input/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/logo/dify-logo.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/markdown-blocks/audio-block.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 5
|
||||
@@ -1394,11 +1296,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/node-status/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/notion-connector/index.stories.tsx": {
|
||||
"no-console": {
|
||||
"count": 1
|
||||
@@ -1430,9 +1327,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/base/portal-to-follow-elem/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
@@ -1614,16 +1508,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/textarea/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/toast/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/video-gallery/VideoPlayer.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
@@ -1667,16 +1551,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/billing/pricing/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/billing/pricing/plan-switcher/plan-range-switcher.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/billing/pricing/plans/cloud-plan-item/index.spec.tsx": {
|
||||
"test/prefer-hooks-in-order": {
|
||||
"count": 1
|
||||
@@ -1727,11 +1601,6 @@
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/datasets/common/image-uploader/store.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"app/components/datasets/common/image-uploader/utils.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
@@ -1742,16 +1611,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/common/retrieval-method-info/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/create/file-preview/index.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
@@ -1782,11 +1641,6 @@
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/datasets/create/step-two/preview-item/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/create/stop-embedding-modal/index.spec.tsx": {
|
||||
"test/prefer-hooks-in-order": {
|
||||
"count": 1
|
||||
@@ -1844,9 +1698,6 @@
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 2
|
||||
},
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
@@ -1911,11 +1762,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/datasets/documents/create-from-pipeline/data-source/store/provider.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/documents/create-from-pipeline/data-source/store/slices/online-drive.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 4
|
||||
@@ -1961,11 +1807,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/documents/detail/completed/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/documents/detail/completed/new-child-segment.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@@ -1989,11 +1830,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/documents/detail/segment-add/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/documents/detail/settings/pipeline-settings/index.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 6
|
||||
@@ -2134,11 +1970,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/explore/try-app/tab.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/goto-anything/actions/commands/command-bus.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
@@ -2150,9 +1981,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/goto-anything/actions/commands/slash.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 3
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
@@ -2170,9 +1998,6 @@
|
||||
"app/components/goto-anything/context.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 4
|
||||
},
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/goto-anything/index.spec.tsx": {
|
||||
@@ -2369,11 +2194,6 @@
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"app/components/plugins/install-plugin/install-bundle/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/plugins/install-plugin/install-bundle/item/github-item.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
@@ -2450,11 +2270,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/plugins/plugin-auth/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/plugins/plugin-auth/plugin-auth-in-agent.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@@ -2539,9 +2354,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/plugins/plugin-detail-panel/subscription-list/create/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
@@ -2582,9 +2394,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/plugins/plugin-page/context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
@@ -2949,11 +2758,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/block-selector/constants.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/block-selector/featured-tools.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 2
|
||||
@@ -2975,11 +2779,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/block-selector/index-bar.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/block-selector/market-place-plugin/action.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
@@ -3018,26 +2817,11 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/block-selector/view-type-select.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/candidate-node-main.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/workflow/context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/datasets-detail-store/provider.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/header/run-mode.tsx": {
|
||||
"no-console": {
|
||||
"count": 1
|
||||
@@ -3046,21 +2830,11 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/header/test-run-menu.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/header/view-workflow-history.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/hooks-store/provider.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/hooks-store/store.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 6
|
||||
@@ -3181,18 +2955,10 @@
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 6
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/_base/components/entry-node-container.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/_base/components/error-handle/default-value.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@@ -3218,16 +2984,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/_base/components/layout/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 7
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/_base/components/mcp-tool-availability.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/_base/components/memory-config.tsx": {
|
||||
"unicorn/prefer-number-properties": {
|
||||
"count": 1
|
||||
@@ -3311,9 +3067,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/_base/components/workflow-panel/tab.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
@@ -3367,9 +3120,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/agent/panel.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
@@ -3545,9 +3295,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/human-input/components/variable-in-markdown.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 8
|
||||
}
|
||||
@@ -3677,11 +3424,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/auto-width-input.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
@@ -3979,11 +3721,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/note-node/note-editor/toolbar/color-picker.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/note-node/note-editor/utils.ts": {
|
||||
"regexp/no-useless-quantifier": {
|
||||
"count": 1
|
||||
@@ -4020,9 +3757,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/workflow/panel/chat-variable-panel/components/object-value-item.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 5
|
||||
},
|
||||
@@ -4309,11 +4043,6 @@
|
||||
"count": 8
|
||||
}
|
||||
},
|
||||
"app/components/workflow/workflow-history-store.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/workflow/workflow-preview/components/nodes/constants.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@@ -4375,79 +4104,30 @@
|
||||
}
|
||||
},
|
||||
"context/app-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"context/datasets-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"context/event-emitter.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"context/external-api-panel-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"context/external-knowledge-api-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"context/global-public-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"context/hooks/use-trigger-events-limit-modal.ts": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"context/mitt-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"context/modal-context.test.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"context/modal-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 5
|
||||
}
|
||||
},
|
||||
"context/provider-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 3
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"context/web-app-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"context/workspace-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"hooks/use-async-window-open.spec.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 6
|
||||
@@ -4484,9 +4164,6 @@
|
||||
"hooks/use-pay.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 4
|
||||
},
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"i18n-config/README.md": {
|
||||
|
||||
@@ -282,6 +282,15 @@ function generateTypeDefinitions(
|
||||
}
|
||||
|
||||
lines.push('')
|
||||
|
||||
// Add UseDifyNodesPath helper type after UseDifyPath
|
||||
if (section === 'use-dify') {
|
||||
lines.push('// UseDify node paths (without prefix)')
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
lines.push('type ExtractNodesPath<T> = T extends `/use-dify/nodes/${infer Path}` ? Path : never')
|
||||
lines.push('export type UseDifyNodesPath = ExtractNodesPath<UseDifyPath>')
|
||||
lines.push('')
|
||||
}
|
||||
}
|
||||
|
||||
// Generate API reference type (English paths only)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
//
|
||||
// Generated from: https://raw.githubusercontent.com/langgenius/dify-docs/refs/heads/main/docs.json
|
||||
// Generated at: 2026-01-21T07:24:02.413Z
|
||||
// Generated at: 2026-01-30T09:14:29.304Z
|
||||
|
||||
// Language prefixes
|
||||
export type DocLanguage = 'en' | 'zh' | 'ja'
|
||||
@@ -104,6 +104,10 @@ export type UseDifyPath =
|
||||
| '/use-dify/workspace/subscription-management'
|
||||
| '/use-dify/workspace/team-members-management'
|
||||
|
||||
// UseDify node paths (without prefix)
|
||||
type ExtractNodesPath<T> = T extends `/use-dify/nodes/${infer Path}` ? Path : never
|
||||
export type UseDifyNodesPath = ExtractNodesPath<UseDifyPath>
|
||||
|
||||
// SelfHost paths
|
||||
export type SelfHostPath =
|
||||
| '/self-host/advanced-deployments/local-source-code'
|
||||
|
||||
Reference in New Issue
Block a user