diff --git a/api/controllers/cli_api/dify_cli/cli_api.py b/api/controllers/cli_api/dify_cli/cli_api.py index 90a0b88cb4..58b55a3bc3 100644 --- a/api/controllers/cli_api/dify_cli/cli_api.py +++ b/api/controllers/cli_api/dify_cli/cli_api.py @@ -1,4 +1,4 @@ -from core.workflow.file.helpers import get_signed_file_url_for_plugin +from dify_graph.file.helpers import get_signed_file_url_for_plugin from flask import abort from flask_restx import Resource from pydantic import BaseModel diff --git a/api/controllers/console/workspace/account.py b/api/controllers/console/workspace/account.py index 8050a67608..6d94ea2ba0 100644 --- a/api/controllers/console/workspace/account.py +++ b/api/controllers/console/workspace/account.py @@ -37,7 +37,7 @@ from controllers.console.wraps import ( only_edition_cloud, setup_required, ) -from core.workflow.file import helpers as file_helpers +from dify_graph.file import helpers as file_helpers from extensions.ext_database import db from fields.member_fields import Account as AccountResponse from libs.datetime_utils import naive_utc_now diff --git a/api/controllers/console/workspace/sandbox_providers.py b/api/controllers/console/workspace/sandbox_providers.py index ab63decb4a..2543f6342c 100644 --- a/api/controllers/console/workspace/sandbox_providers.py +++ b/api/controllers/console/workspace/sandbox_providers.py @@ -1,6 +1,6 @@ import logging -from core.model_runtime.utils.encoders import jsonable_encoder +from dify_graph.model_runtime.utils.encoders import jsonable_encoder from flask import request from flask_restx import Resource, fields from pydantic import BaseModel diff --git a/api/core/agent/agent_app_runner.py b/api/core/agent/agent_app_runner.py index b8b1a9f8e1..9c4f6ed3e2 100644 --- a/api/core/agent/agent_app_runner.py +++ b/api/core/agent/agent_app_runner.py @@ -8,7 +8,7 @@ from core.agent.entities import AgentEntity, AgentLog, AgentResult from core.agent.patterns.strategy_factory import StrategyFactory from core.app.apps.base_app_queue_manager import PublishFrom from core.app.entities.queue_entities import QueueAgentThoughtEvent, QueueMessageEndEvent, QueueMessageFileEvent -from core.model_runtime.entities import ( +from dify_graph.model_runtime.entities import ( AssistantPromptMessage, LLMResult, LLMResultChunk, @@ -19,12 +19,12 @@ from core.model_runtime.entities import ( TextPromptMessageContent, UserPromptMessage, ) -from core.model_runtime.entities.message_entities import ImagePromptMessageContent, PromptMessageContentUnionTypes +from dify_graph.model_runtime.entities.message_entities import ImagePromptMessageContent, PromptMessageContentUnionTypes from core.prompt.agent_history_prompt_transform import AgentHistoryPromptTransform from core.tools.__base.tool import Tool from core.tools.entities.tool_entities import ToolInvokeMeta from core.tools.tool_engine import ToolEngine -from core.workflow.file import file_manager +from dify_graph.file import file_manager from models.model import Message logger = logging.getLogger(__name__) diff --git a/api/core/agent/patterns/base.py b/api/core/agent/patterns/base.py index 0551691bae..950a6594da 100644 --- a/api/core/agent/patterns/base.py +++ b/api/core/agent/patterns/base.py @@ -11,7 +11,7 @@ from typing import TYPE_CHECKING, Any from core.agent.entities import AgentLog, AgentResult, ExecutionContext from core.model_manager import ModelInstance -from core.model_runtime.entities import ( +from dify_graph.model_runtime.entities import ( AssistantPromptMessage, LLMResult, LLMResultChunk, @@ -19,10 +19,10 @@ from core.model_runtime.entities import ( PromptMessage, PromptMessageTool, ) -from core.model_runtime.entities.llm_entities import LLMUsage -from core.model_runtime.entities.message_entities import TextPromptMessageContent +from dify_graph.model_runtime.entities.llm_entities import LLMUsage +from dify_graph.model_runtime.entities.message_entities import TextPromptMessageContent from core.tools.entities.tool_entities import ToolInvokeMessage, ToolInvokeMeta -from core.workflow.file import File +from dify_graph.file import File if TYPE_CHECKING: from core.tools.__base.tool import Tool diff --git a/api/core/agent/patterns/function_call.py b/api/core/agent/patterns/function_call.py index 11d3939c18..af3f577d1a 100644 --- a/api/core/agent/patterns/function_call.py +++ b/api/core/agent/patterns/function_call.py @@ -12,7 +12,7 @@ from collections.abc import Generator from typing import Any, Union from core.agent.entities import AgentLog, AgentResult -from core.model_runtime.entities import ( +from dify_graph.model_runtime.entities import ( AssistantPromptMessage, LLMResult, LLMResultChunk, @@ -23,7 +23,7 @@ from core.model_runtime.entities import ( ToolPromptMessage, ) from core.tools.entities.tool_entities import ToolInvokeMeta -from core.workflow.file import File +from dify_graph.file import File from .base import AgentPattern diff --git a/api/core/agent/patterns/react.py b/api/core/agent/patterns/react.py index 7ef2ed4d9d..e11b775c5a 100644 --- a/api/core/agent/patterns/react.py +++ b/api/core/agent/patterns/react.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Any, Union from core.agent.entities import AgentLog, AgentResult, AgentScratchpadUnit, ExecutionContext from core.agent.output_parser.cot_output_parser import CotAgentOutputParser from core.model_manager import ModelInstance -from core.model_runtime.entities import ( +from dify_graph.model_runtime.entities import ( AssistantPromptMessage, LLMResult, LLMResultChunk, @@ -17,7 +17,7 @@ from core.model_runtime.entities import ( PromptMessage, SystemPromptMessage, ) -from core.workflow.file import File +from dify_graph.file import File from .base import AgentPattern, ToolInvokeHook @@ -204,7 +204,7 @@ class ReActStrategy(AgentPattern): tool_names = [tool.name for tool in prompt_tools] # Format tools as JSON for comprehensive information - from core.model_runtime.utils.encoders import jsonable_encoder + from dify_graph.model_runtime.utils.encoders import jsonable_encoder tools_str = json.dumps(jsonable_encoder(prompt_tools), indent=2) tool_names_str = ", ".join(f'"{name}"' for name in tool_names) diff --git a/api/core/agent/patterns/strategy_factory.py b/api/core/agent/patterns/strategy_factory.py index 54892c5f1c..6d41571449 100644 --- a/api/core/agent/patterns/strategy_factory.py +++ b/api/core/agent/patterns/strategy_factory.py @@ -6,8 +6,8 @@ from typing import TYPE_CHECKING from core.agent.entities import AgentEntity, ExecutionContext from core.model_manager import ModelInstance -from core.model_runtime.entities.model_entities import ModelFeature -from core.workflow.file.models import File +from dify_graph.model_runtime.entities.model_entities import ModelFeature +from dify_graph.file.models import File from .base import AgentPattern, ToolInvokeHook from .function_call import FunctionCallStrategy diff --git a/api/core/app/layers/sandbox_layer.py b/api/core/app/layers/sandbox_layer.py index f66054d41c..1d91f73e40 100644 --- a/api/core/app/layers/sandbox_layer.py +++ b/api/core/app/layers/sandbox_layer.py @@ -1,7 +1,7 @@ import logging -from core.workflow.graph_engine.layers.base import GraphEngineLayer -from core.workflow.graph_events.base import GraphEngineEvent +from dify_graph.graph_engine.layers.base import GraphEngineLayer +from dify_graph.graph_events.base import GraphEngineEvent from core.sandbox import Sandbox diff --git a/api/core/llm_generator/output_models.py b/api/core/llm_generator/output_models.py index ab9af37ea7..deb2a2c6f8 100644 --- a/api/core/llm_generator/output_models.py +++ b/api/core/llm_generator/output_models.py @@ -2,7 +2,7 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field -from core.workflow.variables.types import SegmentType +from dify_graph.variables.types import SegmentType class SuggestedQuestionsOutput(BaseModel): diff --git a/api/core/llm_generator/output_parser/file_ref.py b/api/core/llm_generator/output_parser/file_ref.py index e1f4200ee1..5fb0f7e28b 100644 --- a/api/core/llm_generator/output_parser/file_ref.py +++ b/api/core/llm_generator/output_parser/file_ref.py @@ -10,8 +10,8 @@ This module provides utilities to: from collections.abc import Callable, Mapping, Sequence from typing import Any, cast -from core.workflow.file import File -from core.workflow.variables.segments import ArrayFileSegment, FileSegment +from dify_graph.file import File +from dify_graph.variables.segments import ArrayFileSegment, FileSegment FILE_PATH_FORMAT = "file-path" FILE_PATH_DESCRIPTION_SUFFIX = "this field contains a file path from the Dify sandbox" diff --git a/api/core/llm_generator/utils.py b/api/core/llm_generator/utils.py index 86c9091dd4..7ff57e688a 100644 --- a/api/core/llm_generator/utils.py +++ b/api/core/llm_generator/utils.py @@ -1,6 +1,6 @@ """Utility functions for LLM generator.""" -from core.model_runtime.entities.message_entities import ( +from dify_graph.model_runtime.entities.message_entities import ( AssistantPromptMessage, PromptMessage, PromptMessageRole, diff --git a/api/core/memory/base.py b/api/core/memory/base.py index af6e8eeda3..5a5fa7d145 100644 --- a/api/core/memory/base.py +++ b/api/core/memory/base.py @@ -7,7 +7,7 @@ This module defines the common protocol for memory implementations. from abc import ABC, abstractmethod from collections.abc import Sequence -from core.model_runtime.entities import ImagePromptMessageContent, PromptMessage +from dify_graph.model_runtime.entities import ImagePromptMessageContent, PromptMessage class BaseMemory(ABC): @@ -49,7 +49,7 @@ class BaseMemory(ABC): :param message_limit: Maximum number of messages :return: Formatted history text """ - from core.model_runtime.entities import ( + from dify_graph.model_runtime.entities import ( PromptMessageRole, TextPromptMessageContent, ) diff --git a/api/core/memory/node_token_buffer_memory.py b/api/core/memory/node_token_buffer_memory.py index a9fbbca615..687169c721 100644 --- a/api/core/memory/node_token_buffer_memory.py +++ b/api/core/memory/node_token_buffer_memory.py @@ -22,7 +22,7 @@ from sqlalchemy.orm import Session from core.memory.base import BaseMemory from core.model_manager import ModelInstance -from core.model_runtime.entities import ( +from dify_graph.model_runtime.entities import ( AssistantPromptMessage, MultiModalPromptMessageContent, PromptMessage, @@ -31,9 +31,9 @@ from core.model_runtime.entities import ( ToolPromptMessage, UserPromptMessage, ) -from core.model_runtime.entities.message_entities import PromptMessageContentUnionTypes +from dify_graph.model_runtime.entities.message_entities import PromptMessageContentUnionTypes from core.prompt.utils.extract_thread_messages import extract_thread_messages -from core.workflow.file import file_manager +from dify_graph.file import file_manager from extensions.ext_database import db from models.model import Message from models.workflow import WorkflowNodeExecutionModel diff --git a/api/core/sandbox/bash/dify_cli.py b/api/core/sandbox/bash/dify_cli.py index 9ef498eeed..1c18daaee3 100644 --- a/api/core/sandbox/bash/dify_cli.py +++ b/api/core/sandbox/bash/dify_cli.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any from pydantic import BaseModel, Field -from core.model_runtime.utils.encoders import jsonable_encoder +from dify_graph.model_runtime.utils.encoders import jsonable_encoder from core.session.cli_api import CliApiSession from core.skill.entities import ToolDependencies, ToolReference from core.tools.entities.tool_entities import ToolParameter, ToolProviderType diff --git a/api/core/sandbox/bash/session.py b/api/core/sandbox/bash/session.py index 0dbf06e3e0..e0e3de5480 100644 --- a/api/core/sandbox/bash/session.py +++ b/api/core/sandbox/bash/session.py @@ -14,7 +14,7 @@ from core.skill.entities.tool_dependencies import ToolDependencies from core.tools.signature import sign_tool_file from core.tools.tool_file_manager import ToolFileManager from core.virtual_environment.__base.helpers import pipeline -from core.workflow.file import File, FileTransferMethod, FileType +from dify_graph.file import File, FileTransferMethod, FileType from ..bash.dify_cli import DifyCliConfig from ..entities import DifyCli diff --git a/api/core/tools/__base/tool.py b/api/core/tools/__base/tool.py index e68b498f88..b3598c7578 100644 --- a/api/core/tools/__base/tool.py +++ b/api/core/tools/__base/tool.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any if TYPE_CHECKING: # pragma: no cover from models.model import File -from core.model_runtime.entities.message_entities import PromptMessageTool +from dify_graph.model_runtime.entities.message_entities import PromptMessageTool from core.tools.__base.tool_runtime import ToolRuntime from core.tools.entities.tool_entities import ( diff --git a/api/core/workflow/nodes/command/entities.py b/api/core/workflow/nodes/command/entities.py index 8a4f5f8b05..d5871935d2 100644 --- a/api/core/workflow/nodes/command/entities.py +++ b/api/core/workflow/nodes/command/entities.py @@ -1,4 +1,4 @@ -from core.workflow.nodes.base import BaseNodeData +from dify_graph.nodes.base import BaseNodeData class CommandNodeData(BaseNodeData): diff --git a/api/core/workflow/nodes/command/node.py b/api/core/workflow/nodes/command/node.py index 0bcee4613c..f4256bc0c1 100644 --- a/api/core/workflow/nodes/command/node.py +++ b/api/core/workflow/nodes/command/node.py @@ -2,17 +2,17 @@ import logging from collections.abc import Mapping, Sequence from typing import Any -from core.workflow.enums import NodeType, WorkflowNodeExecutionStatus -from core.workflow.nodes.base.entities import VariableSelector -from core.workflow.nodes.base.node import Node -from core.workflow.nodes.base.variable_template_parser import VariableTemplateParser +from dify_graph.enums import NodeType, WorkflowNodeExecutionStatus +from dify_graph.nodes.base.entities import VariableSelector +from dify_graph.nodes.base.node import Node +from dify_graph.nodes.base.variable_template_parser import VariableTemplateParser from core.sandbox import sandbox_debug from core.sandbox.bash.session import SANDBOX_READY_TIMEOUT from core.virtual_environment.__base.command_future import CommandCancelledError, CommandTimeoutError from core.virtual_environment.__base.helpers import submit_command, with_connection -from core.workflow.node_events import NodeRunResult -from core.workflow.nodes.base import variable_template_parser +from dify_graph.node_events import NodeRunResult +from dify_graph.nodes.base import variable_template_parser from core.workflow.nodes.command.entities import CommandNodeData from core.workflow.nodes.command.exc import CommandExecutionError diff --git a/api/core/workflow/nodes/file_upload/entities.py b/api/core/workflow/nodes/file_upload/entities.py index 1c23515780..2e3d6f75ef 100644 --- a/api/core/workflow/nodes/file_upload/entities.py +++ b/api/core/workflow/nodes/file_upload/entities.py @@ -1,6 +1,6 @@ from collections.abc import Sequence -from core.workflow.nodes.base import BaseNodeData +from dify_graph.nodes.base import BaseNodeData class FileUploadNodeData(BaseNodeData): diff --git a/api/core/workflow/nodes/file_upload/node.py b/api/core/workflow/nodes/file_upload/node.py index c97d5fe609..2d2582387f 100644 --- a/api/core/workflow/nodes/file_upload/node.py +++ b/api/core/workflow/nodes/file_upload/node.py @@ -5,16 +5,16 @@ from collections.abc import Mapping, Sequence from pathlib import PurePosixPath from typing import Any, cast -from core.workflow.enums import NodeType, WorkflowNodeExecutionStatus -from core.workflow.nodes.base.node import Node -from core.workflow.variables.segments import ArrayStringSegment, FileSegment +from dify_graph.enums import NodeType, WorkflowNodeExecutionStatus +from dify_graph.nodes.base.node import Node +from dify_graph.variables.segments import ArrayStringSegment, FileSegment from core.sandbox.bash.session import SANDBOX_READY_TIMEOUT from core.virtual_environment.__base.command_future import CommandCancelledError, CommandTimeoutError from core.virtual_environment.__base.helpers import pipeline -from core.workflow.file import File, FileTransferMethod -from core.workflow.node_events import NodeRunResult -from core.workflow.variables import ArrayFileSegment +from dify_graph.file import File, FileTransferMethod +from dify_graph.node_events import NodeRunResult +from dify_graph.variables import ArrayFileSegment from core.zip_sandbox import SandboxDownloadItem from .entities import FileUploadNodeData diff --git a/api/dify_graph/context/__init__.py b/api/dify_graph/context/__init__.py index 103f526bec..b3109c04d4 100644 --- a/api/dify_graph/context/__init__.py +++ b/api/dify_graph/context/__init__.py @@ -17,15 +17,12 @@ from dify_graph.context.execution_context import ( register_context_capturer, reset_context_provider, ) -from dify_graph.context.models import SandboxContext - __all__ = [ "AppContext", "ContextProviderNotFoundError", "ExecutionContext", "IExecutionContext", "NullAppContext", - "SandboxContext", "capture_current_context", "read_context", "register_context", diff --git a/api/dify_graph/nodes/agent/agent_node.py b/api/dify_graph/nodes/agent/agent_node.py index fa3e1b4d7c..98e1c748be 100644 --- a/api/dify_graph/nodes/agent/agent_node.py +++ b/api/dify_graph/nodes/agent/agent_node.py @@ -517,7 +517,7 @@ class AgentNode(Node[AgentNodeData]): Fetch memory instance for saving node memory. This is a simplified version that doesn't require model_instance. """ - from core.model_runtime.entities.model_entities import ModelType + from dify_graph.model_runtime.entities.model_entities import ModelType from core.model_manager import ModelManager diff --git a/api/dify_graph/nodes/base/node.py b/api/dify_graph/nodes/base/node.py index 6ea67a02b7..7efb4315f3 100644 --- a/api/dify_graph/nodes/base/node.py +++ b/api/dify_graph/nodes/base/node.py @@ -378,7 +378,7 @@ class Node(Generic[NodeDataT]): Nested nodes are nodes with parent_node_id == self._node_id. They are executed before the main node to extract values from list[PromptMessage]. """ - from core.app.workflow.node_factory import DifyNodeFactory + from core.workflow.node_factory import DifyNodeFactory extractor_configs = self._find_extractor_node_configs() logger.debug("[NestedNode] Found %d nested nodes for parent '%s'", len(extractor_configs), self._node_id) @@ -689,7 +689,7 @@ class Node(Generic[NodeDataT]): @_dispatch.register def _(self, event: StreamChunkEvent) -> NodeRunStreamChunkEvent: - from core.workflow.graph_events import ChunkType + from dify_graph.graph_events import ChunkType return NodeRunStreamChunkEvent( id=self.execution_id, @@ -711,7 +711,7 @@ class Node(Generic[NodeDataT]): @_dispatch.register def _(self, event: ToolCallChunkEvent) -> NodeRunStreamChunkEvent: - from core.workflow.graph_events import ChunkType + from dify_graph.graph_events import ChunkType return NodeRunStreamChunkEvent( id=self._node_execution_id, @@ -726,8 +726,8 @@ class Node(Generic[NodeDataT]): @_dispatch.register def _(self, event: ToolResultChunkEvent) -> NodeRunStreamChunkEvent: - from core.workflow.entities import ToolResult, ToolResultStatus - from core.workflow.graph_events import ChunkType + from dify_graph.entities import ToolResult, ToolResultStatus + from dify_graph.graph_events import ChunkType tool_result = event.tool_result or ToolResult() status: ToolResultStatus = tool_result.status or ToolResultStatus.SUCCESS @@ -748,7 +748,7 @@ class Node(Generic[NodeDataT]): @_dispatch.register def _(self, event: ThoughtChunkEvent) -> NodeRunStreamChunkEvent: - from core.workflow.graph_events import ChunkType + from dify_graph.graph_events import ChunkType return NodeRunStreamChunkEvent( id=self._node_execution_id, diff --git a/api/dify_graph/nodes/llm/llm_utils.py b/api/dify_graph/nodes/llm/llm_utils.py index 81d7d8cd73..783d4938c9 100644 --- a/api/dify_graph/nodes/llm/llm_utils.py +++ b/api/dify_graph/nodes/llm/llm_utils.py @@ -199,7 +199,7 @@ def _build_messages_from_trace( assistant_response: str, file_suffix: str = "", ) -> list[PromptMessage]: - from core.workflow.nodes.llm.entities import ModelTraceSegment, ToolTraceSegment + from dify_graph.nodes.llm.entities import ModelTraceSegment, ToolTraceSegment messages: list[PromptMessage] = [] covered_text_len = 0 @@ -266,12 +266,12 @@ def _truncate_multimodal_content(message: PromptMessage) -> PromptMessage: def restore_multimodal_content_in_messages(messages: Sequence[PromptMessage]) -> list[PromptMessage]: - from core.workflow.file import file_manager - - return [_restore_message_content(msg, file_manager) for msg in messages] + return [_restore_message_content(msg) for msg in messages] -def _restore_message_content(message: PromptMessage, file_manager) -> PromptMessage: +def _restore_message_content(message: PromptMessage) -> PromptMessage: + from dify_graph.file.file_manager import restore_multimodal_content + content = message.content if content is None or isinstance(content, str): return message @@ -279,7 +279,7 @@ def _restore_message_content(message: PromptMessage, file_manager) -> PromptMess restored_content: list[PromptMessageContentUnionTypes] = [] for item in content: if isinstance(item, MultiModalPromptMessageContent): - restored_item = file_manager.restore_multimodal_content(item) + restored_item = restore_multimodal_content(item) restored_content.append(cast(PromptMessageContentUnionTypes, restored_item)) else: restored_content.append(item) diff --git a/api/dify_graph/nodes/llm/node.py b/api/dify_graph/nodes/llm/node.py index 9c9386e28f..979b9aef69 100644 --- a/api/dify_graph/nodes/llm/node.py +++ b/api/dify_graph/nodes/llm/node.py @@ -2201,7 +2201,7 @@ class LLMNode(Node[LLMNodeData]): def _extract_prompt_files(self, variable_pool: VariablePool) -> list[File]: """Extract files from prompt template variables.""" - from core.workflow.variables import ArrayFileVariable, FileVariable + from dify_graph.variables import ArrayFileVariable, FileVariable files: list[File] = [] diff --git a/api/dify_graph/nodes/tool/tool_node.py b/api/dify_graph/nodes/tool/tool_node.py index 6a6ffa6432..06fe788682 100644 --- a/api/dify_graph/nodes/tool/tool_node.py +++ b/api/dify_graph/nodes/tool/tool_node.py @@ -568,7 +568,7 @@ class ToolNode(Node[ToolNodeData]): :param parent_node_id: the parent node id to find nested nodes for :return: mapping of variable key to variable selector """ - from core.workflow.nodes.node_mapping import NODE_TYPE_CLASSES_MAPPING + from dify_graph.nodes.node_mapping import NODE_TYPE_CLASSES_MAPPING result: dict[str, Sequence[str]] = {} nodes = graph_config.get("nodes", []) diff --git a/api/dify_graph/variables/utils.py b/api/dify_graph/variables/utils.py index 799a923084..2340c04536 100644 --- a/api/dify_graph/variables/utils.py +++ b/api/dify_graph/variables/utils.py @@ -3,7 +3,7 @@ from typing import Any import orjson -from core.model_runtime.entities import PromptMessage +from dify_graph.model_runtime.entities import PromptMessage from .segment_group import SegmentGroup from .segments import ArrayFileSegment, ArrayPromptMessageSegment, FileSegment, Segment diff --git a/api/services/skill_service.py b/api/services/skill_service.py index 347295139d..6acee37fc3 100644 --- a/api/services/skill_service.py +++ b/api/services/skill_service.py @@ -18,7 +18,7 @@ from collections.abc import Mapping from functools import reduce from typing import Any, cast -from core.workflow.enums import NodeType +from dify_graph.enums import NodeType from core.app.entities.app_asset_entities import AppAssetFileTree, AppAssetNode from core.sandbox.entities.config import AppAssets diff --git a/api/services/workflow/nested_node_graph_service.py b/api/services/workflow/nested_node_graph_service.py index fac463e0b1..9f89a27032 100644 --- a/api/services/workflow/nested_node_graph_service.py +++ b/api/services/workflow/nested_node_graph_service.py @@ -7,10 +7,10 @@ extracting values from list[PromptMessage] variables. from typing import Any -from core.workflow.enums import NodeType +from dify_graph.enums import NodeType from sqlalchemy.orm import Session -from core.model_runtime.entities import LLMMode +from dify_graph.model_runtime.entities import LLMMode from services.model_provider_service import ModelProviderService from services.workflow.entities import NestedNodeGraphRequest, NestedNodeGraphResponse, NestedNodeParameterSchema diff --git a/api/tests/unit_tests/core/agent/patterns/test_base.py b/api/tests/unit_tests/core/agent/patterns/test_base.py index 3ab34b8d67..50696a6d97 100644 --- a/api/tests/unit_tests/core/agent/patterns/test_base.py +++ b/api/tests/unit_tests/core/agent/patterns/test_base.py @@ -4,7 +4,7 @@ from decimal import Decimal from unittest.mock import MagicMock import pytest -from core.model_runtime.entities.llm_entities import LLMUsage +from dify_graph.model_runtime.entities.llm_entities import LLMUsage from core.agent.entities import AgentLog, ExecutionContext from core.agent.patterns.base import AgentPattern diff --git a/api/tests/unit_tests/core/agent/patterns/test_function_call.py b/api/tests/unit_tests/core/agent/patterns/test_function_call.py index 3bffa9dfff..a5c07749bc 100644 --- a/api/tests/unit_tests/core/agent/patterns/test_function_call.py +++ b/api/tests/unit_tests/core/agent/patterns/test_function_call.py @@ -4,8 +4,8 @@ from decimal import Decimal from unittest.mock import MagicMock import pytest -from core.model_runtime.entities.llm_entities import LLMUsage -from core.model_runtime.entities.message_entities import ( +from dify_graph.model_runtime.entities.llm_entities import LLMUsage +from dify_graph.model_runtime.entities.message_entities import ( PromptMessageTool, SystemPromptMessage, UserPromptMessage, @@ -312,7 +312,7 @@ class TestPromptMessageHandling: def test_assistant_message_with_tool_calls(self, mock_model_instance, mock_context, mock_tool): """Test that assistant messages can contain tool calls.""" - from core.model_runtime.entities.message_entities import AssistantPromptMessage + from dify_graph.model_runtime.entities.message_entities import AssistantPromptMessage tool_call = AssistantPromptMessage.ToolCall( id="call_123", diff --git a/api/tests/unit_tests/core/agent/patterns/test_react.py b/api/tests/unit_tests/core/agent/patterns/test_react.py index a942ba6100..012a7ec077 100644 --- a/api/tests/unit_tests/core/agent/patterns/test_react.py +++ b/api/tests/unit_tests/core/agent/patterns/test_react.py @@ -6,7 +6,7 @@ import pytest from core.agent.entities import ExecutionContext from core.agent.patterns.react import ReActStrategy -from core.model_runtime.entities import SystemPromptMessage, UserPromptMessage +from dify_graph.model_runtime.entities import SystemPromptMessage, UserPromptMessage @pytest.fixture @@ -33,7 +33,7 @@ def mock_context(): @pytest.fixture def mock_tool(): """Create a mock tool.""" - from core.model_runtime.entities.message_entities import PromptMessageTool + from dify_graph.model_runtime.entities.message_entities import PromptMessageTool tool = MagicMock() tool.entity.identity.name = "test_tool" @@ -158,7 +158,7 @@ class TestBuildPromptWithReactFormat: def test_scratchpad_appended_as_assistant_message(self, mock_model_instance, mock_context): """Test that agent scratchpad is appended as AssistantPromptMessage.""" from core.agent.entities import AgentScratchpadUnit - from core.model_runtime.entities import AssistantPromptMessage + from dify_graph.model_runtime.entities import AssistantPromptMessage strategy = ReActStrategy( model_instance=mock_model_instance, diff --git a/api/tests/unit_tests/core/agent/patterns/test_strategy_factory.py b/api/tests/unit_tests/core/agent/patterns/test_strategy_factory.py index ca5931cdca..79ff155af6 100644 --- a/api/tests/unit_tests/core/agent/patterns/test_strategy_factory.py +++ b/api/tests/unit_tests/core/agent/patterns/test_strategy_factory.py @@ -3,7 +3,7 @@ from unittest.mock import MagicMock import pytest -from core.model_runtime.entities.model_entities import ModelFeature +from dify_graph.model_runtime.entities.model_entities import ModelFeature from core.agent.entities import AgentEntity, ExecutionContext from core.agent.patterns.function_call import FunctionCallStrategy diff --git a/api/tests/unit_tests/core/agent/test_agent_app_runner.py b/api/tests/unit_tests/core/agent/test_agent_app_runner.py index e2ff344260..fc02d87dfc 100644 --- a/api/tests/unit_tests/core/agent/test_agent_app_runner.py +++ b/api/tests/unit_tests/core/agent/test_agent_app_runner.py @@ -4,10 +4,10 @@ from decimal import Decimal from unittest.mock import MagicMock, patch import pytest -from core.model_runtime.entities.llm_entities import LLMUsage +from dify_graph.model_runtime.entities.llm_entities import LLMUsage from core.agent.entities import AgentEntity, AgentLog, AgentPromptEntity, AgentResult -from core.model_runtime.entities import SystemPromptMessage, UserPromptMessage +from dify_graph.model_runtime.entities import SystemPromptMessage, UserPromptMessage class TestOrganizePromptMessages: @@ -184,7 +184,7 @@ class TestClearUserPromptImageMessages: def test_original_messages_not_modified(self, mock_runner): """Test that original messages are not modified (deep copy).""" - from core.model_runtime.entities.message_entities import ( + from dify_graph.model_runtime.entities.message_entities import ( ImagePromptMessageContent, TextPromptMessageContent, ) @@ -365,13 +365,13 @@ class TestOrganizeUserQuery: def test_query_with_files(self, mock_runner): """Test organizing a query with files.""" - from core.workflow.file.models import File + from dify_graph.file.models import File mock_file = MagicMock(spec=File) mock_runner.files = [mock_file] with patch("core.agent.agent_app_runner.file_manager") as mock_fm: - from core.model_runtime.entities.message_entities import ImagePromptMessageContent + from dify_graph.model_runtime.entities.message_entities import ImagePromptMessageContent mock_fm.to_prompt_message_content.return_value = ImagePromptMessageContent( data="http://example.com/image.jpg", diff --git a/api/tests/unit_tests/core/app/apps/test_workflow_app_runner_stream_chunk.py b/api/tests/unit_tests/core/app/apps/test_workflow_app_runner_stream_chunk.py index 8779e8c586..e891a9edba 100644 --- a/api/tests/unit_tests/core/app/apps/test_workflow_app_runner_stream_chunk.py +++ b/api/tests/unit_tests/core/app/apps/test_workflow_app_runner_stream_chunk.py @@ -2,8 +2,8 @@ from unittest.mock import MagicMock from core.app.apps.base_app_queue_manager import PublishFrom from core.app.apps.workflow_app_runner import WorkflowBasedAppRunner -from core.workflow.graph_events import NodeRunStreamChunkEvent -from core.workflow.nodes import NodeType +from dify_graph.graph_events import NodeRunStreamChunkEvent +from dify_graph.enums import NodeType class DummyQueueManager: diff --git a/api/tests/unit_tests/core/file/test_file_manager.py b/api/tests/unit_tests/core/file/test_file_manager.py index 65707c74fc..b4eb7d3a21 100644 --- a/api/tests/unit_tests/core/file/test_file_manager.py +++ b/api/tests/unit_tests/core/file/test_file_manager.py @@ -2,14 +2,14 @@ from unittest.mock import patch -from core.model_runtime.entities.message_entities import ImagePromptMessageContent -from core.workflow.file.file_manager import ( +from dify_graph.model_runtime.entities.message_entities import ImagePromptMessageContent +from dify_graph.file.file_manager import ( _encode_file_ref, restore_multimodal_content, to_prompt_message_content, ) -from core.workflow.file import File, FileTransferMethod, FileType +from dify_graph.file import File, FileTransferMethod, FileType class TestEncodeFileRef: @@ -52,8 +52,8 @@ class TestEncodeFileRef: class TestToPromptMessageContent: """Tests for to_prompt_message_content function with file_ref field.""" - @patch("core.workflow.file.file_manager.dify_config") - @patch("core.workflow.file.file_manager._get_encoded_string") + @patch("dify_graph.file.file_manager.dify_config") + @patch("dify_graph.file.file_manager._get_encoded_string") def test_includes_file_ref(self, mock_get_encoded, mock_config): """Generated content should include file_ref field.""" mock_config.MULTIMODAL_SEND_FORMAT = "base64" @@ -121,9 +121,9 @@ class TestRestoreMultimodalContent: assert result.url == "https://example.com/image.png" - @patch("core.workflow.file.file_manager.dify_config") - @patch("core.workflow.file.file_manager._build_file_from_ref") - @patch("core.workflow.file.file_manager._to_url") + @patch("dify_graph.file.file_manager.dify_config") + @patch("dify_graph.file.file_manager._build_file_from_ref") + @patch("dify_graph.file.file_manager._to_url") def test_restores_url_from_file_ref(self, mock_to_url, mock_build_file, mock_config): """Content should be restored from file_ref when url is empty (url mode).""" mock_config.MULTIMODAL_SEND_FORMAT = "url" @@ -144,9 +144,9 @@ class TestRestoreMultimodalContent: assert result.url == "https://restored-url.com/image.png" mock_build_file.assert_called_once() - @patch("core.workflow.file.file_manager.dify_config") - @patch("core.workflow.file.file_manager._build_file_from_ref") - @patch("core.workflow.file.file_manager._get_encoded_string") + @patch("dify_graph.file.file_manager.dify_config") + @patch("dify_graph.file.file_manager._build_file_from_ref") + @patch("dify_graph.file.file_manager._get_encoded_string") def test_restores_base64_from_file_ref(self, mock_get_encoded, mock_build_file, mock_config): """Content should be restored as base64 when in base64 mode.""" mock_config.MULTIMODAL_SEND_FORMAT = "base64" diff --git a/api/tests/unit_tests/core/llm_generator/output_parser/test_file_ref.py b/api/tests/unit_tests/core/llm_generator/output_parser/test_file_ref.py index f000b3d5ef..d237dee68c 100644 --- a/api/tests/unit_tests/core/llm_generator/output_parser/test_file_ref.py +++ b/api/tests/unit_tests/core/llm_generator/output_parser/test_file_ref.py @@ -3,7 +3,7 @@ Unit tests for sandbox file path detection and conversion. """ import pytest -from core.workflow.variables.segments import ArrayFileSegment, FileSegment +from dify_graph.variables.segments import ArrayFileSegment, FileSegment from core.llm_generator.output_parser.file_ref import ( FILE_PATH_DESCRIPTION_SUFFIX, @@ -13,7 +13,7 @@ from core.llm_generator.output_parser.file_ref import ( detect_file_path_fields, is_file_path_property, ) -from core.workflow.file import File, FileTransferMethod, FileType +from dify_graph.file import File, FileTransferMethod, FileType def _build_file(file_id: str) -> File: diff --git a/api/tests/unit_tests/core/workflow/graph_engine/test_response_coordinator.py b/api/tests/unit_tests/core/workflow/graph_engine/test_response_coordinator.py index 4ba1e6ae0b..d6b833e5e4 100644 --- a/api/tests/unit_tests/core/workflow/graph_engine/test_response_coordinator.py +++ b/api/tests/unit_tests/core/workflow/graph_engine/test_response_coordinator.py @@ -2,21 +2,21 @@ from unittest.mock import MagicMock -from core.workflow.entities.tool_entities import ToolResultStatus -from core.workflow.enums import NodeType -from core.workflow.graph.graph import Graph -from core.workflow.graph_engine.response_coordinator.coordinator import ResponseStreamCoordinator -from core.workflow.graph_engine.response_coordinator.session import ResponseSession -from core.workflow.nodes.base.entities import BaseNodeData -from core.workflow.nodes.base.template import Template, VariableSegment +from dify_graph.entities.tool_entities import ToolResultStatus +from dify_graph.enums import NodeType +from dify_graph.graph.graph import Graph +from dify_graph.graph_engine.response_coordinator.coordinator import ResponseStreamCoordinator +from dify_graph.graph_engine.response_coordinator.session import ResponseSession +from dify_graph.nodes.base.entities import BaseNodeData +from dify_graph.nodes.base.template import Template, VariableSegment -from core.workflow.graph_events import ( +from dify_graph.graph_events import ( ChunkType, NodeRunStreamChunkEvent, ToolCall, ToolResult, ) -from core.workflow.runtime import VariablePool +from dify_graph.runtime import VariablePool class TestResponseCoordinatorObjectStreaming: diff --git a/api/tests/unit_tests/core/workflow/node_events/test_stream_chunk_events.py b/api/tests/unit_tests/core/workflow/node_events/test_stream_chunk_events.py index 951149e933..e78d5da7db 100644 --- a/api/tests/unit_tests/core/workflow/node_events/test_stream_chunk_events.py +++ b/api/tests/unit_tests/core/workflow/node_events/test_stream_chunk_events.py @@ -1,7 +1,7 @@ """Tests for StreamChunkEvent and its subclasses.""" -from core.workflow.entities import ToolCall, ToolResult, ToolResultStatus -from core.workflow.node_events import ( +from dify_graph.entities import ToolCall, ToolResult, ToolResultStatus +from dify_graph.node_events import ( ChunkType, StreamChunkEvent, ThoughtChunkEvent, diff --git a/api/tests/unit_tests/core/workflow/nodes/command/test_command_node.py b/api/tests/unit_tests/core/workflow/nodes/command/test_command_node.py index e30b3776a4..3d1936b6a0 100644 --- a/api/tests/unit_tests/core/workflow/nodes/command/test_command_node.py +++ b/api/tests/unit_tests/core/workflow/nodes/command/test_command_node.py @@ -4,8 +4,8 @@ from io import BytesIO from typing import Any from unittest.mock import MagicMock -from core.workflow.enums import WorkflowNodeExecutionStatus -from core.workflow.system_variable import SystemVariable +from dify_graph.enums import WorkflowNodeExecutionStatus +from dify_graph.system_variable import SystemVariable from core.entities.provider_entities import BasicProviderConfig from core.virtual_environment.__base.entities import ( @@ -19,9 +19,10 @@ from core.virtual_environment.__base.entities import ( from core.virtual_environment.__base.virtual_environment import VirtualEnvironment from core.virtual_environment.channel.queue_transport import QueueTransportReadCloser from core.virtual_environment.channel.transport import NopTransportWriteCloser -from core.workflow.entities import GraphInitParams +from dify_graph.entities import GraphInitParams +from dify_graph.entities.graph_init_params import DIFY_RUN_CONTEXT_KEY from core.workflow.nodes.command.node import CommandNode -from core.workflow.runtime import GraphRuntimeState, VariablePool +from dify_graph.runtime import GraphRuntimeState, VariablePool class FakeVirtualEnvironment(VirtualEnvironment): @@ -138,14 +139,18 @@ def _make_node( variable_pool = VariablePool(system_variables=system_variables, user_inputs={}) runtime_state = GraphRuntimeState(variable_pool=variable_pool, start_at=time.perf_counter()) init_params = GraphInitParams( - tenant_id="t", - app_id="a", workflow_id="w", graph_config={}, - user_id="u", - user_from="account", - invoke_from="debugger", call_depth=0, + run_context={ + DIFY_RUN_CONTEXT_KEY: { + "tenant_id": "t", + "app_id": "a", + "user_id": "u", + "user_from": "account", + "invoke_from": "debugger", + } + }, ) if vm is not None: diff --git a/api/tests/unit_tests/core/workflow/nodes/llm/test_llm_utils.py b/api/tests/unit_tests/core/workflow/nodes/llm/test_llm_utils.py index 1de5d9532c..0385952e0b 100644 --- a/api/tests/unit_tests/core/workflow/nodes/llm/test_llm_utils.py +++ b/api/tests/unit_tests/core/workflow/nodes/llm/test_llm_utils.py @@ -3,12 +3,12 @@ import string from unittest.mock import patch -from core.model_runtime.entities.message_entities import ( +from dify_graph.model_runtime.entities.message_entities import ( ImagePromptMessageContent, TextPromptMessageContent, UserPromptMessage, ) -from core.workflow.nodes.llm.llm_utils import ( +from dify_graph.nodes.llm.llm_utils import ( _truncate_multimodal_content, build_context, restore_multimodal_content_in_messages, @@ -100,7 +100,7 @@ class TestBuildContext: def test_excludes_system_messages(self): """System messages should be excluded from context.""" - from core.model_runtime.entities.message_entities import SystemPromptMessage + from dify_graph.model_runtime.entities.message_entities import SystemPromptMessage messages = [ SystemPromptMessage(content="You are a helpful assistant."), @@ -125,12 +125,12 @@ class TestBuildContext: def test_builds_context_with_tool_calls_from_generation_data(self): """Should reconstruct full conversation including tool calls when generation_data is provided.""" - from core.model_runtime.entities.llm_entities import LLMUsage - from core.model_runtime.entities.message_entities import ( + from dify_graph.model_runtime.entities.llm_entities import LLMUsage + from dify_graph.model_runtime.entities.message_entities import ( AssistantPromptMessage, ToolPromptMessage, ) - from core.workflow.nodes.llm.entities import ( + from dify_graph.nodes.llm.entities import ( LLMGenerationData, LLMTraceSegment, ModelTraceSegment, @@ -199,12 +199,12 @@ class TestBuildContext: def test_builds_context_with_multiple_tool_calls(self): """Should handle multiple tool calls in a single conversation.""" - from core.model_runtime.entities.llm_entities import LLMUsage - from core.model_runtime.entities.message_entities import ( + from dify_graph.model_runtime.entities.llm_entities import LLMUsage + from dify_graph.model_runtime.entities.message_entities import ( AssistantPromptMessage, ToolPromptMessage, ) - from core.workflow.nodes.llm.entities import ( + from dify_graph.nodes.llm.entities import ( LLMGenerationData, LLMTraceSegment, ModelTraceSegment, @@ -291,8 +291,8 @@ class TestBuildContext: def test_builds_context_with_empty_trace(self): """Should fallback to simple context when trace is empty.""" - from core.model_runtime.entities.llm_entities import LLMUsage - from core.workflow.nodes.llm.entities import LLMGenerationData + from dify_graph.model_runtime.entities.llm_entities import LLMUsage + from dify_graph.nodes.llm.entities import LLMGenerationData messages = [UserPromptMessage(content="Hello!")] @@ -318,7 +318,7 @@ class TestBuildContext: class TestRestoreMultimodalContentInMessages: """Tests for restore_multimodal_content_in_messages function.""" - @patch("core.workflow.file.file_manager.restore_multimodal_content") + @patch("dify_graph.file.file_manager.restore_multimodal_content") def test_restores_multimodal_content(self, mock_restore): """Should restore multimodal content in messages.""" # Setup mock diff --git a/api/tests/unit_tests/core/workflow/nodes/test_llm_node_streaming.py b/api/tests/unit_tests/core/workflow/nodes/test_llm_node_streaming.py index 27d3848fb4..521de01e8f 100644 --- a/api/tests/unit_tests/core/workflow/nodes/test_llm_node_streaming.py +++ b/api/tests/unit_tests/core/workflow/nodes/test_llm_node_streaming.py @@ -3,12 +3,12 @@ from collections.abc import Generator from typing import Any import pytest -from core.model_runtime.entities.llm_entities import LLMUsage -from core.workflow.entities.tool_entities import ToolResultStatus -from core.workflow.nodes.llm.node import LLMNode +from dify_graph.model_runtime.entities.llm_entities import LLMUsage +from dify_graph.entities.tool_entities import ToolResultStatus +from dify_graph.nodes.llm.node import LLMNode -from core.workflow.entities import ToolCallResult -from core.workflow.node_events import ModelInvokeCompletedEvent, NodeEventBase +from dify_graph.entities import ToolCallResult +from dify_graph.node_events import ModelInvokeCompletedEvent, NodeEventBase class _StubModelInstance: @@ -109,9 +109,9 @@ def test_stream_llm_events_no_reasoning_results_in_empty_sequence(): def test_serialize_tool_call_strips_files_to_ids(): - file_cls = pytest.importorskip("core.workflow.file").File - file_type = pytest.importorskip("core.workflow.file.enums").FileType - transfer_method = pytest.importorskip("core.workflow.file.enums").FileTransferMethod + file_cls = pytest.importorskip("dify_graph.file").File + file_type = pytest.importorskip("dify_graph.file.enums").FileType + transfer_method = pytest.importorskip("dify_graph.file.enums").FileTransferMethod file_with_id = file_cls( id="f1", diff --git a/web/__tests__/apps/app-list-browsing-flow.test.tsx b/web/__tests__/apps/app-list-browsing-flow.test.tsx index ddb5113b6a..9035534e01 100644 --- a/web/__tests__/apps/app-list-browsing-flow.test.tsx +++ b/web/__tests__/apps/app-list-browsing-flow.test.tsx @@ -8,6 +8,7 @@ */ import type { AppListResponse } from '@/models/app' import type { App } from '@/types/app' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import List from '@/app/components/apps/list' @@ -165,9 +166,15 @@ const createPage = (apps: App[], hasMore = false, page = 1): AppListResponse => total: apps.length, }) +const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false } }, +}) + const renderList = (searchParams?: Record) => { return renderWithNuqs( - , + + + , { searchParams }, ) } @@ -213,7 +220,9 @@ describe('App List Browsing Flow', () => { it('should transition from loading to content when data loads', () => { mockIsLoading = true - const { rerender } = renderWithNuqs() + const { rerender } = renderWithNuqs( + , + ) const skeletonCards = document.querySelectorAll('.animate-pulse') expect(skeletonCards.length).toBeGreaterThan(0) @@ -224,7 +233,9 @@ describe('App List Browsing Flow', () => { createMockApp({ id: 'app-1', name: 'Loaded App' }), ])] - rerender() + rerender( + , + ) expect(screen.getByText('Loaded App')).toBeInTheDocument() }) @@ -420,9 +431,13 @@ describe('App List Browsing Flow', () => { it('should call refetch when controlRefreshList increments', () => { mockPages = [createPage([createMockApp()])] - const { rerender } = renderWithNuqs() + const { rerender } = renderWithNuqs( + , + ) - rerender() + rerender( + , + ) expect(mockRefetch).toHaveBeenCalled() }) diff --git a/web/__tests__/apps/create-app-flow.test.tsx b/web/__tests__/apps/create-app-flow.test.tsx index d81d1473d2..f1fcac2426 100644 --- a/web/__tests__/apps/create-app-flow.test.tsx +++ b/web/__tests__/apps/create-app-flow.test.tsx @@ -9,6 +9,7 @@ */ import type { AppListResponse } from '@/models/app' import type { App } from '@/types/app' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { fireEvent, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import List from '@/app/components/apps/list' @@ -218,8 +219,16 @@ const createPage = (apps: App[]): AppListResponse => ({ total: apps.length, }) +const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false } }, +}) + const renderList = () => { - return renderWithNuqs() + return renderWithNuqs( + + + , + ) } describe('Create App Flow', () => { @@ -245,7 +254,7 @@ describe('Create App Flow', () => { expect(screen.getByText('app.createApp')).toBeInTheDocument() expect(screen.getByText('app.newApp.startFromBlank')).toBeInTheDocument() expect(screen.getByText('app.newApp.startFromTemplate')).toBeInTheDocument() - expect(screen.getByText('app.importDSL')).toBeInTheDocument() + expect(screen.getByText('app.importApp')).toBeInTheDocument() }) it('should not render NewAppCard when user is not an editor', () => { @@ -354,7 +363,7 @@ describe('Create App Flow', () => { it('should open DSL import modal when "Import DSL" is clicked', async () => { renderList() - fireEvent.click(screen.getByText('app.importDSL')) + fireEvent.click(screen.getByText('app.importApp')) await waitFor(() => { expect(screen.getByTestId('create-from-dsl-modal')).toBeInTheDocument() @@ -364,7 +373,7 @@ describe('Create App Flow', () => { it('should close DSL import modal on cancel', async () => { renderList() - fireEvent.click(screen.getByText('app.importDSL')) + fireEvent.click(screen.getByText('app.importApp')) await waitFor(() => { expect(screen.getByTestId('create-from-dsl-modal')).toBeInTheDocument() }) @@ -378,7 +387,7 @@ describe('Create App Flow', () => { it('should call onPlanInfoChanged and refetch on successful DSL import', async () => { renderList() - fireEvent.click(screen.getByText('app.importDSL')) + fireEvent.click(screen.getByText('app.importApp')) await waitFor(() => { expect(screen.getByTestId('create-from-dsl-modal')).toBeInTheDocument() }) @@ -451,7 +460,7 @@ describe('Create App Flow', () => { // Rapidly click different create options fireEvent.click(screen.getByText('app.newApp.startFromBlank')) fireEvent.click(screen.getByText('app.newApp.startFromTemplate')) - fireEvent.click(screen.getByText('app.importDSL')) + fireEvent.click(screen.getByText('app.importApp')) // Should not crash, and some modal should be present await waitFor(() => { diff --git a/web/app/components/app-sidebar/app-info/__tests__/index.spec.tsx b/web/app/components/app-sidebar/app-info/__tests__/index.spec.tsx index fc0bb56f75..6dc9a4bfc8 100644 --- a/web/app/components/app-sidebar/app-info/__tests__/index.spec.tsx +++ b/web/app/components/app-sidebar/app-info/__tests__/index.spec.tsx @@ -3,11 +3,19 @@ import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' import { AppModeEnum } from '@/types/app' -import AppInfo from '..' +import AppInfo from '../index' let mockIsCurrentWorkspaceEditor = true const mockSetPanelOpen = vi.fn() +vi.mock('next/navigation', () => ({ + useRouter: () => ({ replace: vi.fn() }), +})) + +vi.mock('@/service/use-apps', () => ({ + useInvalidateAppList: () => vi.fn(), +})) + vi.mock('@/context/app-context', () => ({ useAppContext: () => ({ isCurrentWorkspaceEditor: mockIsCurrentWorkspaceEditor, diff --git a/web/app/components/apps/__tests__/app-card.spec.tsx b/web/app/components/apps/__tests__/app-card.spec.tsx index 9bc23ce199..6ebc382c38 100644 --- a/web/app/components/apps/__tests__/app-card.spec.tsx +++ b/web/app/components/apps/__tests__/app-card.spec.tsx @@ -263,11 +263,10 @@ describe('AppCard', () => { }) it('should render app icon', () => { - // AppIcon component renders the emoji icon from app data const { container } = render() - // Check that the icon container is rendered (AppIcon renders within the card) - const iconElement = container.querySelector('[class*="icon"]') || container.querySelector('img') - expect(iconElement || screen.getByText(mockApp.icon)).toBeTruthy() + const emojiElement = container.querySelector('em-emoji') + expect(emojiElement).toBeTruthy() + expect(emojiElement?.getAttribute('id')).toBe(mockApp.icon) }) it('should render app type icon', () => { diff --git a/web/app/components/apps/__tests__/index.spec.tsx b/web/app/components/apps/__tests__/index.spec.tsx index da4fbc2d44..458d68683f 100644 --- a/web/app/components/apps/__tests__/index.spec.tsx +++ b/web/app/components/apps/__tests__/index.spec.tsx @@ -20,6 +20,11 @@ vi.mock('@/app/education-apply/hooks', () => ({ }, })) +vi.mock('next/navigation', () => ({ + useRouter: () => ({ replace: vi.fn() }), + useSearchParams: () => new URLSearchParams(), +})) + vi.mock('@/hooks/use-import-dsl', () => ({ useImportDSL: () => ({ handleImportDSL: vi.fn(), diff --git a/web/app/components/apps/__tests__/list.spec.tsx b/web/app/components/apps/__tests__/list.spec.tsx index 82e2347781..c71a77cac1 100644 --- a/web/app/components/apps/__tests__/list.spec.tsx +++ b/web/app/components/apps/__tests__/list.spec.tsx @@ -1,3 +1,4 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { act, fireEvent, screen } from '@testing-library/react' import * as React from 'react' import { useStore as useTagStore } from '@/app/components/base/tag-management/store' @@ -200,9 +201,17 @@ beforeAll(() => { } as unknown as typeof IntersectionObserver }) -// Render helper wrapping with shared nuqs testing helper. +const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false } }, +}) + const renderList = (searchParams = '') => { - return renderWithNuqs(, { searchParams }) + return renderWithNuqs( + + + , + { searchParams }, + ) } describe('List', () => { @@ -399,10 +408,14 @@ describe('List', () => { describe('Edge Cases', () => { it('should handle multiple renders without issues', () => { - const { rerender } = renderWithNuqs() + const { rerender } = renderWithNuqs( + , + ) expect(screen.getByText('app.types.all')).toBeInTheDocument() - rerender() + rerender( + , + ) expect(screen.getByText('app.types.all')).toBeInTheDocument() }) diff --git a/web/app/components/apps/__tests__/new-app-card.spec.tsx b/web/app/components/apps/__tests__/new-app-card.spec.tsx index f4c357b9f9..9ae793ee8a 100644 --- a/web/app/components/apps/__tests__/new-app-card.spec.tsx +++ b/web/app/components/apps/__tests__/new-app-card.spec.tsx @@ -71,7 +71,7 @@ describe('CreateAppCard', () => { expect(screen.getByText('app.newApp.startFromBlank')).toBeInTheDocument() expect(screen.getByText('app.newApp.startFromTemplate')).toBeInTheDocument() - expect(screen.getByText('app.importDSL')).toBeInTheDocument() + expect(screen.getByText('app.importApp')).toBeInTheDocument() }) it('should render all buttons as clickable', () => { @@ -190,7 +190,7 @@ describe('CreateAppCard', () => { it('should open DSL modal when clicking Import DSL', () => { render() - fireEvent.click(screen.getByText('app.importDSL')) + fireEvent.click(screen.getByText('app.importApp')) expect(screen.getByTestId('create-dsl-modal')).toBeInTheDocument() }) @@ -198,7 +198,7 @@ describe('CreateAppCard', () => { it('should close DSL modal when clicking close button', () => { render() - fireEvent.click(screen.getByText('app.importDSL')) + fireEvent.click(screen.getByText('app.importApp')) expect(screen.getByTestId('create-dsl-modal')).toBeInTheDocument() fireEvent.click(screen.getByTestId('close-dsl-modal')) @@ -209,7 +209,7 @@ describe('CreateAppCard', () => { const mockOnSuccess = vi.fn() render() - fireEvent.click(screen.getByText('app.importDSL')) + fireEvent.click(screen.getByText('app.importApp')) fireEvent.click(screen.getByTestId('success-dsl-modal')) expect(mockOnPlanInfoChanged).toHaveBeenCalled() @@ -245,7 +245,7 @@ describe('CreateAppCard', () => { fireEvent.click(screen.getByText('app.newApp.startFromTemplate')) fireEvent.click(screen.getByTestId('close-template-dialog')) - fireEvent.click(screen.getByText('app.importDSL')) + fireEvent.click(screen.getByText('app.importApp')) fireEvent.click(screen.getByTestId('close-dsl-modal')) expect(screen.queryByTestId('create-app-modal')).not.toBeInTheDocument() diff --git a/web/app/components/base/features/new-feature-panel/conversation-opener/__tests__/modal.spec.tsx b/web/app/components/base/features/new-feature-panel/conversation-opener/__tests__/modal.spec.tsx index f03763d192..e3e3f82dc0 100644 --- a/web/app/components/base/features/new-feature-panel/conversation-opener/__tests__/modal.spec.tsx +++ b/web/app/components/base/features/new-feature-panel/conversation-opener/__tests__/modal.spec.tsx @@ -14,6 +14,7 @@ const getPromptEditor = () => { vi.mock('@/utils/var', () => ({ checkKeys: (_keys: string[]) => ({ isValid: true }), getNewVar: (key: string, type: string) => ({ key, name: key, type, required: true }), + basePath: '', })) vi.mock('@/app/components/app/configuration/config-prompt/confirm-add-var', () => ({ diff --git a/web/app/components/base/prompt-editor/__tests__/index.spec.tsx b/web/app/components/base/prompt-editor/__tests__/index.spec.tsx index 0b86b4e006..2443dea06d 100644 --- a/web/app/components/base/prompt-editor/__tests__/index.spec.tsx +++ b/web/app/components/base/prompt-editor/__tests__/index.spec.tsx @@ -68,6 +68,7 @@ vi.mock('lexical', async (importOriginal) => { getChildren: () => mocks.rootLines.map(line => ({ getTextContent: () => line, })), + getAllTextNodes: () => [], }), TextNode: class TextNode { __text: string diff --git a/web/app/components/header/account-dropdown/index.spec.tsx b/web/app/components/header/account-dropdown/index.spec.tsx index a92f8503ee..fc262333fa 100644 --- a/web/app/components/header/account-dropdown/index.spec.tsx +++ b/web/app/components/header/account-dropdown/index.spec.tsx @@ -77,12 +77,16 @@ const { mockConfig, mockEnv } = vi.hoisted(() => ({ }, }, })) -vi.mock('@/config', () => ({ - get IS_CLOUD_EDITION() { return mockConfig.IS_CLOUD_EDITION }, - get ZENDESK_WIDGET_KEY() { return mockConfig.ZENDESK_WIDGET_KEY }, - IS_DEV: false, - IS_CE_EDITION: false, -})) +vi.mock('@/config', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + get IS_CLOUD_EDITION() { return mockConfig.IS_CLOUD_EDITION }, + get ZENDESK_WIDGET_KEY() { return mockConfig.ZENDESK_WIDGET_KEY }, + IS_DEV: false, + IS_CE_EDITION: false, + } +}) vi.mock('@/env', () => mockEnv) const baseAppContextValue: AppContextValue = { diff --git a/web/app/components/workflow-app/hooks/__tests__/use-nodes-sync-draft.spec.ts b/web/app/components/workflow-app/hooks/__tests__/use-nodes-sync-draft.spec.ts index d35e6e3612..441ddf7e92 100644 --- a/web/app/components/workflow-app/hooks/__tests__/use-nodes-sync-draft.spec.ts +++ b/web/app/components/workflow-app/hooks/__tests__/use-nodes-sync-draft.spec.ts @@ -55,8 +55,20 @@ vi.mock('@/service/workflow', () => ({ syncWorkflowDraft: (p: unknown) => mockSyncWorkflowDraft(p), })) -vi.mock('@/service/fetch', () => ({ postWithKeepalive: vi.fn() })) -vi.mock('@/config', () => ({ API_PREFIX: '/api' })) +vi.mock('@/service/fetch', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + postWithKeepalive: vi.fn(), + } +}) +vi.mock('@/config', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + API_PREFIX: '/api', + } +}) const mockHandleRefreshWorkflowDraft = vi.fn() vi.mock('@/app/components/workflow-app/hooks', () => ({ diff --git a/web/app/components/workflow-app/hooks/__tests__/use-workflow-refresh-draft.spec.ts b/web/app/components/workflow-app/hooks/__tests__/use-workflow-refresh-draft.spec.ts index 2fd06e587b..d9f5efa05f 100644 --- a/web/app/components/workflow-app/hooks/__tests__/use-workflow-refresh-draft.spec.ts +++ b/web/app/components/workflow-app/hooks/__tests__/use-workflow-refresh-draft.spec.ts @@ -11,7 +11,7 @@ vi.mock('@/app/components/workflow/store', () => ({ getState: () => ({ appId: 'app-1', isWorkflowDataLoaded: true, - debouncedSyncWorkflowDraft: undefined, + debouncedSyncWorkflowDraft: { cancel: vi.fn() }, setSyncWorkflowDraftHash: mockSetSyncWorkflowDraftHash, setIsSyncingWorkflowDraft: vi.fn(), setEnvironmentVariables: vi.fn(), diff --git a/web/app/components/workflow/hooks/__tests__/use-edges-interactions.spec.ts b/web/app/components/workflow/hooks/__tests__/use-edges-interactions.spec.ts index 6d19862efd..620ba15bf6 100644 --- a/web/app/components/workflow/hooks/__tests__/use-edges-interactions.spec.ts +++ b/web/app/components/workflow/hooks/__tests__/use-edges-interactions.spec.ts @@ -26,6 +26,7 @@ vi.mock('../use-workflow', () => ({ vi.mock('../../utils', () => ({ getNodesConnectedSourceOrTargetHandleIdsMap: vi.fn(() => ({})), + genNodeMetaData: vi.fn(({ type, sort }: { type: string, sort: number }) => ({ type, sort })), })) // useNodesSyncDraft is used REAL — via renderWorkflowHook + hooksStoreProps diff --git a/web/app/components/workflow/hooks/__tests__/use-workflow-run-event-store-only.spec.ts b/web/app/components/workflow/hooks/__tests__/use-workflow-run-event-store-only.spec.ts index 2085e5ab47..e10988cf1a 100644 --- a/web/app/components/workflow/hooks/__tests__/use-workflow-run-event-store-only.spec.ts +++ b/web/app/components/workflow/hooks/__tests__/use-workflow-run-event-store-only.spec.ts @@ -53,7 +53,7 @@ describe('useWorkflowTextChunk', () => { }, }) - result.current.handleWorkflowTextChunk({ data: { text: ' World' } } as TextChunkResponse) + result.current.handleWorkflowTextChunk({ data: { text: ' World', chunk_type: 'text' } } as TextChunkResponse) const state = store.getState().workflowRunningData! expect(state.resultText).toBe('Hello World') diff --git a/web/app/components/workflow/store/__tests__/workflow-store.spec.ts b/web/app/components/workflow/store/__tests__/workflow-store.spec.ts index c917986953..7ad0be02c7 100644 --- a/web/app/components/workflow/store/__tests__/workflow-store.spec.ts +++ b/web/app/components/workflow/store/__tests__/workflow-store.spec.ts @@ -172,9 +172,9 @@ describe('createWorkflowStore', () => { expect(store.getState().controlMode).toBe('pointer') }) - it('should default controlMode to hand when localStorage has no value', () => { + it('should default controlMode to pointer when localStorage has no value', () => { const store = createStore() - expect(store.getState().controlMode).toBe('hand') + expect(store.getState().controlMode).toBe('pointer') }) it('should read panelWidth from localStorage', () => {