mirror of
https://github.com/langgenius/dify.git
synced 2026-05-13 08:57:28 +08:00
fix: resolve import migrations and test failures after segment 3 merge
- Migrate core.model_runtime -> dify_graph.model_runtime across 20+ files - Migrate core.workflow.file -> dify_graph.file across 15+ files - Migrate core.workflow.enums -> dify_graph.enums in service files - Fix SandboxContext phantom import in dify_graph/context/__init__.py - Fix core.app.workflow.node_factory -> core.workflow.node_factory - Fix toast import paths (useToastContext from toast/context) - Fix app-info.tsx import paths for relocated app-operations - Fix 15 frontend test files for API changes, missing QueryClientProvider, i18n key renames, and component behavior changes Made-with: Cursor
This commit is contained in:
parent
94b01f6821
commit
6b75188ddc
@ -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 import abort
|
||||||
from flask_restx import Resource
|
from flask_restx import Resource
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|||||||
@ -37,7 +37,7 @@ from controllers.console.wraps import (
|
|||||||
only_edition_cloud,
|
only_edition_cloud,
|
||||||
setup_required,
|
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 extensions.ext_database import db
|
||||||
from fields.member_fields import Account as AccountResponse
|
from fields.member_fields import Account as AccountResponse
|
||||||
from libs.datetime_utils import naive_utc_now
|
from libs.datetime_utils import naive_utc_now
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import logging
|
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 import request
|
||||||
from flask_restx import Resource, fields
|
from flask_restx import Resource, fields
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|||||||
@ -8,7 +8,7 @@ from core.agent.entities import AgentEntity, AgentLog, AgentResult
|
|||||||
from core.agent.patterns.strategy_factory import StrategyFactory
|
from core.agent.patterns.strategy_factory import StrategyFactory
|
||||||
from core.app.apps.base_app_queue_manager import PublishFrom
|
from core.app.apps.base_app_queue_manager import PublishFrom
|
||||||
from core.app.entities.queue_entities import QueueAgentThoughtEvent, QueueMessageEndEvent, QueueMessageFileEvent
|
from core.app.entities.queue_entities import QueueAgentThoughtEvent, QueueMessageEndEvent, QueueMessageFileEvent
|
||||||
from core.model_runtime.entities import (
|
from dify_graph.model_runtime.entities import (
|
||||||
AssistantPromptMessage,
|
AssistantPromptMessage,
|
||||||
LLMResult,
|
LLMResult,
|
||||||
LLMResultChunk,
|
LLMResultChunk,
|
||||||
@ -19,12 +19,12 @@ from core.model_runtime.entities import (
|
|||||||
TextPromptMessageContent,
|
TextPromptMessageContent,
|
||||||
UserPromptMessage,
|
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.prompt.agent_history_prompt_transform import AgentHistoryPromptTransform
|
||||||
from core.tools.__base.tool import Tool
|
from core.tools.__base.tool import Tool
|
||||||
from core.tools.entities.tool_entities import ToolInvokeMeta
|
from core.tools.entities.tool_entities import ToolInvokeMeta
|
||||||
from core.tools.tool_engine import ToolEngine
|
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
|
from models.model import Message
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@ -11,7 +11,7 @@ from typing import TYPE_CHECKING, Any
|
|||||||
|
|
||||||
from core.agent.entities import AgentLog, AgentResult, ExecutionContext
|
from core.agent.entities import AgentLog, AgentResult, ExecutionContext
|
||||||
from core.model_manager import ModelInstance
|
from core.model_manager import ModelInstance
|
||||||
from core.model_runtime.entities import (
|
from dify_graph.model_runtime.entities import (
|
||||||
AssistantPromptMessage,
|
AssistantPromptMessage,
|
||||||
LLMResult,
|
LLMResult,
|
||||||
LLMResultChunk,
|
LLMResultChunk,
|
||||||
@ -19,10 +19,10 @@ from core.model_runtime.entities import (
|
|||||||
PromptMessage,
|
PromptMessage,
|
||||||
PromptMessageTool,
|
PromptMessageTool,
|
||||||
)
|
)
|
||||||
from core.model_runtime.entities.llm_entities import LLMUsage
|
from dify_graph.model_runtime.entities.llm_entities import LLMUsage
|
||||||
from core.model_runtime.entities.message_entities import TextPromptMessageContent
|
from dify_graph.model_runtime.entities.message_entities import TextPromptMessageContent
|
||||||
from core.tools.entities.tool_entities import ToolInvokeMessage, ToolInvokeMeta
|
from core.tools.entities.tool_entities import ToolInvokeMessage, ToolInvokeMeta
|
||||||
from core.workflow.file import File
|
from dify_graph.file import File
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from core.tools.__base.tool import Tool
|
from core.tools.__base.tool import Tool
|
||||||
|
|||||||
@ -12,7 +12,7 @@ from collections.abc import Generator
|
|||||||
from typing import Any, Union
|
from typing import Any, Union
|
||||||
|
|
||||||
from core.agent.entities import AgentLog, AgentResult
|
from core.agent.entities import AgentLog, AgentResult
|
||||||
from core.model_runtime.entities import (
|
from dify_graph.model_runtime.entities import (
|
||||||
AssistantPromptMessage,
|
AssistantPromptMessage,
|
||||||
LLMResult,
|
LLMResult,
|
||||||
LLMResultChunk,
|
LLMResultChunk,
|
||||||
@ -23,7 +23,7 @@ from core.model_runtime.entities import (
|
|||||||
ToolPromptMessage,
|
ToolPromptMessage,
|
||||||
)
|
)
|
||||||
from core.tools.entities.tool_entities import ToolInvokeMeta
|
from core.tools.entities.tool_entities import ToolInvokeMeta
|
||||||
from core.workflow.file import File
|
from dify_graph.file import File
|
||||||
|
|
||||||
from .base import AgentPattern
|
from .base import AgentPattern
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Any, Union
|
|||||||
from core.agent.entities import AgentLog, AgentResult, AgentScratchpadUnit, ExecutionContext
|
from core.agent.entities import AgentLog, AgentResult, AgentScratchpadUnit, ExecutionContext
|
||||||
from core.agent.output_parser.cot_output_parser import CotAgentOutputParser
|
from core.agent.output_parser.cot_output_parser import CotAgentOutputParser
|
||||||
from core.model_manager import ModelInstance
|
from core.model_manager import ModelInstance
|
||||||
from core.model_runtime.entities import (
|
from dify_graph.model_runtime.entities import (
|
||||||
AssistantPromptMessage,
|
AssistantPromptMessage,
|
||||||
LLMResult,
|
LLMResult,
|
||||||
LLMResultChunk,
|
LLMResultChunk,
|
||||||
@ -17,7 +17,7 @@ from core.model_runtime.entities import (
|
|||||||
PromptMessage,
|
PromptMessage,
|
||||||
SystemPromptMessage,
|
SystemPromptMessage,
|
||||||
)
|
)
|
||||||
from core.workflow.file import File
|
from dify_graph.file import File
|
||||||
|
|
||||||
from .base import AgentPattern, ToolInvokeHook
|
from .base import AgentPattern, ToolInvokeHook
|
||||||
|
|
||||||
@ -204,7 +204,7 @@ class ReActStrategy(AgentPattern):
|
|||||||
tool_names = [tool.name for tool in prompt_tools]
|
tool_names = [tool.name for tool in prompt_tools]
|
||||||
|
|
||||||
# Format tools as JSON for comprehensive information
|
# 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)
|
tools_str = json.dumps(jsonable_encoder(prompt_tools), indent=2)
|
||||||
tool_names_str = ", ".join(f'"{name}"' for name in tool_names)
|
tool_names_str = ", ".join(f'"{name}"' for name in tool_names)
|
||||||
|
|||||||
@ -6,8 +6,8 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
from core.agent.entities import AgentEntity, ExecutionContext
|
from core.agent.entities import AgentEntity, ExecutionContext
|
||||||
from core.model_manager import ModelInstance
|
from core.model_manager import ModelInstance
|
||||||
from core.model_runtime.entities.model_entities import ModelFeature
|
from dify_graph.model_runtime.entities.model_entities import ModelFeature
|
||||||
from core.workflow.file.models import File
|
from dify_graph.file.models import File
|
||||||
|
|
||||||
from .base import AgentPattern, ToolInvokeHook
|
from .base import AgentPattern, ToolInvokeHook
|
||||||
from .function_call import FunctionCallStrategy
|
from .function_call import FunctionCallStrategy
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from core.workflow.graph_engine.layers.base import GraphEngineLayer
|
from dify_graph.graph_engine.layers.base import GraphEngineLayer
|
||||||
from core.workflow.graph_events.base import GraphEngineEvent
|
from dify_graph.graph_events.base import GraphEngineEvent
|
||||||
|
|
||||||
from core.sandbox import Sandbox
|
from core.sandbox import Sandbox
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
|
|
||||||
from core.workflow.variables.types import SegmentType
|
from dify_graph.variables.types import SegmentType
|
||||||
|
|
||||||
|
|
||||||
class SuggestedQuestionsOutput(BaseModel):
|
class SuggestedQuestionsOutput(BaseModel):
|
||||||
|
|||||||
@ -10,8 +10,8 @@ This module provides utilities to:
|
|||||||
from collections.abc import Callable, Mapping, Sequence
|
from collections.abc import Callable, Mapping, Sequence
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from core.workflow.file import File
|
from dify_graph.file import File
|
||||||
from core.workflow.variables.segments import ArrayFileSegment, FileSegment
|
from dify_graph.variables.segments import ArrayFileSegment, FileSegment
|
||||||
|
|
||||||
FILE_PATH_FORMAT = "file-path"
|
FILE_PATH_FORMAT = "file-path"
|
||||||
FILE_PATH_DESCRIPTION_SUFFIX = "this field contains a file path from the Dify sandbox"
|
FILE_PATH_DESCRIPTION_SUFFIX = "this field contains a file path from the Dify sandbox"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
"""Utility functions for LLM generator."""
|
"""Utility functions for LLM generator."""
|
||||||
|
|
||||||
from core.model_runtime.entities.message_entities import (
|
from dify_graph.model_runtime.entities.message_entities import (
|
||||||
AssistantPromptMessage,
|
AssistantPromptMessage,
|
||||||
PromptMessage,
|
PromptMessage,
|
||||||
PromptMessageRole,
|
PromptMessageRole,
|
||||||
|
|||||||
@ -7,7 +7,7 @@ This module defines the common protocol for memory implementations.
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from collections.abc import Sequence
|
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):
|
class BaseMemory(ABC):
|
||||||
@ -49,7 +49,7 @@ class BaseMemory(ABC):
|
|||||||
:param message_limit: Maximum number of messages
|
:param message_limit: Maximum number of messages
|
||||||
:return: Formatted history text
|
:return: Formatted history text
|
||||||
"""
|
"""
|
||||||
from core.model_runtime.entities import (
|
from dify_graph.model_runtime.entities import (
|
||||||
PromptMessageRole,
|
PromptMessageRole,
|
||||||
TextPromptMessageContent,
|
TextPromptMessageContent,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -22,7 +22,7 @@ from sqlalchemy.orm import Session
|
|||||||
|
|
||||||
from core.memory.base import BaseMemory
|
from core.memory.base import BaseMemory
|
||||||
from core.model_manager import ModelInstance
|
from core.model_manager import ModelInstance
|
||||||
from core.model_runtime.entities import (
|
from dify_graph.model_runtime.entities import (
|
||||||
AssistantPromptMessage,
|
AssistantPromptMessage,
|
||||||
MultiModalPromptMessageContent,
|
MultiModalPromptMessageContent,
|
||||||
PromptMessage,
|
PromptMessage,
|
||||||
@ -31,9 +31,9 @@ from core.model_runtime.entities import (
|
|||||||
ToolPromptMessage,
|
ToolPromptMessage,
|
||||||
UserPromptMessage,
|
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.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 extensions.ext_database import db
|
||||||
from models.model import Message
|
from models.model import Message
|
||||||
from models.workflow import WorkflowNodeExecutionModel
|
from models.workflow import WorkflowNodeExecutionModel
|
||||||
|
|||||||
@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any
|
|||||||
|
|
||||||
from pydantic import BaseModel, Field
|
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.session.cli_api import CliApiSession
|
||||||
from core.skill.entities import ToolDependencies, ToolReference
|
from core.skill.entities import ToolDependencies, ToolReference
|
||||||
from core.tools.entities.tool_entities import ToolParameter, ToolProviderType
|
from core.tools.entities.tool_entities import ToolParameter, ToolProviderType
|
||||||
|
|||||||
@ -14,7 +14,7 @@ from core.skill.entities.tool_dependencies import ToolDependencies
|
|||||||
from core.tools.signature import sign_tool_file
|
from core.tools.signature import sign_tool_file
|
||||||
from core.tools.tool_file_manager import ToolFileManager
|
from core.tools.tool_file_manager import ToolFileManager
|
||||||
from core.virtual_environment.__base.helpers import pipeline
|
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 ..bash.dify_cli import DifyCliConfig
|
||||||
from ..entities import DifyCli
|
from ..entities import DifyCli
|
||||||
|
|||||||
@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any
|
|||||||
if TYPE_CHECKING: # pragma: no cover
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
from models.model import File
|
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.__base.tool_runtime import ToolRuntime
|
||||||
from core.tools.entities.tool_entities import (
|
from core.tools.entities.tool_entities import (
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from core.workflow.nodes.base import BaseNodeData
|
from dify_graph.nodes.base import BaseNodeData
|
||||||
|
|
||||||
|
|
||||||
class CommandNodeData(BaseNodeData):
|
class CommandNodeData(BaseNodeData):
|
||||||
|
|||||||
@ -2,17 +2,17 @@ import logging
|
|||||||
from collections.abc import Mapping, Sequence
|
from collections.abc import Mapping, Sequence
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from core.workflow.enums import NodeType, WorkflowNodeExecutionStatus
|
from dify_graph.enums import NodeType, WorkflowNodeExecutionStatus
|
||||||
from core.workflow.nodes.base.entities import VariableSelector
|
from dify_graph.nodes.base.entities import VariableSelector
|
||||||
from core.workflow.nodes.base.node import Node
|
from dify_graph.nodes.base.node import Node
|
||||||
from core.workflow.nodes.base.variable_template_parser import VariableTemplateParser
|
from dify_graph.nodes.base.variable_template_parser import VariableTemplateParser
|
||||||
|
|
||||||
from core.sandbox import sandbox_debug
|
from core.sandbox import sandbox_debug
|
||||||
from core.sandbox.bash.session import SANDBOX_READY_TIMEOUT
|
from core.sandbox.bash.session import SANDBOX_READY_TIMEOUT
|
||||||
from core.virtual_environment.__base.command_future import CommandCancelledError, CommandTimeoutError
|
from core.virtual_environment.__base.command_future import CommandCancelledError, CommandTimeoutError
|
||||||
from core.virtual_environment.__base.helpers import submit_command, with_connection
|
from core.virtual_environment.__base.helpers import submit_command, with_connection
|
||||||
from core.workflow.node_events import NodeRunResult
|
from dify_graph.node_events import NodeRunResult
|
||||||
from core.workflow.nodes.base import variable_template_parser
|
from dify_graph.nodes.base import variable_template_parser
|
||||||
from core.workflow.nodes.command.entities import CommandNodeData
|
from core.workflow.nodes.command.entities import CommandNodeData
|
||||||
from core.workflow.nodes.command.exc import CommandExecutionError
|
from core.workflow.nodes.command.exc import CommandExecutionError
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
|
|
||||||
from core.workflow.nodes.base import BaseNodeData
|
from dify_graph.nodes.base import BaseNodeData
|
||||||
|
|
||||||
|
|
||||||
class FileUploadNodeData(BaseNodeData):
|
class FileUploadNodeData(BaseNodeData):
|
||||||
|
|||||||
@ -5,16 +5,16 @@ from collections.abc import Mapping, Sequence
|
|||||||
from pathlib import PurePosixPath
|
from pathlib import PurePosixPath
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from core.workflow.enums import NodeType, WorkflowNodeExecutionStatus
|
from dify_graph.enums import NodeType, WorkflowNodeExecutionStatus
|
||||||
from core.workflow.nodes.base.node import Node
|
from dify_graph.nodes.base.node import Node
|
||||||
from core.workflow.variables.segments import ArrayStringSegment, FileSegment
|
from dify_graph.variables.segments import ArrayStringSegment, FileSegment
|
||||||
|
|
||||||
from core.sandbox.bash.session import SANDBOX_READY_TIMEOUT
|
from core.sandbox.bash.session import SANDBOX_READY_TIMEOUT
|
||||||
from core.virtual_environment.__base.command_future import CommandCancelledError, CommandTimeoutError
|
from core.virtual_environment.__base.command_future import CommandCancelledError, CommandTimeoutError
|
||||||
from core.virtual_environment.__base.helpers import pipeline
|
from core.virtual_environment.__base.helpers import pipeline
|
||||||
from core.workflow.file import File, FileTransferMethod
|
from dify_graph.file import File, FileTransferMethod
|
||||||
from core.workflow.node_events import NodeRunResult
|
from dify_graph.node_events import NodeRunResult
|
||||||
from core.workflow.variables import ArrayFileSegment
|
from dify_graph.variables import ArrayFileSegment
|
||||||
from core.zip_sandbox import SandboxDownloadItem
|
from core.zip_sandbox import SandboxDownloadItem
|
||||||
|
|
||||||
from .entities import FileUploadNodeData
|
from .entities import FileUploadNodeData
|
||||||
|
|||||||
@ -17,15 +17,12 @@ from dify_graph.context.execution_context import (
|
|||||||
register_context_capturer,
|
register_context_capturer,
|
||||||
reset_context_provider,
|
reset_context_provider,
|
||||||
)
|
)
|
||||||
from dify_graph.context.models import SandboxContext
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AppContext",
|
"AppContext",
|
||||||
"ContextProviderNotFoundError",
|
"ContextProviderNotFoundError",
|
||||||
"ExecutionContext",
|
"ExecutionContext",
|
||||||
"IExecutionContext",
|
"IExecutionContext",
|
||||||
"NullAppContext",
|
"NullAppContext",
|
||||||
"SandboxContext",
|
|
||||||
"capture_current_context",
|
"capture_current_context",
|
||||||
"read_context",
|
"read_context",
|
||||||
"register_context",
|
"register_context",
|
||||||
|
|||||||
@ -517,7 +517,7 @@ class AgentNode(Node[AgentNodeData]):
|
|||||||
Fetch memory instance for saving node memory.
|
Fetch memory instance for saving node memory.
|
||||||
This is a simplified version that doesn't require model_instance.
|
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
|
from core.model_manager import ModelManager
|
||||||
|
|
||||||
|
|||||||
@ -378,7 +378,7 @@ class Node(Generic[NodeDataT]):
|
|||||||
Nested nodes are nodes with parent_node_id == self._node_id.
|
Nested nodes are nodes with parent_node_id == self._node_id.
|
||||||
They are executed before the main node to extract values from list[PromptMessage].
|
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()
|
extractor_configs = self._find_extractor_node_configs()
|
||||||
logger.debug("[NestedNode] Found %d nested nodes for parent '%s'", len(extractor_configs), self._node_id)
|
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
|
@_dispatch.register
|
||||||
def _(self, event: StreamChunkEvent) -> NodeRunStreamChunkEvent:
|
def _(self, event: StreamChunkEvent) -> NodeRunStreamChunkEvent:
|
||||||
from core.workflow.graph_events import ChunkType
|
from dify_graph.graph_events import ChunkType
|
||||||
|
|
||||||
return NodeRunStreamChunkEvent(
|
return NodeRunStreamChunkEvent(
|
||||||
id=self.execution_id,
|
id=self.execution_id,
|
||||||
@ -711,7 +711,7 @@ class Node(Generic[NodeDataT]):
|
|||||||
|
|
||||||
@_dispatch.register
|
@_dispatch.register
|
||||||
def _(self, event: ToolCallChunkEvent) -> NodeRunStreamChunkEvent:
|
def _(self, event: ToolCallChunkEvent) -> NodeRunStreamChunkEvent:
|
||||||
from core.workflow.graph_events import ChunkType
|
from dify_graph.graph_events import ChunkType
|
||||||
|
|
||||||
return NodeRunStreamChunkEvent(
|
return NodeRunStreamChunkEvent(
|
||||||
id=self._node_execution_id,
|
id=self._node_execution_id,
|
||||||
@ -726,8 +726,8 @@ class Node(Generic[NodeDataT]):
|
|||||||
|
|
||||||
@_dispatch.register
|
@_dispatch.register
|
||||||
def _(self, event: ToolResultChunkEvent) -> NodeRunStreamChunkEvent:
|
def _(self, event: ToolResultChunkEvent) -> NodeRunStreamChunkEvent:
|
||||||
from core.workflow.entities import ToolResult, ToolResultStatus
|
from dify_graph.entities import ToolResult, ToolResultStatus
|
||||||
from core.workflow.graph_events import ChunkType
|
from dify_graph.graph_events import ChunkType
|
||||||
|
|
||||||
tool_result = event.tool_result or ToolResult()
|
tool_result = event.tool_result or ToolResult()
|
||||||
status: ToolResultStatus = tool_result.status or ToolResultStatus.SUCCESS
|
status: ToolResultStatus = tool_result.status or ToolResultStatus.SUCCESS
|
||||||
@ -748,7 +748,7 @@ class Node(Generic[NodeDataT]):
|
|||||||
|
|
||||||
@_dispatch.register
|
@_dispatch.register
|
||||||
def _(self, event: ThoughtChunkEvent) -> NodeRunStreamChunkEvent:
|
def _(self, event: ThoughtChunkEvent) -> NodeRunStreamChunkEvent:
|
||||||
from core.workflow.graph_events import ChunkType
|
from dify_graph.graph_events import ChunkType
|
||||||
|
|
||||||
return NodeRunStreamChunkEvent(
|
return NodeRunStreamChunkEvent(
|
||||||
id=self._node_execution_id,
|
id=self._node_execution_id,
|
||||||
|
|||||||
@ -199,7 +199,7 @@ def _build_messages_from_trace(
|
|||||||
assistant_response: str,
|
assistant_response: str,
|
||||||
file_suffix: str = "",
|
file_suffix: str = "",
|
||||||
) -> list[PromptMessage]:
|
) -> list[PromptMessage]:
|
||||||
from core.workflow.nodes.llm.entities import ModelTraceSegment, ToolTraceSegment
|
from dify_graph.nodes.llm.entities import ModelTraceSegment, ToolTraceSegment
|
||||||
|
|
||||||
messages: list[PromptMessage] = []
|
messages: list[PromptMessage] = []
|
||||||
covered_text_len = 0
|
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]:
|
def restore_multimodal_content_in_messages(messages: Sequence[PromptMessage]) -> list[PromptMessage]:
|
||||||
from core.workflow.file import file_manager
|
return [_restore_message_content(msg) for msg in messages]
|
||||||
|
|
||||||
return [_restore_message_content(msg, file_manager) 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
|
content = message.content
|
||||||
if content is None or isinstance(content, str):
|
if content is None or isinstance(content, str):
|
||||||
return message
|
return message
|
||||||
@ -279,7 +279,7 @@ def _restore_message_content(message: PromptMessage, file_manager) -> PromptMess
|
|||||||
restored_content: list[PromptMessageContentUnionTypes] = []
|
restored_content: list[PromptMessageContentUnionTypes] = []
|
||||||
for item in content:
|
for item in content:
|
||||||
if isinstance(item, MultiModalPromptMessageContent):
|
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))
|
restored_content.append(cast(PromptMessageContentUnionTypes, restored_item))
|
||||||
else:
|
else:
|
||||||
restored_content.append(item)
|
restored_content.append(item)
|
||||||
|
|||||||
@ -2201,7 +2201,7 @@ class LLMNode(Node[LLMNodeData]):
|
|||||||
|
|
||||||
def _extract_prompt_files(self, variable_pool: VariablePool) -> list[File]:
|
def _extract_prompt_files(self, variable_pool: VariablePool) -> list[File]:
|
||||||
"""Extract files from prompt template variables."""
|
"""Extract files from prompt template variables."""
|
||||||
from core.workflow.variables import ArrayFileVariable, FileVariable
|
from dify_graph.variables import ArrayFileVariable, FileVariable
|
||||||
|
|
||||||
files: list[File] = []
|
files: list[File] = []
|
||||||
|
|
||||||
|
|||||||
@ -568,7 +568,7 @@ class ToolNode(Node[ToolNodeData]):
|
|||||||
:param parent_node_id: the parent node id to find nested nodes for
|
:param parent_node_id: the parent node id to find nested nodes for
|
||||||
:return: mapping of variable key to variable selector
|
: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]] = {}
|
result: dict[str, Sequence[str]] = {}
|
||||||
nodes = graph_config.get("nodes", [])
|
nodes = graph_config.get("nodes", [])
|
||||||
|
|||||||
@ -3,7 +3,7 @@ from typing import Any
|
|||||||
|
|
||||||
import orjson
|
import orjson
|
||||||
|
|
||||||
from core.model_runtime.entities import PromptMessage
|
from dify_graph.model_runtime.entities import PromptMessage
|
||||||
|
|
||||||
from .segment_group import SegmentGroup
|
from .segment_group import SegmentGroup
|
||||||
from .segments import ArrayFileSegment, ArrayPromptMessageSegment, FileSegment, Segment
|
from .segments import ArrayFileSegment, ArrayPromptMessageSegment, FileSegment, Segment
|
||||||
|
|||||||
@ -18,7 +18,7 @@ from collections.abc import Mapping
|
|||||||
from functools import reduce
|
from functools import reduce
|
||||||
from typing import Any, cast
|
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.app.entities.app_asset_entities import AppAssetFileTree, AppAssetNode
|
||||||
from core.sandbox.entities.config import AppAssets
|
from core.sandbox.entities.config import AppAssets
|
||||||
|
|||||||
@ -7,10 +7,10 @@ extracting values from list[PromptMessage] variables.
|
|||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from core.workflow.enums import NodeType
|
from dify_graph.enums import NodeType
|
||||||
from sqlalchemy.orm import Session
|
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.model_provider_service import ModelProviderService
|
||||||
from services.workflow.entities import NestedNodeGraphRequest, NestedNodeGraphResponse, NestedNodeParameterSchema
|
from services.workflow.entities import NestedNodeGraphRequest, NestedNodeGraphResponse, NestedNodeParameterSchema
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ from decimal import Decimal
|
|||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
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.entities import AgentLog, ExecutionContext
|
||||||
from core.agent.patterns.base import AgentPattern
|
from core.agent.patterns.base import AgentPattern
|
||||||
|
|||||||
@ -4,8 +4,8 @@ from decimal import Decimal
|
|||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from core.model_runtime.entities.llm_entities import LLMUsage
|
from dify_graph.model_runtime.entities.llm_entities import LLMUsage
|
||||||
from core.model_runtime.entities.message_entities import (
|
from dify_graph.model_runtime.entities.message_entities import (
|
||||||
PromptMessageTool,
|
PromptMessageTool,
|
||||||
SystemPromptMessage,
|
SystemPromptMessage,
|
||||||
UserPromptMessage,
|
UserPromptMessage,
|
||||||
@ -312,7 +312,7 @@ class TestPromptMessageHandling:
|
|||||||
|
|
||||||
def test_assistant_message_with_tool_calls(self, mock_model_instance, mock_context, mock_tool):
|
def test_assistant_message_with_tool_calls(self, mock_model_instance, mock_context, mock_tool):
|
||||||
"""Test that assistant messages can contain tool calls."""
|
"""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(
|
tool_call = AssistantPromptMessage.ToolCall(
|
||||||
id="call_123",
|
id="call_123",
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import pytest
|
|||||||
|
|
||||||
from core.agent.entities import ExecutionContext
|
from core.agent.entities import ExecutionContext
|
||||||
from core.agent.patterns.react import ReActStrategy
|
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
|
@pytest.fixture
|
||||||
@ -33,7 +33,7 @@ def mock_context():
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_tool():
|
def mock_tool():
|
||||||
"""Create a 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 = MagicMock()
|
||||||
tool.entity.identity.name = "test_tool"
|
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):
|
def test_scratchpad_appended_as_assistant_message(self, mock_model_instance, mock_context):
|
||||||
"""Test that agent scratchpad is appended as AssistantPromptMessage."""
|
"""Test that agent scratchpad is appended as AssistantPromptMessage."""
|
||||||
from core.agent.entities import AgentScratchpadUnit
|
from core.agent.entities import AgentScratchpadUnit
|
||||||
from core.model_runtime.entities import AssistantPromptMessage
|
from dify_graph.model_runtime.entities import AssistantPromptMessage
|
||||||
|
|
||||||
strategy = ReActStrategy(
|
strategy = ReActStrategy(
|
||||||
model_instance=mock_model_instance,
|
model_instance=mock_model_instance,
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
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.entities import AgentEntity, ExecutionContext
|
||||||
from core.agent.patterns.function_call import FunctionCallStrategy
|
from core.agent.patterns.function_call import FunctionCallStrategy
|
||||||
|
|||||||
@ -4,10 +4,10 @@ from decimal import Decimal
|
|||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
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.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:
|
class TestOrganizePromptMessages:
|
||||||
@ -184,7 +184,7 @@ class TestClearUserPromptImageMessages:
|
|||||||
|
|
||||||
def test_original_messages_not_modified(self, mock_runner):
|
def test_original_messages_not_modified(self, mock_runner):
|
||||||
"""Test that original messages are not modified (deep copy)."""
|
"""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,
|
ImagePromptMessageContent,
|
||||||
TextPromptMessageContent,
|
TextPromptMessageContent,
|
||||||
)
|
)
|
||||||
@ -365,13 +365,13 @@ class TestOrganizeUserQuery:
|
|||||||
|
|
||||||
def test_query_with_files(self, mock_runner):
|
def test_query_with_files(self, mock_runner):
|
||||||
"""Test organizing a query with files."""
|
"""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_file = MagicMock(spec=File)
|
||||||
mock_runner.files = [mock_file]
|
mock_runner.files = [mock_file]
|
||||||
|
|
||||||
with patch("core.agent.agent_app_runner.file_manager") as mock_fm:
|
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(
|
mock_fm.to_prompt_message_content.return_value = ImagePromptMessageContent(
|
||||||
data="http://example.com/image.jpg",
|
data="http://example.com/image.jpg",
|
||||||
|
|||||||
@ -2,8 +2,8 @@ from unittest.mock import MagicMock
|
|||||||
|
|
||||||
from core.app.apps.base_app_queue_manager import PublishFrom
|
from core.app.apps.base_app_queue_manager import PublishFrom
|
||||||
from core.app.apps.workflow_app_runner import WorkflowBasedAppRunner
|
from core.app.apps.workflow_app_runner import WorkflowBasedAppRunner
|
||||||
from core.workflow.graph_events import NodeRunStreamChunkEvent
|
from dify_graph.graph_events import NodeRunStreamChunkEvent
|
||||||
from core.workflow.nodes import NodeType
|
from dify_graph.enums import NodeType
|
||||||
|
|
||||||
|
|
||||||
class DummyQueueManager:
|
class DummyQueueManager:
|
||||||
|
|||||||
@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from core.model_runtime.entities.message_entities import ImagePromptMessageContent
|
from dify_graph.model_runtime.entities.message_entities import ImagePromptMessageContent
|
||||||
from core.workflow.file.file_manager import (
|
from dify_graph.file.file_manager import (
|
||||||
_encode_file_ref,
|
_encode_file_ref,
|
||||||
restore_multimodal_content,
|
restore_multimodal_content,
|
||||||
to_prompt_message_content,
|
to_prompt_message_content,
|
||||||
)
|
)
|
||||||
|
|
||||||
from core.workflow.file import File, FileTransferMethod, FileType
|
from dify_graph.file import File, FileTransferMethod, FileType
|
||||||
|
|
||||||
|
|
||||||
class TestEncodeFileRef:
|
class TestEncodeFileRef:
|
||||||
@ -52,8 +52,8 @@ class TestEncodeFileRef:
|
|||||||
class TestToPromptMessageContent:
|
class TestToPromptMessageContent:
|
||||||
"""Tests for to_prompt_message_content function with file_ref field."""
|
"""Tests for to_prompt_message_content function with file_ref field."""
|
||||||
|
|
||||||
@patch("core.workflow.file.file_manager.dify_config")
|
@patch("dify_graph.file.file_manager.dify_config")
|
||||||
@patch("core.workflow.file.file_manager._get_encoded_string")
|
@patch("dify_graph.file.file_manager._get_encoded_string")
|
||||||
def test_includes_file_ref(self, mock_get_encoded, mock_config):
|
def test_includes_file_ref(self, mock_get_encoded, mock_config):
|
||||||
"""Generated content should include file_ref field."""
|
"""Generated content should include file_ref field."""
|
||||||
mock_config.MULTIMODAL_SEND_FORMAT = "base64"
|
mock_config.MULTIMODAL_SEND_FORMAT = "base64"
|
||||||
@ -121,9 +121,9 @@ class TestRestoreMultimodalContent:
|
|||||||
|
|
||||||
assert result.url == "https://example.com/image.png"
|
assert result.url == "https://example.com/image.png"
|
||||||
|
|
||||||
@patch("core.workflow.file.file_manager.dify_config")
|
@patch("dify_graph.file.file_manager.dify_config")
|
||||||
@patch("core.workflow.file.file_manager._build_file_from_ref")
|
@patch("dify_graph.file.file_manager._build_file_from_ref")
|
||||||
@patch("core.workflow.file.file_manager._to_url")
|
@patch("dify_graph.file.file_manager._to_url")
|
||||||
def test_restores_url_from_file_ref(self, mock_to_url, mock_build_file, mock_config):
|
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)."""
|
"""Content should be restored from file_ref when url is empty (url mode)."""
|
||||||
mock_config.MULTIMODAL_SEND_FORMAT = "url"
|
mock_config.MULTIMODAL_SEND_FORMAT = "url"
|
||||||
@ -144,9 +144,9 @@ class TestRestoreMultimodalContent:
|
|||||||
assert result.url == "https://restored-url.com/image.png"
|
assert result.url == "https://restored-url.com/image.png"
|
||||||
mock_build_file.assert_called_once()
|
mock_build_file.assert_called_once()
|
||||||
|
|
||||||
@patch("core.workflow.file.file_manager.dify_config")
|
@patch("dify_graph.file.file_manager.dify_config")
|
||||||
@patch("core.workflow.file.file_manager._build_file_from_ref")
|
@patch("dify_graph.file.file_manager._build_file_from_ref")
|
||||||
@patch("core.workflow.file.file_manager._get_encoded_string")
|
@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):
|
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."""
|
"""Content should be restored as base64 when in base64 mode."""
|
||||||
mock_config.MULTIMODAL_SEND_FORMAT = "base64"
|
mock_config.MULTIMODAL_SEND_FORMAT = "base64"
|
||||||
|
|||||||
@ -3,7 +3,7 @@ Unit tests for sandbox file path detection and conversion.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
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 (
|
from core.llm_generator.output_parser.file_ref import (
|
||||||
FILE_PATH_DESCRIPTION_SUFFIX,
|
FILE_PATH_DESCRIPTION_SUFFIX,
|
||||||
@ -13,7 +13,7 @@ from core.llm_generator.output_parser.file_ref import (
|
|||||||
detect_file_path_fields,
|
detect_file_path_fields,
|
||||||
is_file_path_property,
|
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:
|
def _build_file(file_id: str) -> File:
|
||||||
|
|||||||
@ -2,21 +2,21 @@
|
|||||||
|
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from core.workflow.entities.tool_entities import ToolResultStatus
|
from dify_graph.entities.tool_entities import ToolResultStatus
|
||||||
from core.workflow.enums import NodeType
|
from dify_graph.enums import NodeType
|
||||||
from core.workflow.graph.graph import Graph
|
from dify_graph.graph.graph import Graph
|
||||||
from core.workflow.graph_engine.response_coordinator.coordinator import ResponseStreamCoordinator
|
from dify_graph.graph_engine.response_coordinator.coordinator import ResponseStreamCoordinator
|
||||||
from core.workflow.graph_engine.response_coordinator.session import ResponseSession
|
from dify_graph.graph_engine.response_coordinator.session import ResponseSession
|
||||||
from core.workflow.nodes.base.entities import BaseNodeData
|
from dify_graph.nodes.base.entities import BaseNodeData
|
||||||
from core.workflow.nodes.base.template import Template, VariableSegment
|
from dify_graph.nodes.base.template import Template, VariableSegment
|
||||||
|
|
||||||
from core.workflow.graph_events import (
|
from dify_graph.graph_events import (
|
||||||
ChunkType,
|
ChunkType,
|
||||||
NodeRunStreamChunkEvent,
|
NodeRunStreamChunkEvent,
|
||||||
ToolCall,
|
ToolCall,
|
||||||
ToolResult,
|
ToolResult,
|
||||||
)
|
)
|
||||||
from core.workflow.runtime import VariablePool
|
from dify_graph.runtime import VariablePool
|
||||||
|
|
||||||
|
|
||||||
class TestResponseCoordinatorObjectStreaming:
|
class TestResponseCoordinatorObjectStreaming:
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"""Tests for StreamChunkEvent and its subclasses."""
|
"""Tests for StreamChunkEvent and its subclasses."""
|
||||||
|
|
||||||
from core.workflow.entities import ToolCall, ToolResult, ToolResultStatus
|
from dify_graph.entities import ToolCall, ToolResult, ToolResultStatus
|
||||||
from core.workflow.node_events import (
|
from dify_graph.node_events import (
|
||||||
ChunkType,
|
ChunkType,
|
||||||
StreamChunkEvent,
|
StreamChunkEvent,
|
||||||
ThoughtChunkEvent,
|
ThoughtChunkEvent,
|
||||||
|
|||||||
@ -4,8 +4,8 @@ from io import BytesIO
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from core.workflow.enums import WorkflowNodeExecutionStatus
|
from dify_graph.enums import WorkflowNodeExecutionStatus
|
||||||
from core.workflow.system_variable import SystemVariable
|
from dify_graph.system_variable import SystemVariable
|
||||||
|
|
||||||
from core.entities.provider_entities import BasicProviderConfig
|
from core.entities.provider_entities import BasicProviderConfig
|
||||||
from core.virtual_environment.__base.entities import (
|
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.__base.virtual_environment import VirtualEnvironment
|
||||||
from core.virtual_environment.channel.queue_transport import QueueTransportReadCloser
|
from core.virtual_environment.channel.queue_transport import QueueTransportReadCloser
|
||||||
from core.virtual_environment.channel.transport import NopTransportWriteCloser
|
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.nodes.command.node import CommandNode
|
||||||
from core.workflow.runtime import GraphRuntimeState, VariablePool
|
from dify_graph.runtime import GraphRuntimeState, VariablePool
|
||||||
|
|
||||||
|
|
||||||
class FakeVirtualEnvironment(VirtualEnvironment):
|
class FakeVirtualEnvironment(VirtualEnvironment):
|
||||||
@ -138,14 +139,18 @@ def _make_node(
|
|||||||
variable_pool = VariablePool(system_variables=system_variables, user_inputs={})
|
variable_pool = VariablePool(system_variables=system_variables, user_inputs={})
|
||||||
runtime_state = GraphRuntimeState(variable_pool=variable_pool, start_at=time.perf_counter())
|
runtime_state = GraphRuntimeState(variable_pool=variable_pool, start_at=time.perf_counter())
|
||||||
init_params = GraphInitParams(
|
init_params = GraphInitParams(
|
||||||
tenant_id="t",
|
|
||||||
app_id="a",
|
|
||||||
workflow_id="w",
|
workflow_id="w",
|
||||||
graph_config={},
|
graph_config={},
|
||||||
user_id="u",
|
|
||||||
user_from="account",
|
|
||||||
invoke_from="debugger",
|
|
||||||
call_depth=0,
|
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:
|
if vm is not None:
|
||||||
|
|||||||
@ -3,12 +3,12 @@
|
|||||||
import string
|
import string
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from core.model_runtime.entities.message_entities import (
|
from dify_graph.model_runtime.entities.message_entities import (
|
||||||
ImagePromptMessageContent,
|
ImagePromptMessageContent,
|
||||||
TextPromptMessageContent,
|
TextPromptMessageContent,
|
||||||
UserPromptMessage,
|
UserPromptMessage,
|
||||||
)
|
)
|
||||||
from core.workflow.nodes.llm.llm_utils import (
|
from dify_graph.nodes.llm.llm_utils import (
|
||||||
_truncate_multimodal_content,
|
_truncate_multimodal_content,
|
||||||
build_context,
|
build_context,
|
||||||
restore_multimodal_content_in_messages,
|
restore_multimodal_content_in_messages,
|
||||||
@ -100,7 +100,7 @@ class TestBuildContext:
|
|||||||
|
|
||||||
def test_excludes_system_messages(self):
|
def test_excludes_system_messages(self):
|
||||||
"""System messages should be excluded from context."""
|
"""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 = [
|
messages = [
|
||||||
SystemPromptMessage(content="You are a helpful assistant."),
|
SystemPromptMessage(content="You are a helpful assistant."),
|
||||||
@ -125,12 +125,12 @@ class TestBuildContext:
|
|||||||
|
|
||||||
def test_builds_context_with_tool_calls_from_generation_data(self):
|
def test_builds_context_with_tool_calls_from_generation_data(self):
|
||||||
"""Should reconstruct full conversation including tool calls when generation_data is provided."""
|
"""Should reconstruct full conversation including tool calls when generation_data is provided."""
|
||||||
from core.model_runtime.entities.llm_entities import LLMUsage
|
from dify_graph.model_runtime.entities.llm_entities import LLMUsage
|
||||||
from core.model_runtime.entities.message_entities import (
|
from dify_graph.model_runtime.entities.message_entities import (
|
||||||
AssistantPromptMessage,
|
AssistantPromptMessage,
|
||||||
ToolPromptMessage,
|
ToolPromptMessage,
|
||||||
)
|
)
|
||||||
from core.workflow.nodes.llm.entities import (
|
from dify_graph.nodes.llm.entities import (
|
||||||
LLMGenerationData,
|
LLMGenerationData,
|
||||||
LLMTraceSegment,
|
LLMTraceSegment,
|
||||||
ModelTraceSegment,
|
ModelTraceSegment,
|
||||||
@ -199,12 +199,12 @@ class TestBuildContext:
|
|||||||
|
|
||||||
def test_builds_context_with_multiple_tool_calls(self):
|
def test_builds_context_with_multiple_tool_calls(self):
|
||||||
"""Should handle multiple tool calls in a single conversation."""
|
"""Should handle multiple tool calls in a single conversation."""
|
||||||
from core.model_runtime.entities.llm_entities import LLMUsage
|
from dify_graph.model_runtime.entities.llm_entities import LLMUsage
|
||||||
from core.model_runtime.entities.message_entities import (
|
from dify_graph.model_runtime.entities.message_entities import (
|
||||||
AssistantPromptMessage,
|
AssistantPromptMessage,
|
||||||
ToolPromptMessage,
|
ToolPromptMessage,
|
||||||
)
|
)
|
||||||
from core.workflow.nodes.llm.entities import (
|
from dify_graph.nodes.llm.entities import (
|
||||||
LLMGenerationData,
|
LLMGenerationData,
|
||||||
LLMTraceSegment,
|
LLMTraceSegment,
|
||||||
ModelTraceSegment,
|
ModelTraceSegment,
|
||||||
@ -291,8 +291,8 @@ class TestBuildContext:
|
|||||||
|
|
||||||
def test_builds_context_with_empty_trace(self):
|
def test_builds_context_with_empty_trace(self):
|
||||||
"""Should fallback to simple context when trace is empty."""
|
"""Should fallback to simple context when trace is empty."""
|
||||||
from core.model_runtime.entities.llm_entities import LLMUsage
|
from dify_graph.model_runtime.entities.llm_entities import LLMUsage
|
||||||
from core.workflow.nodes.llm.entities import LLMGenerationData
|
from dify_graph.nodes.llm.entities import LLMGenerationData
|
||||||
|
|
||||||
messages = [UserPromptMessage(content="Hello!")]
|
messages = [UserPromptMessage(content="Hello!")]
|
||||||
|
|
||||||
@ -318,7 +318,7 @@ class TestBuildContext:
|
|||||||
class TestRestoreMultimodalContentInMessages:
|
class TestRestoreMultimodalContentInMessages:
|
||||||
"""Tests for restore_multimodal_content_in_messages function."""
|
"""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):
|
def test_restores_multimodal_content(self, mock_restore):
|
||||||
"""Should restore multimodal content in messages."""
|
"""Should restore multimodal content in messages."""
|
||||||
# Setup mock
|
# Setup mock
|
||||||
|
|||||||
@ -3,12 +3,12 @@ from collections.abc import Generator
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from core.model_runtime.entities.llm_entities import LLMUsage
|
from dify_graph.model_runtime.entities.llm_entities import LLMUsage
|
||||||
from core.workflow.entities.tool_entities import ToolResultStatus
|
from dify_graph.entities.tool_entities import ToolResultStatus
|
||||||
from core.workflow.nodes.llm.node import LLMNode
|
from dify_graph.nodes.llm.node import LLMNode
|
||||||
|
|
||||||
from core.workflow.entities import ToolCallResult
|
from dify_graph.entities import ToolCallResult
|
||||||
from core.workflow.node_events import ModelInvokeCompletedEvent, NodeEventBase
|
from dify_graph.node_events import ModelInvokeCompletedEvent, NodeEventBase
|
||||||
|
|
||||||
|
|
||||||
class _StubModelInstance:
|
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():
|
def test_serialize_tool_call_strips_files_to_ids():
|
||||||
file_cls = pytest.importorskip("core.workflow.file").File
|
file_cls = pytest.importorskip("dify_graph.file").File
|
||||||
file_type = pytest.importorskip("core.workflow.file.enums").FileType
|
file_type = pytest.importorskip("dify_graph.file.enums").FileType
|
||||||
transfer_method = pytest.importorskip("core.workflow.file.enums").FileTransferMethod
|
transfer_method = pytest.importorskip("dify_graph.file.enums").FileTransferMethod
|
||||||
|
|
||||||
file_with_id = file_cls(
|
file_with_id = file_cls(
|
||||||
id="f1",
|
id="f1",
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
import type { AppListResponse } from '@/models/app'
|
import type { AppListResponse } from '@/models/app'
|
||||||
import type { App } from '@/types/app'
|
import type { App } from '@/types/app'
|
||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||||
import { fireEvent, screen } from '@testing-library/react'
|
import { fireEvent, screen } from '@testing-library/react'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import List from '@/app/components/apps/list'
|
import List from '@/app/components/apps/list'
|
||||||
@ -165,9 +166,15 @@ const createPage = (apps: App[], hasMore = false, page = 1): AppListResponse =>
|
|||||||
total: apps.length,
|
total: apps.length,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const queryClient = new QueryClient({
|
||||||
|
defaultOptions: { queries: { retry: false } },
|
||||||
|
})
|
||||||
|
|
||||||
const renderList = (searchParams?: Record<string, string>) => {
|
const renderList = (searchParams?: Record<string, string>) => {
|
||||||
return renderWithNuqs(
|
return renderWithNuqs(
|
||||||
<List controlRefreshList={0} />,
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<List controlRefreshList={0} />
|
||||||
|
</QueryClientProvider>,
|
||||||
{ searchParams },
|
{ searchParams },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -213,7 +220,9 @@ describe('App List Browsing Flow', () => {
|
|||||||
|
|
||||||
it('should transition from loading to content when data loads', () => {
|
it('should transition from loading to content when data loads', () => {
|
||||||
mockIsLoading = true
|
mockIsLoading = true
|
||||||
const { rerender } = renderWithNuqs(<List controlRefreshList={0} />)
|
const { rerender } = renderWithNuqs(
|
||||||
|
<QueryClientProvider client={queryClient}><List controlRefreshList={0} /></QueryClientProvider>,
|
||||||
|
)
|
||||||
|
|
||||||
const skeletonCards = document.querySelectorAll('.animate-pulse')
|
const skeletonCards = document.querySelectorAll('.animate-pulse')
|
||||||
expect(skeletonCards.length).toBeGreaterThan(0)
|
expect(skeletonCards.length).toBeGreaterThan(0)
|
||||||
@ -224,7 +233,9 @@ describe('App List Browsing Flow', () => {
|
|||||||
createMockApp({ id: 'app-1', name: 'Loaded App' }),
|
createMockApp({ id: 'app-1', name: 'Loaded App' }),
|
||||||
])]
|
])]
|
||||||
|
|
||||||
rerender(<List controlRefreshList={0} />)
|
rerender(
|
||||||
|
<QueryClientProvider client={queryClient}><List controlRefreshList={0} /></QueryClientProvider>,
|
||||||
|
)
|
||||||
|
|
||||||
expect(screen.getByText('Loaded App')).toBeInTheDocument()
|
expect(screen.getByText('Loaded App')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
@ -420,9 +431,13 @@ describe('App List Browsing Flow', () => {
|
|||||||
it('should call refetch when controlRefreshList increments', () => {
|
it('should call refetch when controlRefreshList increments', () => {
|
||||||
mockPages = [createPage([createMockApp()])]
|
mockPages = [createPage([createMockApp()])]
|
||||||
|
|
||||||
const { rerender } = renderWithNuqs(<List controlRefreshList={0} />)
|
const { rerender } = renderWithNuqs(
|
||||||
|
<QueryClientProvider client={queryClient}><List controlRefreshList={0} /></QueryClientProvider>,
|
||||||
|
)
|
||||||
|
|
||||||
rerender(<List controlRefreshList={1} />)
|
rerender(
|
||||||
|
<QueryClientProvider client={queryClient}><List controlRefreshList={1} /></QueryClientProvider>,
|
||||||
|
)
|
||||||
|
|
||||||
expect(mockRefetch).toHaveBeenCalled()
|
expect(mockRefetch).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
import type { AppListResponse } from '@/models/app'
|
import type { AppListResponse } from '@/models/app'
|
||||||
import type { App } from '@/types/app'
|
import type { App } from '@/types/app'
|
||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import List from '@/app/components/apps/list'
|
import List from '@/app/components/apps/list'
|
||||||
@ -218,8 +219,16 @@ const createPage = (apps: App[]): AppListResponse => ({
|
|||||||
total: apps.length,
|
total: apps.length,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const queryClient = new QueryClient({
|
||||||
|
defaultOptions: { queries: { retry: false } },
|
||||||
|
})
|
||||||
|
|
||||||
const renderList = () => {
|
const renderList = () => {
|
||||||
return renderWithNuqs(<List controlRefreshList={0} />)
|
return renderWithNuqs(
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<List controlRefreshList={0} />
|
||||||
|
</QueryClientProvider>,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Create App Flow', () => {
|
describe('Create App Flow', () => {
|
||||||
@ -245,7 +254,7 @@ describe('Create App Flow', () => {
|
|||||||
expect(screen.getByText('app.createApp')).toBeInTheDocument()
|
expect(screen.getByText('app.createApp')).toBeInTheDocument()
|
||||||
expect(screen.getByText('app.newApp.startFromBlank')).toBeInTheDocument()
|
expect(screen.getByText('app.newApp.startFromBlank')).toBeInTheDocument()
|
||||||
expect(screen.getByText('app.newApp.startFromTemplate')).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', () => {
|
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 () => {
|
it('should open DSL import modal when "Import DSL" is clicked', async () => {
|
||||||
renderList()
|
renderList()
|
||||||
|
|
||||||
fireEvent.click(screen.getByText('app.importDSL'))
|
fireEvent.click(screen.getByText('app.importApp'))
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('create-from-dsl-modal')).toBeInTheDocument()
|
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 () => {
|
it('should close DSL import modal on cancel', async () => {
|
||||||
renderList()
|
renderList()
|
||||||
|
|
||||||
fireEvent.click(screen.getByText('app.importDSL'))
|
fireEvent.click(screen.getByText('app.importApp'))
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('create-from-dsl-modal')).toBeInTheDocument()
|
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 () => {
|
it('should call onPlanInfoChanged and refetch on successful DSL import', async () => {
|
||||||
renderList()
|
renderList()
|
||||||
|
|
||||||
fireEvent.click(screen.getByText('app.importDSL'))
|
fireEvent.click(screen.getByText('app.importApp'))
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('create-from-dsl-modal')).toBeInTheDocument()
|
expect(screen.getByTestId('create-from-dsl-modal')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
@ -451,7 +460,7 @@ describe('Create App Flow', () => {
|
|||||||
// Rapidly click different create options
|
// Rapidly click different create options
|
||||||
fireEvent.click(screen.getByText('app.newApp.startFromBlank'))
|
fireEvent.click(screen.getByText('app.newApp.startFromBlank'))
|
||||||
fireEvent.click(screen.getByText('app.newApp.startFromTemplate'))
|
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
|
// Should not crash, and some modal should be present
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
|
|||||||
@ -3,11 +3,19 @@ import { render, screen } from '@testing-library/react'
|
|||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { AppModeEnum } from '@/types/app'
|
import { AppModeEnum } from '@/types/app'
|
||||||
import AppInfo from '..'
|
import AppInfo from '../index'
|
||||||
|
|
||||||
let mockIsCurrentWorkspaceEditor = true
|
let mockIsCurrentWorkspaceEditor = true
|
||||||
const mockSetPanelOpen = vi.fn()
|
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', () => ({
|
vi.mock('@/context/app-context', () => ({
|
||||||
useAppContext: () => ({
|
useAppContext: () => ({
|
||||||
isCurrentWorkspaceEditor: mockIsCurrentWorkspaceEditor,
|
isCurrentWorkspaceEditor: mockIsCurrentWorkspaceEditor,
|
||||||
|
|||||||
@ -263,11 +263,10 @@ describe('AppCard', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should render app icon', () => {
|
it('should render app icon', () => {
|
||||||
// AppIcon component renders the emoji icon from app data
|
|
||||||
const { container } = render(<AppCard app={mockApp} />)
|
const { container } = render(<AppCard app={mockApp} />)
|
||||||
// Check that the icon container is rendered (AppIcon renders within the card)
|
const emojiElement = container.querySelector('em-emoji')
|
||||||
const iconElement = container.querySelector('[class*="icon"]') || container.querySelector('img')
|
expect(emojiElement).toBeTruthy()
|
||||||
expect(iconElement || screen.getByText(mockApp.icon)).toBeTruthy()
|
expect(emojiElement?.getAttribute('id')).toBe(mockApp.icon)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should render app type icon', () => {
|
it('should render app type icon', () => {
|
||||||
|
|||||||
@ -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', () => ({
|
vi.mock('@/hooks/use-import-dsl', () => ({
|
||||||
useImportDSL: () => ({
|
useImportDSL: () => ({
|
||||||
handleImportDSL: vi.fn(),
|
handleImportDSL: vi.fn(),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||||
import { act, fireEvent, screen } from '@testing-library/react'
|
import { act, fireEvent, screen } from '@testing-library/react'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useStore as useTagStore } from '@/app/components/base/tag-management/store'
|
import { useStore as useTagStore } from '@/app/components/base/tag-management/store'
|
||||||
@ -200,9 +201,17 @@ beforeAll(() => {
|
|||||||
} as unknown as typeof IntersectionObserver
|
} as unknown as typeof IntersectionObserver
|
||||||
})
|
})
|
||||||
|
|
||||||
// Render helper wrapping with shared nuqs testing helper.
|
const queryClient = new QueryClient({
|
||||||
|
defaultOptions: { queries: { retry: false } },
|
||||||
|
})
|
||||||
|
|
||||||
const renderList = (searchParams = '') => {
|
const renderList = (searchParams = '') => {
|
||||||
return renderWithNuqs(<List />, { searchParams })
|
return renderWithNuqs(
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<List />
|
||||||
|
</QueryClientProvider>,
|
||||||
|
{ searchParams },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('List', () => {
|
describe('List', () => {
|
||||||
@ -399,10 +408,14 @@ describe('List', () => {
|
|||||||
|
|
||||||
describe('Edge Cases', () => {
|
describe('Edge Cases', () => {
|
||||||
it('should handle multiple renders without issues', () => {
|
it('should handle multiple renders without issues', () => {
|
||||||
const { rerender } = renderWithNuqs(<List />)
|
const { rerender } = renderWithNuqs(
|
||||||
|
<QueryClientProvider client={queryClient}><List /></QueryClientProvider>,
|
||||||
|
)
|
||||||
expect(screen.getByText('app.types.all')).toBeInTheDocument()
|
expect(screen.getByText('app.types.all')).toBeInTheDocument()
|
||||||
|
|
||||||
rerender(<List />)
|
rerender(
|
||||||
|
<QueryClientProvider client={queryClient}><List /></QueryClientProvider>,
|
||||||
|
)
|
||||||
expect(screen.getByText('app.types.all')).toBeInTheDocument()
|
expect(screen.getByText('app.types.all')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -71,7 +71,7 @@ describe('CreateAppCard', () => {
|
|||||||
|
|
||||||
expect(screen.getByText('app.newApp.startFromBlank')).toBeInTheDocument()
|
expect(screen.getByText('app.newApp.startFromBlank')).toBeInTheDocument()
|
||||||
expect(screen.getByText('app.newApp.startFromTemplate')).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', () => {
|
it('should render all buttons as clickable', () => {
|
||||||
@ -190,7 +190,7 @@ describe('CreateAppCard', () => {
|
|||||||
it('should open DSL modal when clicking Import DSL', () => {
|
it('should open DSL modal when clicking Import DSL', () => {
|
||||||
render(<CreateAppCard ref={defaultRef} />)
|
render(<CreateAppCard ref={defaultRef} />)
|
||||||
|
|
||||||
fireEvent.click(screen.getByText('app.importDSL'))
|
fireEvent.click(screen.getByText('app.importApp'))
|
||||||
|
|
||||||
expect(screen.getByTestId('create-dsl-modal')).toBeInTheDocument()
|
expect(screen.getByTestId('create-dsl-modal')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
@ -198,7 +198,7 @@ describe('CreateAppCard', () => {
|
|||||||
it('should close DSL modal when clicking close button', () => {
|
it('should close DSL modal when clicking close button', () => {
|
||||||
render(<CreateAppCard ref={defaultRef} />)
|
render(<CreateAppCard ref={defaultRef} />)
|
||||||
|
|
||||||
fireEvent.click(screen.getByText('app.importDSL'))
|
fireEvent.click(screen.getByText('app.importApp'))
|
||||||
expect(screen.getByTestId('create-dsl-modal')).toBeInTheDocument()
|
expect(screen.getByTestId('create-dsl-modal')).toBeInTheDocument()
|
||||||
|
|
||||||
fireEvent.click(screen.getByTestId('close-dsl-modal'))
|
fireEvent.click(screen.getByTestId('close-dsl-modal'))
|
||||||
@ -209,7 +209,7 @@ describe('CreateAppCard', () => {
|
|||||||
const mockOnSuccess = vi.fn()
|
const mockOnSuccess = vi.fn()
|
||||||
render(<CreateAppCard ref={defaultRef} onSuccess={mockOnSuccess} />)
|
render(<CreateAppCard ref={defaultRef} onSuccess={mockOnSuccess} />)
|
||||||
|
|
||||||
fireEvent.click(screen.getByText('app.importDSL'))
|
fireEvent.click(screen.getByText('app.importApp'))
|
||||||
fireEvent.click(screen.getByTestId('success-dsl-modal'))
|
fireEvent.click(screen.getByTestId('success-dsl-modal'))
|
||||||
|
|
||||||
expect(mockOnPlanInfoChanged).toHaveBeenCalled()
|
expect(mockOnPlanInfoChanged).toHaveBeenCalled()
|
||||||
@ -245,7 +245,7 @@ describe('CreateAppCard', () => {
|
|||||||
fireEvent.click(screen.getByText('app.newApp.startFromTemplate'))
|
fireEvent.click(screen.getByText('app.newApp.startFromTemplate'))
|
||||||
fireEvent.click(screen.getByTestId('close-template-dialog'))
|
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'))
|
fireEvent.click(screen.getByTestId('close-dsl-modal'))
|
||||||
|
|
||||||
expect(screen.queryByTestId('create-app-modal')).not.toBeInTheDocument()
|
expect(screen.queryByTestId('create-app-modal')).not.toBeInTheDocument()
|
||||||
|
|||||||
@ -14,6 +14,7 @@ const getPromptEditor = () => {
|
|||||||
vi.mock('@/utils/var', () => ({
|
vi.mock('@/utils/var', () => ({
|
||||||
checkKeys: (_keys: string[]) => ({ isValid: true }),
|
checkKeys: (_keys: string[]) => ({ isValid: true }),
|
||||||
getNewVar: (key: string, type: string) => ({ key, name: key, type, required: true }),
|
getNewVar: (key: string, type: string) => ({ key, name: key, type, required: true }),
|
||||||
|
basePath: '',
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/app/components/app/configuration/config-prompt/confirm-add-var', () => ({
|
vi.mock('@/app/components/app/configuration/config-prompt/confirm-add-var', () => ({
|
||||||
|
|||||||
@ -68,6 +68,7 @@ vi.mock('lexical', async (importOriginal) => {
|
|||||||
getChildren: () => mocks.rootLines.map(line => ({
|
getChildren: () => mocks.rootLines.map(line => ({
|
||||||
getTextContent: () => line,
|
getTextContent: () => line,
|
||||||
})),
|
})),
|
||||||
|
getAllTextNodes: () => [],
|
||||||
}),
|
}),
|
||||||
TextNode: class TextNode {
|
TextNode: class TextNode {
|
||||||
__text: string
|
__text: string
|
||||||
|
|||||||
@ -77,12 +77,16 @@ const { mockConfig, mockEnv } = vi.hoisted(() => ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
vi.mock('@/config', () => ({
|
vi.mock('@/config', async (importOriginal) => {
|
||||||
get IS_CLOUD_EDITION() { return mockConfig.IS_CLOUD_EDITION },
|
const actual = await importOriginal<typeof import('@/config')>()
|
||||||
get ZENDESK_WIDGET_KEY() { return mockConfig.ZENDESK_WIDGET_KEY },
|
return {
|
||||||
IS_DEV: false,
|
...actual,
|
||||||
IS_CE_EDITION: false,
|
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)
|
vi.mock('@/env', () => mockEnv)
|
||||||
|
|
||||||
const baseAppContextValue: AppContextValue = {
|
const baseAppContextValue: AppContextValue = {
|
||||||
|
|||||||
@ -55,8 +55,20 @@ vi.mock('@/service/workflow', () => ({
|
|||||||
syncWorkflowDraft: (p: unknown) => mockSyncWorkflowDraft(p),
|
syncWorkflowDraft: (p: unknown) => mockSyncWorkflowDraft(p),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/service/fetch', () => ({ postWithKeepalive: vi.fn() }))
|
vi.mock('@/service/fetch', async (importOriginal) => {
|
||||||
vi.mock('@/config', () => ({ API_PREFIX: '/api' }))
|
const actual = await importOriginal<typeof import('@/service/fetch')>()
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
postWithKeepalive: vi.fn(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
vi.mock('@/config', async (importOriginal) => {
|
||||||
|
const actual = await importOriginal<typeof import('@/config')>()
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
API_PREFIX: '/api',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const mockHandleRefreshWorkflowDraft = vi.fn()
|
const mockHandleRefreshWorkflowDraft = vi.fn()
|
||||||
vi.mock('@/app/components/workflow-app/hooks', () => ({
|
vi.mock('@/app/components/workflow-app/hooks', () => ({
|
||||||
|
|||||||
@ -11,7 +11,7 @@ vi.mock('@/app/components/workflow/store', () => ({
|
|||||||
getState: () => ({
|
getState: () => ({
|
||||||
appId: 'app-1',
|
appId: 'app-1',
|
||||||
isWorkflowDataLoaded: true,
|
isWorkflowDataLoaded: true,
|
||||||
debouncedSyncWorkflowDraft: undefined,
|
debouncedSyncWorkflowDraft: { cancel: vi.fn() },
|
||||||
setSyncWorkflowDraftHash: mockSetSyncWorkflowDraftHash,
|
setSyncWorkflowDraftHash: mockSetSyncWorkflowDraftHash,
|
||||||
setIsSyncingWorkflowDraft: vi.fn(),
|
setIsSyncingWorkflowDraft: vi.fn(),
|
||||||
setEnvironmentVariables: vi.fn(),
|
setEnvironmentVariables: vi.fn(),
|
||||||
|
|||||||
@ -26,6 +26,7 @@ vi.mock('../use-workflow', () => ({
|
|||||||
|
|
||||||
vi.mock('../../utils', () => ({
|
vi.mock('../../utils', () => ({
|
||||||
getNodesConnectedSourceOrTargetHandleIdsMap: vi.fn(() => ({})),
|
getNodesConnectedSourceOrTargetHandleIdsMap: vi.fn(() => ({})),
|
||||||
|
genNodeMetaData: vi.fn(({ type, sort }: { type: string, sort: number }) => ({ type, sort })),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// useNodesSyncDraft is used REAL — via renderWorkflowHook + hooksStoreProps
|
// useNodesSyncDraft is used REAL — via renderWorkflowHook + hooksStoreProps
|
||||||
|
|||||||
@ -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!
|
const state = store.getState().workflowRunningData!
|
||||||
expect(state.resultText).toBe('Hello World')
|
expect(state.resultText).toBe('Hello World')
|
||||||
|
|||||||
@ -172,9 +172,9 @@ describe('createWorkflowStore', () => {
|
|||||||
expect(store.getState().controlMode).toBe('pointer')
|
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()
|
const store = createStore()
|
||||||
expect(store.getState().controlMode).toBe('hand')
|
expect(store.getState().controlMode).toBe('pointer')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should read panelWidth from localStorage', () => {
|
it('should read panelWidth from localStorage', () => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user