mirror of
https://github.com/langgenius/dify.git
synced 2026-05-14 01:00:18 -04:00
merge hitl
This commit is contained in:
@@ -253,7 +253,20 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator):
|
||||
):
|
||||
"""
|
||||
Resume a paused advanced chat execution.
|
||||
|
||||
``trace_manager`` is transient and excluded from generate-entity serialization,
|
||||
so resumed executions rebuild it here before persistence layers receive the entity.
|
||||
"""
|
||||
if application_generate_entity.trace_manager is None:
|
||||
application_generate_entity = application_generate_entity.model_copy(
|
||||
update={
|
||||
"trace_manager": TraceQueueManager(
|
||||
app_id=app_model.id,
|
||||
user_id=user.id if isinstance(user, Account) else user.session_id,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
return self._generate(
|
||||
workflow=workflow,
|
||||
user=user,
|
||||
|
||||
@@ -272,7 +272,20 @@ class WorkflowAppGenerator(BaseAppGenerator):
|
||||
) -> Mapping[str, Any] | Generator[str | Mapping[str, Any], None, None]:
|
||||
"""
|
||||
Resume a paused workflow execution using the persisted runtime state.
|
||||
|
||||
``trace_manager`` is transient and excluded from generate-entity serialization,
|
||||
so resumed executions rebuild it here before persistence layers receive the entity.
|
||||
"""
|
||||
if application_generate_entity.trace_manager is None:
|
||||
application_generate_entity = application_generate_entity.model_copy(
|
||||
update={
|
||||
"trace_manager": TraceQueueManager(
|
||||
app_id=app_model.id,
|
||||
user_id=user.id if isinstance(user, Account) else user.session_id,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
return self._generate(
|
||||
app_model=app_model,
|
||||
workflow=workflow,
|
||||
|
||||
@@ -197,6 +197,7 @@ class TestAdvancedChatAppGeneratorInternals:
|
||||
|
||||
def test_resume_delegates_to_generate(self, monkeypatch: pytest.MonkeyPatch):
|
||||
generator = AdvancedChatAppGenerator()
|
||||
existing_trace_manager = SimpleNamespace(app_id="existing-app", user_id="existing-user")
|
||||
application_generate_entity = AdvancedChatAppGenerateEntity.model_construct(
|
||||
task_id="task",
|
||||
app_config=self._build_app_config(),
|
||||
@@ -207,22 +208,25 @@ class TestAdvancedChatAppGeneratorInternals:
|
||||
stream=True,
|
||||
invoke_from=InvokeFrom.WEB_APP,
|
||||
extras={},
|
||||
trace_manager=None,
|
||||
trace_manager=existing_trace_manager,
|
||||
workflow_run_id="run-id",
|
||||
)
|
||||
|
||||
captured: dict[str, object] = {}
|
||||
captured_entity: AdvancedChatAppGenerateEntity | None = None
|
||||
captured_graph_runtime_state: object | None = None
|
||||
|
||||
def _fake_generate(**kwargs):
|
||||
captured.update(kwargs)
|
||||
return {"resumed": True}
|
||||
nonlocal captured_entity, captured_graph_runtime_state
|
||||
captured_entity = kwargs["application_generate_entity"]
|
||||
captured_graph_runtime_state = kwargs["graph_runtime_state"]
|
||||
return SimpleNamespace(resumed=True)
|
||||
|
||||
monkeypatch.setattr(generator, "_generate", _fake_generate)
|
||||
|
||||
result = generator.resume(
|
||||
app_model=SimpleNamespace(),
|
||||
app_model=SimpleNamespace(id="app-id"),
|
||||
workflow=SimpleNamespace(),
|
||||
user=SimpleNamespace(),
|
||||
user=SimpleNamespace(id="end-user-id", session_id="session-id"),
|
||||
conversation=SimpleNamespace(id="conversation-id"),
|
||||
message=SimpleNamespace(id="message-id"),
|
||||
application_generate_entity=application_generate_entity,
|
||||
@@ -232,8 +236,10 @@ class TestAdvancedChatAppGeneratorInternals:
|
||||
pause_state_config=None,
|
||||
)
|
||||
|
||||
assert result == {"resumed": True}
|
||||
assert captured["graph_runtime_state"] is not None
|
||||
assert result.resumed is True
|
||||
assert captured_entity is not None
|
||||
assert captured_entity.trace_manager is existing_trace_manager
|
||||
assert captured_graph_runtime_state is not None
|
||||
|
||||
def test_single_iteration_generate_builds_debug_task(self, monkeypatch: pytest.MonkeyPatch):
|
||||
generator = AdvancedChatAppGenerator()
|
||||
@@ -1243,3 +1249,119 @@ class TestAdvancedChatAppGeneratorInternals:
|
||||
)
|
||||
|
||||
assert captured["application_generate_entity"].parent_message_id == UUID_NIL
|
||||
|
||||
|
||||
class TestAdvancedChatAppGeneratorResume:
|
||||
@staticmethod
|
||||
def _build_app_config() -> WorkflowUIBasedAppConfig:
|
||||
return WorkflowUIBasedAppConfig(
|
||||
tenant_id="tenant",
|
||||
app_id="app",
|
||||
app_mode=AppMode.ADVANCED_CHAT,
|
||||
additional_features=AppAdditionalFeatures(),
|
||||
variables=[],
|
||||
workflow_id="workflow-id",
|
||||
)
|
||||
|
||||
def test_resume_restores_trace_manager_when_missing(self, monkeypatch: pytest.MonkeyPatch):
|
||||
generator = AdvancedChatAppGenerator()
|
||||
application_generate_entity = AdvancedChatAppGenerateEntity.model_construct(
|
||||
task_id="task",
|
||||
app_config=self._build_app_config(),
|
||||
file_upload_config=None,
|
||||
conversation_id="conversation-id",
|
||||
inputs={},
|
||||
query="hello",
|
||||
files=[],
|
||||
parent_message_id="parent-message-id",
|
||||
user_id="user",
|
||||
stream=False,
|
||||
invoke_from=InvokeFrom.WEB_APP,
|
||||
extras={},
|
||||
trace_manager=None,
|
||||
workflow_run_id="run-id",
|
||||
)
|
||||
DummyTraceQueueManager = type(
|
||||
"_DummyTraceQueueManager",
|
||||
(TraceQueueManager,),
|
||||
{
|
||||
"__init__": lambda self, app_id=None, user_id=None: (
|
||||
setattr(self, "app_id", app_id) or setattr(self, "user_id", user_id)
|
||||
)
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"core.app.apps.advanced_chat.app_generator.TraceQueueManager",
|
||||
DummyTraceQueueManager,
|
||||
)
|
||||
captured_entity: AdvancedChatAppGenerateEntity | None = None
|
||||
|
||||
def _fake_generate(**kwargs):
|
||||
nonlocal captured_entity
|
||||
captured_entity = kwargs["application_generate_entity"]
|
||||
return SimpleNamespace(ok=True)
|
||||
|
||||
monkeypatch.setattr(generator, "_generate", _fake_generate)
|
||||
|
||||
result = generator.resume(
|
||||
app_model=SimpleNamespace(id="app-id"),
|
||||
workflow=SimpleNamespace(),
|
||||
user=SimpleNamespace(id="end-user-id", session_id="session-id"),
|
||||
conversation=SimpleNamespace(id="conversation-id"),
|
||||
message=SimpleNamespace(id="message-id"),
|
||||
application_generate_entity=application_generate_entity,
|
||||
workflow_execution_repository=SimpleNamespace(),
|
||||
workflow_node_execution_repository=SimpleNamespace(),
|
||||
graph_runtime_state=SimpleNamespace(),
|
||||
)
|
||||
|
||||
assert result.ok is True
|
||||
assert captured_entity is not None
|
||||
trace_manager = captured_entity.trace_manager
|
||||
assert isinstance(trace_manager, DummyTraceQueueManager)
|
||||
assert trace_manager.app_id == "app-id"
|
||||
assert trace_manager.user_id == "session-id"
|
||||
|
||||
def test_resume_preserves_existing_trace_manager(self, monkeypatch: pytest.MonkeyPatch):
|
||||
generator = AdvancedChatAppGenerator()
|
||||
existing_trace_manager = SimpleNamespace(app_id="existing-app", user_id="existing-user")
|
||||
application_generate_entity = AdvancedChatAppGenerateEntity.model_construct(
|
||||
task_id="task",
|
||||
app_config=self._build_app_config(),
|
||||
file_upload_config=None,
|
||||
conversation_id="conversation-id",
|
||||
inputs={},
|
||||
query="hello",
|
||||
files=[],
|
||||
parent_message_id="parent-message-id",
|
||||
user_id="user",
|
||||
stream=False,
|
||||
invoke_from=InvokeFrom.WEB_APP,
|
||||
extras={},
|
||||
trace_manager=existing_trace_manager,
|
||||
workflow_run_id="run-id",
|
||||
)
|
||||
captured_entity: AdvancedChatAppGenerateEntity | None = None
|
||||
|
||||
def _fake_generate(**kwargs):
|
||||
nonlocal captured_entity
|
||||
captured_entity = kwargs["application_generate_entity"]
|
||||
return SimpleNamespace(ok=True)
|
||||
|
||||
monkeypatch.setattr(generator, "_generate", _fake_generate)
|
||||
|
||||
result = generator.resume(
|
||||
app_model=SimpleNamespace(id="app-id"),
|
||||
workflow=SimpleNamespace(),
|
||||
user=SimpleNamespace(id="end-user-id", session_id="session-id"),
|
||||
conversation=SimpleNamespace(id="conversation-id"),
|
||||
message=SimpleNamespace(id="message-id"),
|
||||
application_generate_entity=application_generate_entity,
|
||||
workflow_execution_repository=SimpleNamespace(),
|
||||
workflow_node_execution_repository=SimpleNamespace(),
|
||||
graph_runtime_state=SimpleNamespace(),
|
||||
)
|
||||
|
||||
assert result.ok is True
|
||||
assert captured_entity is not None
|
||||
assert captured_entity.trace_manager is existing_trace_manager
|
||||
|
||||
@@ -228,7 +228,11 @@ def test_workflow_app_pause_resume_matches_baseline(mocker: MockerFixture):
|
||||
app_model=SimpleNamespace(mode="workflow"),
|
||||
workflow=SimpleNamespace(),
|
||||
user=SimpleNamespace(),
|
||||
application_generate_entity=SimpleNamespace(stream=False, invoke_from=InvokeFrom.SERVICE_API),
|
||||
application_generate_entity=SimpleNamespace(
|
||||
stream=False,
|
||||
invoke_from=InvokeFrom.SERVICE_API,
|
||||
trace_manager=SimpleNamespace(),
|
||||
),
|
||||
graph_runtime_state=resumed_state,
|
||||
workflow_execution_repository=SimpleNamespace(),
|
||||
workflow_node_execution_repository=SimpleNamespace(),
|
||||
@@ -270,7 +274,11 @@ def test_advanced_chat_pause_resume_matches_baseline(mocker: MockerFixture):
|
||||
user=SimpleNamespace(),
|
||||
conversation=SimpleNamespace(id="conv"),
|
||||
message=SimpleNamespace(id="msg"),
|
||||
application_generate_entity=SimpleNamespace(stream=False, invoke_from=InvokeFrom.SERVICE_API),
|
||||
application_generate_entity=SimpleNamespace(
|
||||
stream=False,
|
||||
invoke_from=InvokeFrom.SERVICE_API,
|
||||
trace_manager=SimpleNamespace(),
|
||||
),
|
||||
workflow_execution_repository=SimpleNamespace(),
|
||||
workflow_node_execution_repository=SimpleNamespace(),
|
||||
graph_runtime_state=resumed_state,
|
||||
|
||||
@@ -99,7 +99,7 @@ def test_resume_delegates_to_generate(mocker: MockerFixture):
|
||||
generator = WorkflowAppGenerator()
|
||||
mock_generate = mocker.patch.object(generator, "_generate", return_value="ok")
|
||||
|
||||
application_generate_entity = SimpleNamespace(stream=False, invoke_from="debugger")
|
||||
application_generate_entity = SimpleNamespace(stream=False, invoke_from="debugger", trace_manager=MagicMock())
|
||||
runtime_state = MagicMock(name="runtime-state")
|
||||
pause_config = MagicMock(name="pause-config")
|
||||
|
||||
|
||||
@@ -186,3 +186,114 @@ class TestWorkflowAppGeneratorGenerate:
|
||||
)
|
||||
|
||||
assert result == {"ok": True}
|
||||
|
||||
|
||||
class TestWorkflowAppGeneratorResume:
|
||||
def test_resume_restores_trace_manager_when_missing(self, monkeypatch: pytest.MonkeyPatch):
|
||||
generator = WorkflowAppGenerator()
|
||||
app_config = WorkflowUIBasedAppConfig(
|
||||
tenant_id="tenant",
|
||||
app_id="app",
|
||||
app_mode=AppMode.WORKFLOW,
|
||||
additional_features=AppAdditionalFeatures(),
|
||||
variables=[],
|
||||
workflow_id="workflow-id",
|
||||
)
|
||||
application_generate_entity = WorkflowAppGenerateEntity.model_construct(
|
||||
task_id="task",
|
||||
app_config=app_config,
|
||||
inputs={},
|
||||
files=[],
|
||||
user_id="user",
|
||||
stream=False,
|
||||
invoke_from=InvokeFrom.WEB_APP,
|
||||
extras={},
|
||||
trace_manager=None,
|
||||
workflow_execution_id="run-id",
|
||||
call_depth=0,
|
||||
)
|
||||
DummyTraceQueueManager = type(
|
||||
"_DummyTraceQueueManager",
|
||||
(TraceQueueManager,),
|
||||
{
|
||||
"__init__": lambda self, app_id=None, user_id=None: (
|
||||
setattr(self, "app_id", app_id) or setattr(self, "user_id", user_id)
|
||||
)
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"core.app.apps.workflow.app_generator.TraceQueueManager",
|
||||
DummyTraceQueueManager,
|
||||
)
|
||||
captured_entity: WorkflowAppGenerateEntity | None = None
|
||||
|
||||
def _fake_generate(**kwargs):
|
||||
nonlocal captured_entity
|
||||
captured_entity = kwargs["application_generate_entity"]
|
||||
return SimpleNamespace(ok=True)
|
||||
|
||||
monkeypatch.setattr(generator, "_generate", _fake_generate)
|
||||
|
||||
result = generator.resume(
|
||||
app_model=SimpleNamespace(id="app-id"),
|
||||
workflow=SimpleNamespace(),
|
||||
user=SimpleNamespace(id="end-user-id", session_id="session-id"),
|
||||
application_generate_entity=application_generate_entity,
|
||||
graph_runtime_state=SimpleNamespace(),
|
||||
workflow_execution_repository=SimpleNamespace(),
|
||||
workflow_node_execution_repository=SimpleNamespace(),
|
||||
)
|
||||
|
||||
assert result.ok is True
|
||||
assert captured_entity is not None
|
||||
trace_manager = captured_entity.trace_manager
|
||||
assert isinstance(trace_manager, DummyTraceQueueManager)
|
||||
assert trace_manager.app_id == "app-id"
|
||||
assert trace_manager.user_id == "session-id"
|
||||
|
||||
def test_resume_preserves_existing_trace_manager(self, monkeypatch: pytest.MonkeyPatch):
|
||||
generator = WorkflowAppGenerator()
|
||||
app_config = WorkflowUIBasedAppConfig(
|
||||
tenant_id="tenant",
|
||||
app_id="app",
|
||||
app_mode=AppMode.WORKFLOW,
|
||||
additional_features=AppAdditionalFeatures(),
|
||||
variables=[],
|
||||
workflow_id="workflow-id",
|
||||
)
|
||||
existing_trace_manager = SimpleNamespace(app_id="existing-app", user_id="existing-user")
|
||||
application_generate_entity = WorkflowAppGenerateEntity.model_construct(
|
||||
task_id="task",
|
||||
app_config=app_config,
|
||||
inputs={},
|
||||
files=[],
|
||||
user_id="user",
|
||||
stream=False,
|
||||
invoke_from=InvokeFrom.WEB_APP,
|
||||
extras={},
|
||||
trace_manager=existing_trace_manager,
|
||||
workflow_execution_id="run-id",
|
||||
call_depth=0,
|
||||
)
|
||||
captured_entity: WorkflowAppGenerateEntity | None = None
|
||||
|
||||
def _fake_generate(**kwargs):
|
||||
nonlocal captured_entity
|
||||
captured_entity = kwargs["application_generate_entity"]
|
||||
return SimpleNamespace(ok=True)
|
||||
|
||||
monkeypatch.setattr(generator, "_generate", _fake_generate)
|
||||
|
||||
result = generator.resume(
|
||||
app_model=SimpleNamespace(id="app-id"),
|
||||
workflow=SimpleNamespace(),
|
||||
user=SimpleNamespace(id="end-user-id", session_id="session-id"),
|
||||
application_generate_entity=application_generate_entity,
|
||||
graph_runtime_state=SimpleNamespace(),
|
||||
workflow_execution_repository=SimpleNamespace(),
|
||||
workflow_node_execution_repository=SimpleNamespace(),
|
||||
)
|
||||
|
||||
assert result.ok is True
|
||||
assert captured_entity is not None
|
||||
assert captured_entity.trace_manager is existing_trace_manager
|
||||
|
||||
@@ -120,6 +120,9 @@
|
||||
}
|
||||
},
|
||||
"web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
@@ -192,6 +195,11 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/app/annotation/batch-add-annotation-modal/csv-downloader.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/app/annotation/batch-add-annotation-modal/index.tsx": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 1
|
||||
@@ -411,7 +419,7 @@
|
||||
},
|
||||
"web/app/components/app/configuration/debug/index.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 11
|
||||
@@ -472,6 +480,11 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/app/overview/app-chart.tsx": {
|
||||
"react/component-hook-factories": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/app/overview/trigger-card.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@@ -612,14 +625,14 @@
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
},
|
||||
"react/component-hook-factories": {
|
||||
"count": 1
|
||||
},
|
||||
"react/no-nested-component-definitions": {
|
||||
"count": 1
|
||||
},
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
},
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/base/carousel/index.tsx": {
|
||||
@@ -690,9 +703,6 @@
|
||||
}
|
||||
},
|
||||
"web/app/components/base/chat/chat/answer/index.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 3
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
@@ -811,7 +821,7 @@
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 3
|
||||
},
|
||||
"react/component-hook-factories": {
|
||||
"react/jsx-no-key-after-spread": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
@@ -929,14 +939,21 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/base/form/form-scenarios/base/__tests__/field.spec.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/base/form/form-scenarios/base/field.tsx": {
|
||||
"react/component-hook-factories": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/base/form/form-scenarios/base/index.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/base/form/form-scenarios/base/types.ts": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 1
|
||||
@@ -950,10 +967,12 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/base/form/form-scenarios/input-field/__tests__/field.spec.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/base/form/form-scenarios/input-field/field.tsx": {
|
||||
"react/component-hook-factories": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
@@ -966,10 +985,12 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/base/form/form-scenarios/node-panel/__tests__/field.spec.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/base/form/form-scenarios/node-panel/field.tsx": {
|
||||
"react/component-hook-factories": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
@@ -1322,9 +1343,6 @@
|
||||
}
|
||||
},
|
||||
"web/app/components/base/markdown-blocks/code-block.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 7
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 9
|
||||
}
|
||||
@@ -1379,7 +1397,7 @@
|
||||
},
|
||||
"web/app/components/base/mermaid/index.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 7
|
||||
"count": 4
|
||||
},
|
||||
"regexp/no-super-linear-backtracking": {
|
||||
"count": 3
|
||||
@@ -1659,11 +1677,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/base/video-gallery/VideoPlayer.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/base/voice-input/__tests__/index.spec.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
@@ -1685,15 +1698,9 @@
|
||||
"web/app/components/base/with-input-validation/index.stories.tsx": {
|
||||
"no-console": {
|
||||
"count": 1
|
||||
},
|
||||
"react/component-hook-factories": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/base/with-input-validation/index.tsx": {
|
||||
"react/component-hook-factories": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
@@ -1726,6 +1733,11 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/billing/pricing/plans/self-hosted-plan-item/button.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/billing/pricing/types.ts": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 1
|
||||
@@ -1736,6 +1748,11 @@
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/common/document-status-with-action/status-with-action.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/common/image-previewer/index.tsx": {
|
||||
"no-irregular-whitespace": {
|
||||
"count": 1
|
||||
@@ -1919,6 +1936,9 @@
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/options/index.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
@@ -1934,6 +1954,9 @@
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/documents/create-from-pipeline/process-documents/form.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
}
|
||||
@@ -1953,6 +1976,11 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/documents/detail/batch-modal/csv-downloader.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/documents/detail/completed/common/chunk-content.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
@@ -1979,6 +2007,11 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/documents/detail/completed/segment-list.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/documents/detail/context.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@@ -2075,6 +2108,16 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/metadata/metadata-dataset/dataset-metadata-drawer.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/metadata/metadata-dataset/select-metadata.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/metadata/types.ts": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 2
|
||||
@@ -2090,6 +2133,11 @@
|
||||
"count": 7
|
||||
}
|
||||
},
|
||||
"web/app/components/develop/doc.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/develop/md.tsx": {
|
||||
"ts/no-empty-object-type": {
|
||||
"count": 1
|
||||
@@ -2103,7 +2151,7 @@
|
||||
"count": 1
|
||||
},
|
||||
"react/set-state-in-effect": {
|
||||
"count": 3
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/explore/banner/indicator-button.tsx": {
|
||||
@@ -2329,7 +2377,7 @@
|
||||
},
|
||||
"web/app/components/header/account-setting/model-provider-page/provider-added-card/cooldown-timer.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/__tests__/use-activate-credential.spec.tsx": {
|
||||
@@ -2618,9 +2666,19 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/rag-pipeline/components/panel/input-field/editor/form/__tests__/hidden-fields.spec.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"web/app/components/rag-pipeline/components/panel/input-field/editor/form/__tests__/show-all-settings.spec.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"web/app/components/rag-pipeline/components/panel/input-field/editor/form/hidden-fields.tsx": {
|
||||
"react/component-hook-factories": {
|
||||
"count": 1
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@@ -2631,18 +2689,20 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/rag-pipeline/components/panel/input-field/editor/form/index.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 6
|
||||
}
|
||||
},
|
||||
"web/app/components/rag-pipeline/components/panel/input-field/editor/form/initial-fields.tsx": {
|
||||
"react/component-hook-factories": {
|
||||
"count": 1
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/rag-pipeline/components/panel/input-field/editor/form/show-all-settings.tsx": {
|
||||
"react/component-hook-factories": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
@@ -2657,12 +2717,20 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/rag-pipeline/components/panel/input-field/preview/form.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/rag-pipeline/components/panel/test-run/preparation/document-processing/index.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/rag-pipeline/components/panel/test-run/preparation/document-processing/options.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
@@ -2772,11 +2840,24 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/share/text-generation/run-batch/csv-download/index.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/share/text-generation/run-batch/csv-reader/index.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/share/text-generation/run-batch/res-download/index.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/share/text-generation/run-once/index.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
@@ -2846,6 +2927,11 @@
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow-app/components/__tests__/workflow-children.spec.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow-app/components/workflow-children.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
@@ -2901,22 +2987,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/block-selector/featured-tools.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/block-selector/featured-triggers.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/block-selector/hooks.ts": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
@@ -2952,29 +3022,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/block-selector/tool/tool.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/block-selector/trigger-plugin/item.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/block-selector/types.ts": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 4
|
||||
@@ -3013,6 +3060,11 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/header/__tests__/index.spec.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/header/run-mode.tsx": {
|
||||
"no-console": {
|
||||
"count": 1
|
||||
@@ -3159,9 +3211,6 @@
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 6
|
||||
}
|
||||
@@ -3201,6 +3250,9 @@
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/_base/components/input-var-type-icon.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
@@ -3273,9 +3325,17 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-icon.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/_base/components/variable/variable-label/hooks.ts": {
|
||||
"react/no-unnecessary-use-prefix": {
|
||||
"count": 2
|
||||
},
|
||||
"react/use-memo": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/_base/components/variable/variable-label/index.tsx": {
|
||||
@@ -3285,7 +3345,7 @@
|
||||
},
|
||||
"web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 3
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 6
|
||||
@@ -3363,6 +3423,9 @@
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/agent/node.tsx": {
|
||||
"react/jsx-no-key-after-spread": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
@@ -3554,9 +3617,6 @@
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/human-input/components/form-content.tsx": {
|
||||
"react/component-hook-factories": {
|
||||
"count": 1
|
||||
},
|
||||
"react/no-nested-component-definitions": {
|
||||
"count": 1
|
||||
},
|
||||
@@ -3597,6 +3657,11 @@
|
||||
"count": 5
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/index.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/iteration/default.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@@ -3716,11 +3781,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/auto-width-input.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@@ -3838,11 +3898,6 @@
|
||||
"count": 9
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/question-classifier/components/class-list.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 8
|
||||
@@ -4053,6 +4108,11 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/panel/__tests__/index.spec.tsx": {
|
||||
"react/static-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/panel/chat-record/index.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 8
|
||||
@@ -4144,7 +4204,7 @@
|
||||
},
|
||||
"web/app/components/workflow/run/index.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/run/iteration-log/index.tsx": {
|
||||
@@ -4464,9 +4524,6 @@
|
||||
}
|
||||
},
|
||||
"web/hooks/use-mitt.ts": {
|
||||
"react/component-hook-factories": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
@@ -4777,11 +4834,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/utils/context.ts": {
|
||||
"react/component-hook-factories": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/utils/error-parser.ts": {
|
||||
"no-console": {
|
||||
"count": 1
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "dify",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@11.0.8",
|
||||
"packageManager": "pnpm@11.1.1",
|
||||
"engines": {
|
||||
"node": "^22.22.1"
|
||||
},
|
||||
|
||||
2509
pnpm-lock.yaml
generated
2509
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -30,7 +30,10 @@ overrides:
|
||||
'@monaco-editor/loader': 1.7.0
|
||||
brace-expansion@>=2.0.0 <2.0.3: 2.0.3
|
||||
canvas: ^3.2.2
|
||||
dompurify@>=3.1.3 <=3.3.1: 3.3.2
|
||||
dompurify@<3.4.0: ^3.4.0
|
||||
dompurify@<=3.3.3: ^3.3.4
|
||||
dompurify@>=1.0.10 <3.4.0: ^3.4.0
|
||||
dompurify@>=3.0.1 <3.4.0: ^3.4.0
|
||||
esbuild@<0.27.2: 0.27.2
|
||||
flatted@<=3.4.1: 3.4.2
|
||||
glob@>=10.2.0 <10.5.0: 11.1.0
|
||||
@@ -39,6 +42,7 @@ overrides:
|
||||
lodash@>=4.0.0 <= 4.17.23: 4.18.0
|
||||
picomatch@<2.3.2: 2.3.2
|
||||
picomatch@>=4.0.0 <4.0.4: 4.0.4
|
||||
postcss@<8.5.10: ^8.5.10
|
||||
rollup@>=4.0.0 <4.59.0: 4.59.0
|
||||
safe-buffer: ^5.2.1
|
||||
safer-buffer: npm:@nolyfill/safer-buffer@^1.0.44
|
||||
@@ -54,21 +58,21 @@ overrides:
|
||||
yaml@>=2.0.0 <2.8.3: 2.8.3
|
||||
yauzl@<3.2.1: 3.2.1
|
||||
catalog:
|
||||
'@amplitude/analytics-browser': 2.42.1
|
||||
'@amplitude/plugin-session-replay-browser': 1.30.1
|
||||
'@antfu/eslint-config': 8.2.0
|
||||
'@amplitude/analytics-browser': 2.42.2
|
||||
'@amplitude/plugin-session-replay-browser': 1.30.3
|
||||
'@antfu/eslint-config': 9.0.0
|
||||
'@base-ui/react': 1.4.1
|
||||
'@chromatic-com/storybook': 5.1.2
|
||||
'@cucumber/cucumber': 12.8.2
|
||||
'@cucumber/cucumber': 12.8.3
|
||||
'@egoist/tailwindcss-icons': 1.9.2
|
||||
'@emoji-mart/data': 1.2.1
|
||||
'@eslint-react/eslint-plugin': 3.0.0
|
||||
'@eslint-react/eslint-plugin': 5.7.7
|
||||
'@eslint/js': 10.0.1
|
||||
'@floating-ui/react': 0.27.19
|
||||
'@formatjs/intl-localematcher': 0.8.6
|
||||
'@formatjs/intl-localematcher': 0.8.7
|
||||
'@heroicons/react': 2.2.0
|
||||
'@hey-api/openapi-ts': 0.97.1
|
||||
'@hono/node-server': 2.0.1
|
||||
'@hono/node-server': 2.0.2
|
||||
'@iconify-json/heroicons': 1.2.3
|
||||
'@iconify-json/ri': 1.2.10
|
||||
'@lexical/code': 0.44.0
|
||||
@@ -84,14 +88,14 @@ catalog:
|
||||
'@monaco-editor/react': 4.7.0
|
||||
'@next/eslint-plugin-next': 16.2.6
|
||||
'@next/mdx': 16.2.6
|
||||
'@orpc/client': 1.14.2
|
||||
'@orpc/contract': 1.14.2
|
||||
'@orpc/openapi-client': 1.14.2
|
||||
'@orpc/tanstack-query': 1.14.2
|
||||
'@playwright/test': 1.59.1
|
||||
'@orpc/client': 1.14.3
|
||||
'@orpc/contract': 1.14.3
|
||||
'@orpc/openapi-client': 1.14.3
|
||||
'@orpc/tanstack-query': 1.14.3
|
||||
'@playwright/test': 1.60.0
|
||||
'@remixicon/react': 4.9.0
|
||||
'@rgrove/parse-xml': 4.2.0
|
||||
'@sentry/react': 10.52.0
|
||||
'@sentry/react': 10.53.1
|
||||
'@storybook/addon-docs': 10.3.6
|
||||
'@storybook/addon-links': 10.3.6
|
||||
'@storybook/addon-onboarding': 10.3.6
|
||||
@@ -102,42 +106,42 @@ catalog:
|
||||
'@streamdown/math': 1.0.2
|
||||
'@svgdotjs/svg.js': 3.2.5
|
||||
'@t3-oss/env-nextjs': 0.13.11
|
||||
'@tailwindcss/postcss': 4.2.4
|
||||
'@tailwindcss/postcss': 4.3.0
|
||||
'@tailwindcss/typography': 0.5.19
|
||||
'@tailwindcss/vite': 4.2.4
|
||||
'@tanstack/eslint-plugin-query': 5.100.9
|
||||
'@tanstack/react-devtools': 0.10.2
|
||||
'@tanstack/react-form': 1.29.1
|
||||
'@tanstack/react-form-devtools': 0.2.22
|
||||
'@tailwindcss/vite': 4.3.0
|
||||
'@tanstack/eslint-plugin-query': 5.100.10
|
||||
'@tanstack/react-devtools': 0.10.3
|
||||
'@tanstack/react-form': 1.32.0
|
||||
'@tanstack/react-form-devtools': 0.2.27
|
||||
'@tanstack/react-hotkeys': 0.10.0
|
||||
'@tanstack/react-query': 5.100.9
|
||||
'@tanstack/react-query-devtools': 5.100.9
|
||||
'@tanstack/react-query': 5.100.10
|
||||
'@tanstack/react-query-devtools': 5.100.10
|
||||
'@tanstack/react-virtual': 3.13.24
|
||||
'@testing-library/dom': 10.4.1
|
||||
'@testing-library/jest-dom': 6.9.1
|
||||
'@testing-library/react': 16.3.2
|
||||
'@testing-library/user-event': 14.6.1
|
||||
'@tsslint/cli': 3.1.1
|
||||
'@tsslint/compat-eslint': 3.1.1
|
||||
'@tsslint/config': 3.1.1
|
||||
'@tsslint/cli': 3.1.2
|
||||
'@tsslint/compat-eslint': 3.1.2
|
||||
'@tsslint/config': 3.1.2
|
||||
'@types/js-cookie': 3.0.6
|
||||
'@types/js-yaml': 4.0.9
|
||||
'@types/negotiator': 0.6.4
|
||||
'@types/node': 25.6.2
|
||||
'@types/node': 25.7.0
|
||||
'@types/qs': 6.15.1
|
||||
'@types/react': 19.2.14
|
||||
'@types/react-dom': 19.2.3
|
||||
'@types/sortablejs': 1.15.9
|
||||
'@typescript-eslint/eslint-plugin': 8.59.2
|
||||
'@typescript-eslint/parser': 8.59.2
|
||||
'@typescript/native-preview': 7.0.0-dev.20260507.1
|
||||
'@typescript-eslint/eslint-plugin': 8.59.3
|
||||
'@typescript-eslint/parser': 8.59.3
|
||||
'@typescript/native-preview': 7.0.0-dev.20260512.1
|
||||
'@vitejs/plugin-react': 6.0.1
|
||||
'@vitejs/plugin-rsc': 0.5.26
|
||||
'@vitest/coverage-v8': 4.1.5
|
||||
'@vitest/coverage-v8': 4.1.6
|
||||
abcjs: 6.6.3
|
||||
agentation: 3.0.2
|
||||
ahooks: 3.9.7
|
||||
c12: 1.11.2
|
||||
c12: 4.0.0-beta.5
|
||||
class-variance-authority: 0.7.1
|
||||
client-only: 0.0.1
|
||||
clsx: 2.1.1
|
||||
@@ -172,22 +176,22 @@ catalog:
|
||||
hono: 4.12.18
|
||||
html-entities: 2.6.0
|
||||
html-to-image: 1.11.13
|
||||
i18next: 26.0.10
|
||||
i18next: 26.1.0
|
||||
i18next-resources-to-backend: 1.2.1
|
||||
iconify-import-svg: 0.2.0
|
||||
immer: 11.1.7
|
||||
immer: 11.1.8
|
||||
jotai: 2.20.0
|
||||
js-audio-recorder: 1.0.7
|
||||
js-cookie: 3.0.5
|
||||
js-yaml: 4.1.1
|
||||
jsonschema: 1.5.0
|
||||
katex: 0.16.45
|
||||
knip: 6.12.1
|
||||
knip: 6.13.1
|
||||
ky: 2.0.2
|
||||
lamejs: 1.2.1
|
||||
lexical: 0.44.0
|
||||
loro-crdt: 1.12.1
|
||||
mermaid: 11.14.0
|
||||
mermaid: 11.15.0
|
||||
mime: 4.1.0
|
||||
mitt: 3.0.1
|
||||
negotiator: 1.0.0
|
||||
@@ -195,7 +199,7 @@ catalog:
|
||||
next-themes: 0.4.6
|
||||
nuqs: 2.8.9
|
||||
pinyin-pro: 3.28.1
|
||||
playwright: 1.59.1
|
||||
playwright: 1.60.0
|
||||
postcss: 8.5.14
|
||||
qrcode.react: 4.2.0
|
||||
qs: 6.15.1
|
||||
@@ -223,8 +227,8 @@ catalog:
|
||||
storybook: 10.3.6
|
||||
streamdown: 2.5.0
|
||||
string-ts: 2.3.1
|
||||
tailwind-merge: 3.5.0
|
||||
tailwindcss: 4.2.4
|
||||
tailwind-merge: 3.6.0
|
||||
tailwindcss: 4.3.0
|
||||
tldts: 7.0.30
|
||||
tsx: 4.21.0
|
||||
typescript: 6.0.3
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { DSLExportConfirmContent } from '@/app/components/workflow/dsl-export-confirm-modal'
|
||||
import dynamic from '@/next/dynamic'
|
||||
@@ -157,7 +157,14 @@ const AppInfoModals = ({
|
||||
</AlertDialogDescription>
|
||||
<div className="mt-2">
|
||||
<label className="mb-1 block system-sm-regular text-text-secondary">
|
||||
{t('deleteAppConfirmInputLabel', { ns: 'app', appName: appDetail.name })}
|
||||
<Trans
|
||||
i18nKey="deleteAppConfirmInputLabel"
|
||||
ns="app"
|
||||
values={{ appName: appDetail.name }}
|
||||
components={{
|
||||
appName: <span className="system-sm-semibold text-text-primary" translate="no" />,
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
|
||||
@@ -44,7 +44,7 @@ type PlaygroundFormFieldsProps = {
|
||||
const PlaygroundFormFields = ({ form, status }: PlaygroundFormFieldsProps) => {
|
||||
type PlaygroundFormValues = typeof demoFormOpts.defaultValues
|
||||
const name = useStore(form.store, state => (state.values as PlaygroundFormValues).name)
|
||||
const contactFormApi = form as ContactFieldsFormApi
|
||||
const contactFormApi = form as unknown as ContactFieldsFormApi
|
||||
|
||||
return (
|
||||
<form
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react'
|
||||
import { HITL_INPUT_REG } from '@/config'
|
||||
import { decoratorTransform } from '../../utils'
|
||||
@@ -39,6 +40,32 @@ const HITLInputReplacementBlock = ({
|
||||
acc.push(...curr.vars.filter(v => v.isRagVariable))
|
||||
return acc
|
||||
}, []), [variables])
|
||||
const latestConfigRef = useRef({
|
||||
nodeId,
|
||||
formInputs,
|
||||
onFormInputsChange,
|
||||
onFormInputItemRename,
|
||||
onFormInputItemRemove,
|
||||
workflowNodesMap,
|
||||
getVarType,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
ragVariables,
|
||||
readonly,
|
||||
})
|
||||
latestConfigRef.current = {
|
||||
nodeId,
|
||||
formInputs,
|
||||
onFormInputsChange,
|
||||
onFormInputItemRename,
|
||||
onFormInputItemRemove,
|
||||
workflowNodesMap,
|
||||
getVarType,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
ragVariables,
|
||||
readonly,
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor.hasNodes([HITLInputNode]))
|
||||
@@ -47,6 +74,20 @@ const HITLInputReplacementBlock = ({
|
||||
|
||||
const createHITLInputBlockNode = useCallback((textNode: TextNode): HITLInputNode => {
|
||||
const varName = textNode.getTextContent().split('.')[1]!.replace(/#\}\}$/, '')
|
||||
const {
|
||||
nodeId,
|
||||
formInputs,
|
||||
onFormInputsChange,
|
||||
onFormInputItemRename,
|
||||
onFormInputItemRemove,
|
||||
workflowNodesMap,
|
||||
getVarType,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
ragVariables,
|
||||
readonly,
|
||||
} = latestConfigRef.current
|
||||
|
||||
return $applyNodeReplacement($createHITLInputNode(
|
||||
varName,
|
||||
nodeId,
|
||||
@@ -61,7 +102,7 @@ const HITLInputReplacementBlock = ({
|
||||
ragVariables,
|
||||
readonly,
|
||||
))
|
||||
}, [nodeId, formInputs, onFormInputsChange, onFormInputItemRename, onFormInputItemRemove, workflowNodesMap, getVarType, environmentVariables, conversationVariables, ragVariables, readonly])
|
||||
}, [])
|
||||
|
||||
const getMatch = useCallback((text: string) => {
|
||||
const matchArr = REGEX.exec(text)
|
||||
|
||||
@@ -188,7 +188,7 @@ describe('ShortcutsPopupPlugin', () => {
|
||||
const portalContent = await screen.findByText(SHORTCUTS_EMPTY_CONTENT)
|
||||
const floatingDiv = portalContent.closest('div')
|
||||
expect(document.body).toContainElement(portalContent)
|
||||
expect(floatingDiv).toHaveClass('z-1002')
|
||||
expect(floatingDiv).toHaveStyle({ zIndex: '50' })
|
||||
})
|
||||
|
||||
// ─── matchHotkey: string hotkey ───
|
||||
|
||||
@@ -160,8 +160,7 @@ export default function ShortcutsPopupPlugin({
|
||||
Object.assign(elements.floating.style, {
|
||||
maxWidth: `${Math.min(400, availableWidth)}px`,
|
||||
maxHeight: `${Math.max(0, availableHeight)}px`,
|
||||
overflowX: 'hidden',
|
||||
overflowY: 'auto',
|
||||
overflow: 'visible',
|
||||
})
|
||||
},
|
||||
padding: 8,
|
||||
@@ -300,12 +299,13 @@ export default function ShortcutsPopupPlugin({
|
||||
refs.setFloating(node)
|
||||
}}
|
||||
className={cn(
|
||||
useContainer ? '' : 'z-1002',
|
||||
'absolute rounded-xl bg-components-panel-bg-blur shadow-lg',
|
||||
className,
|
||||
)}
|
||||
style={{
|
||||
...floatingStyles,
|
||||
zIndex: useContainer ? undefined : 50,
|
||||
overflow: 'visible',
|
||||
visibility: isPositioned ? 'visible' : 'hidden',
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import type {
|
||||
EditorState,
|
||||
LexicalCommand,
|
||||
} from 'lexical'
|
||||
import type { EditorState } from 'lexical'
|
||||
import type { FC } from 'react'
|
||||
import type { Hotkey } from './plugins/shortcuts-popup-plugin'
|
||||
import type { Hotkey, ShortcutPopupInsertHandler } from './plugins/shortcuts-popup-plugin'
|
||||
import type {
|
||||
ContextBlockType,
|
||||
CurrentBlockType,
|
||||
@@ -71,7 +68,7 @@ import {
|
||||
|
||||
type ShortcutPopup = {
|
||||
hotkey: Hotkey
|
||||
Popup: React.ComponentType<{ onClose: () => void, onInsert: (command: LexicalCommand<unknown>, params: unknown[]) => void }>
|
||||
Popup: React.ComponentType<{ onClose: () => void, onInsert: ShortcutPopupInsertHandler }>
|
||||
}
|
||||
|
||||
type PromptEditorContentProps = {
|
||||
|
||||
@@ -118,6 +118,23 @@ describe('ModelSelector', () => {
|
||||
expect(triggerButton).toHaveAttribute('aria-expanded', 'false')
|
||||
})
|
||||
|
||||
it('should use the default model settings popup width when the trigger is narrow', () => {
|
||||
renderWithQueryClient(
|
||||
<div className="w-[355px]">
|
||||
<ModelSelector modelList={[makeModel()]} />
|
||||
</div>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('combobox'))
|
||||
|
||||
expect(
|
||||
Array.from(document.body.querySelectorAll('[class]')).some(element =>
|
||||
element.className.includes('w-[432px]')
|
||||
&& element.className.includes('max-w-[432px]'),
|
||||
),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('should not open popup when readonly', () => {
|
||||
renderWithQueryClient(<ModelSelector modelList={[makeModel()]} readonly />)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ReactElement, ReactNode } from 'react'
|
||||
import type { DefaultModel, Model, ModelItem } from '../../declarations'
|
||||
import { Combobox } from '@langgenius/dify-ui/combobox'
|
||||
import { createPreviewCardHandle } from '@langgenius/dify-ui/preview-card'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import {
|
||||
ConfigurationMethodEnum,
|
||||
@@ -106,6 +107,11 @@ const makeProvider = (overrides: Record<string, unknown> = {}) => ({
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const previewCardProps = () => ({
|
||||
previewCardHandle: createPreviewCardHandle(),
|
||||
onPreviewCardClose: vi.fn(),
|
||||
})
|
||||
|
||||
const createComboboxNode = (
|
||||
node: ReactElement,
|
||||
onValueChange = vi.fn(),
|
||||
@@ -152,7 +158,7 @@ describe('PopupItem', () => {
|
||||
})
|
||||
|
||||
const { container } = renderWithCombobox(
|
||||
<PopupItem model={makeModel()} onHide={vi.fn()} />,
|
||||
<PopupItem {...previewCardProps()} model={makeModel()} onHide={vi.fn()} />,
|
||||
)
|
||||
|
||||
expect(container.textContent).toBe('')
|
||||
@@ -160,7 +166,7 @@ describe('PopupItem', () => {
|
||||
|
||||
it('should select the combobox value when clicking an active model', () => {
|
||||
const onValueChange = vi.fn()
|
||||
renderWithCombobox(<PopupItem model={makeModel()} onHide={vi.fn()} />, onValueChange)
|
||||
renderWithCombobox(<PopupItem {...previewCardProps()} model={makeModel()} onHide={vi.fn()} />, onValueChange)
|
||||
|
||||
fireEvent.click(screen.getByText('GPT-4'))
|
||||
|
||||
@@ -170,10 +176,27 @@ describe('PopupItem', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('should close the shared preview before pressing an active model', () => {
|
||||
const onPreviewCardClose = vi.fn()
|
||||
renderWithCombobox(
|
||||
<PopupItem
|
||||
previewCardHandle={createPreviewCardHandle()}
|
||||
onPreviewCardClose={onPreviewCardClose}
|
||||
model={makeModel()}
|
||||
onHide={vi.fn()}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.pointerDown(screen.getByText('GPT-4'))
|
||||
|
||||
expect(onPreviewCardClose).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should not select the combobox value when model is not active', () => {
|
||||
const onValueChange = vi.fn()
|
||||
renderWithCombobox(
|
||||
<PopupItem
|
||||
{...previewCardProps()}
|
||||
model={makeModel({ models: [makeModelItem({ status: ModelStatusEnum.disabled })] })}
|
||||
onHide={vi.fn()}
|
||||
/>,
|
||||
@@ -188,7 +211,7 @@ describe('PopupItem', () => {
|
||||
it('should open model modal when clicking add on unconfigured model', () => {
|
||||
const onValueChange = vi.fn()
|
||||
const { rerender } = renderWithCombobox(
|
||||
<PopupItem model={makeModel({ models: [makeModelItem({ status: ModelStatusEnum.noConfigure })] })} onHide={vi.fn()} />,
|
||||
<PopupItem {...previewCardProps()} model={makeModel({ models: [makeModelItem({ status: ModelStatusEnum.noConfigure })] })} onHide={vi.fn()} />,
|
||||
onValueChange,
|
||||
)
|
||||
|
||||
@@ -206,6 +229,7 @@ describe('PopupItem', () => {
|
||||
|
||||
rerender(createComboboxNode(
|
||||
<PopupItem
|
||||
{...previewCardProps()}
|
||||
model={makeModel({
|
||||
models: [makeModelItem({ status: ModelStatusEnum.noConfigure, model_type: undefined as unknown as ModelTypeEnum })],
|
||||
})}
|
||||
@@ -225,6 +249,7 @@ describe('PopupItem', () => {
|
||||
const defaultModel: DefaultModel = { provider: 'openai', model: 'gpt-4' }
|
||||
renderWithCombobox(
|
||||
<PopupItem
|
||||
{...previewCardProps()}
|
||||
defaultModel={defaultModel}
|
||||
model={makeModel()}
|
||||
onHide={vi.fn()}
|
||||
@@ -239,6 +264,7 @@ describe('PopupItem', () => {
|
||||
|
||||
renderWithCombobox(
|
||||
<PopupItem
|
||||
{...previewCardProps()}
|
||||
model={makeModel({
|
||||
label: { en_US: 'OpenAI only' } as Model['label'],
|
||||
models: [makeModelItem({ label: { en_US: 'GPT-4 only' } as ModelItem['label'] })],
|
||||
@@ -252,7 +278,7 @@ describe('PopupItem', () => {
|
||||
})
|
||||
|
||||
it('should toggle collapsed state when clicking provider header', () => {
|
||||
renderWithCombobox(<PopupItem model={makeModel()} onHide={vi.fn()} />)
|
||||
renderWithCombobox(<PopupItem {...previewCardProps()} model={makeModel()} onHide={vi.fn()} />)
|
||||
|
||||
expect(screen.getByText('GPT-4'))!.toBeInTheDocument()
|
||||
|
||||
@@ -266,7 +292,7 @@ describe('PopupItem', () => {
|
||||
})
|
||||
|
||||
it('should show credential name when using custom provider', () => {
|
||||
renderWithCombobox(<PopupItem model={makeModel()} onHide={vi.fn()} />)
|
||||
renderWithCombobox(<PopupItem {...previewCardProps()} model={makeModel()} onHide={vi.fn()} />)
|
||||
|
||||
expect(screen.getByText('my-api-key'))!.toBeInTheDocument()
|
||||
})
|
||||
@@ -283,7 +309,7 @@ describe('PopupItem', () => {
|
||||
credits: 200,
|
||||
})
|
||||
|
||||
renderWithCombobox(<PopupItem model={makeModel()} onHide={vi.fn()} />)
|
||||
renderWithCombobox(<PopupItem {...previewCardProps()} model={makeModel()} onHide={vi.fn()} />)
|
||||
|
||||
expect(screen.getByText('stale-key'))!.toBeInTheDocument()
|
||||
expect(document.querySelector('.bg-components-badge-status-light-error-bg')).not.toBeNull()
|
||||
@@ -309,7 +335,7 @@ describe('PopupItem', () => {
|
||||
credits: 0,
|
||||
})
|
||||
|
||||
renderWithCombobox(<PopupItem model={makeModel()} onHide={vi.fn()} />)
|
||||
renderWithCombobox(<PopupItem {...previewCardProps()} model={makeModel()} onHide={vi.fn()} />)
|
||||
|
||||
expect(screen.getByText(/modelProvider\.selector\.configureRequired/))!.toBeInTheDocument()
|
||||
})
|
||||
@@ -331,7 +357,7 @@ describe('PopupItem', () => {
|
||||
credits: 200,
|
||||
})
|
||||
|
||||
renderWithCombobox(<PopupItem model={makeModel()} onHide={vi.fn()} />)
|
||||
renderWithCombobox(<PopupItem {...previewCardProps()} model={makeModel()} onHide={vi.fn()} />)
|
||||
|
||||
expect(screen.getByText(/modelProvider\.selector\.aiCredits/))!.toBeInTheDocument()
|
||||
})
|
||||
@@ -356,7 +382,7 @@ describe('PopupItem', () => {
|
||||
credits: 0,
|
||||
})
|
||||
|
||||
renderWithCombobox(<PopupItem model={makeModel()} onHide={vi.fn()} />)
|
||||
renderWithCombobox(<PopupItem {...previewCardProps()} model={makeModel()} onHide={vi.fn()} />)
|
||||
|
||||
expect(screen.getByText(/modelProvider\.selector\.creditsExhausted/))!.toBeInTheDocument()
|
||||
})
|
||||
@@ -364,7 +390,7 @@ describe('PopupItem', () => {
|
||||
it('should close the dropdown through dropdown content callbacks', () => {
|
||||
const onHide = vi.fn()
|
||||
|
||||
renderWithCombobox(<PopupItem model={makeModel()} onHide={onHide} />)
|
||||
renderWithCombobox(<PopupItem {...previewCardProps()} model={makeModel()} onHide={onHide} />)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /my-api-key/ }))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'close dropdown' }))
|
||||
|
||||
@@ -130,7 +130,7 @@ function ModelSelector({
|
||||
<ComboboxContent
|
||||
placement="bottom-start"
|
||||
sideOffset={4}
|
||||
popupClassName={cn('min-w-[320px] overflow-hidden rounded-xl', popupClassName)}
|
||||
popupClassName={cn('w-[432px] max-w-[432px] overflow-hidden rounded-xl', popupClassName)}
|
||||
>
|
||||
<Popup
|
||||
defaultModel={defaultModel}
|
||||
|
||||
@@ -1,32 +1,41 @@
|
||||
import type { DefaultModel, Model } from '../declarations'
|
||||
import type { ComponentProps } from 'react'
|
||||
import type { DefaultModel, Model, ModelItem } from '../declarations'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { ComboboxGroup, ComboboxItem, ComboboxItemIndicator } from '@langgenius/dify-ui/combobox'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
import { PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
|
||||
import { PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { CreditsCoin } from '@/app/components/base/icons/src/vender/line/financeAndECommerce'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { ConfigurationMethodEnum, ModelFeatureEnum, ModelStatusEnum, ModelTypeEnum } from '../declarations'
|
||||
import { ConfigurationMethodEnum, ModelStatusEnum } from '../declarations'
|
||||
import { useLanguage, useUpdateModelList, useUpdateModelProviders } from '../hooks'
|
||||
import ModelBadge from '../model-badge'
|
||||
import ModelIcon from '../model-icon'
|
||||
import ModelName from '../model-name'
|
||||
import DropdownContent from '../provider-added-card/model-auth-dropdown/dropdown-content'
|
||||
import { useChangeProviderPriority } from '../provider-added-card/use-change-provider-priority'
|
||||
import { useCredentialPanelState } from '../provider-added-card/use-credential-panel-state'
|
||||
import { modelTypeFormat, sizeFormat } from '../utils'
|
||||
import FeatureIcon from './feature-icon'
|
||||
|
||||
export type ModelSelectorPreviewPayload = {
|
||||
provider: Model
|
||||
modelItem: ModelItem
|
||||
}
|
||||
|
||||
type PreviewCardHandle = NonNullable<ComponentProps<typeof PreviewCardTrigger>['handle']>
|
||||
|
||||
type PopupItemProps = {
|
||||
defaultModel?: DefaultModel
|
||||
model: Model
|
||||
previewCardHandle: PreviewCardHandle
|
||||
onPreviewCardClose: () => void
|
||||
onHide: () => void
|
||||
}
|
||||
function PopupItem({
|
||||
defaultModel,
|
||||
model,
|
||||
previewCardHandle,
|
||||
onPreviewCardClose,
|
||||
onHide,
|
||||
}: PopupItemProps) {
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
@@ -167,7 +176,11 @@ function PopupItem({
|
||||
)
|
||||
const itemRender = modelItem.status === ModelStatusEnum.noConfigure
|
||||
? (
|
||||
<div className={rowClassName} aria-disabled="true">
|
||||
<div
|
||||
className={rowClassName}
|
||||
aria-disabled="true"
|
||||
onPointerDown={onPreviewCardClose}
|
||||
>
|
||||
{rowContent}
|
||||
<button
|
||||
type="button"
|
||||
@@ -186,67 +199,21 @@ function PopupItem({
|
||||
}}
|
||||
disabled={modelItem.status !== ModelStatusEnum.active}
|
||||
className={rowClassName}
|
||||
onPointerDown={onPreviewCardClose}
|
||||
>
|
||||
{rowContent}
|
||||
</ComboboxItem>
|
||||
)
|
||||
|
||||
return (
|
||||
<PreviewCard key={modelItem.model}>
|
||||
<PreviewCardTrigger
|
||||
delay={150}
|
||||
closeDelay={150}
|
||||
render={itemRender}
|
||||
/>
|
||||
<PreviewCardContent
|
||||
placement="right"
|
||||
popupClassName="w-[206px] bg-components-panel-bg-blur p-3 shadow-none backdrop-blur-xs"
|
||||
>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
<ModelIcon
|
||||
className={cn('h-5 w-5 shrink-0')}
|
||||
provider={model}
|
||||
modelName={modelItem.model}
|
||||
/>
|
||||
<div className="system-md-medium text-wrap wrap-break-word text-text-primary">{modelItem.label[language] || modelItem.label.en_US}</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{!!modelItem.model_type && (
|
||||
<ModelBadge>
|
||||
{modelTypeFormat(modelItem.model_type)}
|
||||
</ModelBadge>
|
||||
)}
|
||||
{!!modelItem.model_properties.mode && (
|
||||
<ModelBadge>
|
||||
{(modelItem.model_properties.mode as string).toLocaleUpperCase()}
|
||||
</ModelBadge>
|
||||
)}
|
||||
{!!modelItem.model_properties.context_size && (
|
||||
<ModelBadge>
|
||||
{sizeFormat(modelItem.model_properties.context_size as number)}
|
||||
</ModelBadge>
|
||||
)}
|
||||
</div>
|
||||
{[ModelTypeEnum.textGeneration, ModelTypeEnum.textEmbedding, ModelTypeEnum.rerank].includes(modelItem.model_type as ModelTypeEnum)
|
||||
&& modelItem.features?.some(feature => [ModelFeatureEnum.vision, ModelFeatureEnum.audio, ModelFeatureEnum.video, ModelFeatureEnum.document].includes(feature))
|
||||
&& (
|
||||
<div className="pt-2">
|
||||
<div className="mb-1 system-2xs-medium-uppercase text-text-tertiary">{t('model.capabilities', { ns: 'common' })}</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{modelItem.features?.map(feature => (
|
||||
<FeatureIcon
|
||||
key={feature}
|
||||
feature={feature}
|
||||
showFeaturesLabel
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PreviewCardContent>
|
||||
</PreviewCard>
|
||||
<PreviewCardTrigger
|
||||
key={modelItem.model}
|
||||
delay={150}
|
||||
closeDelay={150}
|
||||
handle={previewCardHandle}
|
||||
payload={{ provider: model, modelItem }}
|
||||
render={itemRender}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</ComboboxGroup>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { DefaultModel, Model, ModelFeatureEnum } from '../declarations'
|
||||
import type { DefaultModel, Model } from '../declarations'
|
||||
import type { ModelSelectorPreviewPayload } from './popup-item'
|
||||
import type { ModelProviderQuotaGetPaid } from '@/types/model-provider'
|
||||
import { ComboboxList } from '@langgenius/dify-ui/combobox'
|
||||
import { createPreviewCardHandle, PreviewCard, PreviewCardContent } from '@langgenius/dify-ui/preview-card'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
@@ -12,12 +14,15 @@ import { useModalContext } from '@/context/modal-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { systemFeaturesQueryOptions } from '@/service/system-features'
|
||||
import { useInstallPackageFromMarketPlace } from '@/service/use-plugins'
|
||||
import { CustomConfigurationStatusEnum, ModelStatusEnum } from '../declarations'
|
||||
import { CustomConfigurationStatusEnum, ModelFeatureEnum, ModelStatusEnum, ModelTypeEnum } from '../declarations'
|
||||
import { useLanguage, useMarketplaceAllPlugins } from '../hooks'
|
||||
import ModelBadge from '../model-badge'
|
||||
import ModelIcon from '../model-icon'
|
||||
import CreditsExhaustedAlert from '../provider-added-card/model-auth-dropdown/credits-exhausted-alert'
|
||||
import { useTrialCredits } from '../provider-added-card/use-trial-credits'
|
||||
import { providerSupportsCredits } from '../supports-credits'
|
||||
import { MODEL_PROVIDER_QUOTA_GET_PAID, providerKeyToPluginId } from '../utils'
|
||||
import { MODEL_PROVIDER_QUOTA_GET_PAID, modelTypeFormat, providerKeyToPluginId, sizeFormat } from '../utils'
|
||||
import FeatureIcon from './feature-icon'
|
||||
import MarketplaceSection from './marketplace-section'
|
||||
import { createModelSelectorSearchIndex, filterModelSelectorModels } from './model-search'
|
||||
import ModelSelectorEmptyState from './popup-empty-state'
|
||||
@@ -43,6 +48,7 @@ function Popup({
|
||||
const { t } = useTranslation()
|
||||
const { theme } = useTheme()
|
||||
const language = useLanguage()
|
||||
const previewCardHandle = useMemo(() => createPreviewCardHandle<ModelSelectorPreviewPayload>(), [])
|
||||
const [marketplaceCollapsed, setMarketplaceCollapsed] = useState(false)
|
||||
const { setShowAccountSettingModal } = useModalContext()
|
||||
const { modelProviders } = useProviderContext()
|
||||
@@ -151,6 +157,9 @@ function Popup({
|
||||
onHide()
|
||||
setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.PROVIDER })
|
||||
}, [onHide, setShowAccountSettingModal])
|
||||
const handleClosePreviewCard = useCallback(() => {
|
||||
previewCardHandle.close()
|
||||
}, [previewCardHandle])
|
||||
|
||||
return (
|
||||
<ModelSelectorPopupFrame>
|
||||
@@ -170,6 +179,8 @@ function Popup({
|
||||
key={model.provider}
|
||||
defaultModel={defaultModel}
|
||||
model={model}
|
||||
previewCardHandle={previewCardHandle}
|
||||
onPreviewCardClose={handleClosePreviewCard}
|
||||
onHide={onHide}
|
||||
/>
|
||||
))
|
||||
@@ -201,9 +212,86 @@ function Popup({
|
||||
/>
|
||||
</div>
|
||||
</ModelSelectorScrollBody>
|
||||
<PreviewCard handle={previewCardHandle}>
|
||||
{({ payload }) => (
|
||||
<ModelSelectorPreviewCard
|
||||
capabilitiesLabel={t('model.capabilities', { ns: 'common' })}
|
||||
language={language}
|
||||
payload={payload as ModelSelectorPreviewPayload | undefined}
|
||||
/>
|
||||
)}
|
||||
</PreviewCard>
|
||||
<ModelProviderSettingsFooter onOpenSettings={handleOpenSettings} />
|
||||
</ModelSelectorPopupFrame>
|
||||
)
|
||||
}
|
||||
|
||||
type ModelSelectorPreviewCardProps = {
|
||||
capabilitiesLabel: string
|
||||
language: string
|
||||
payload?: ModelSelectorPreviewPayload
|
||||
}
|
||||
|
||||
function ModelSelectorPreviewCard({
|
||||
capabilitiesLabel,
|
||||
language,
|
||||
payload,
|
||||
}: ModelSelectorPreviewCardProps) {
|
||||
if (!payload)
|
||||
return null
|
||||
|
||||
const { provider, modelItem } = payload
|
||||
|
||||
return (
|
||||
<PreviewCardContent
|
||||
placement="right"
|
||||
popupClassName="w-[206px] bg-components-panel-bg-blur p-3 shadow-none backdrop-blur-xs"
|
||||
>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
<ModelIcon
|
||||
className="h-5 w-5 shrink-0"
|
||||
provider={provider}
|
||||
modelName={modelItem.model}
|
||||
/>
|
||||
<div className="system-md-medium text-wrap wrap-break-word text-text-primary">{modelItem.label[language] || modelItem.label.en_US}</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{!!modelItem.model_type && (
|
||||
<ModelBadge>
|
||||
{modelTypeFormat(modelItem.model_type)}
|
||||
</ModelBadge>
|
||||
)}
|
||||
{!!modelItem.model_properties.mode && (
|
||||
<ModelBadge>
|
||||
{(modelItem.model_properties.mode as string).toLocaleUpperCase()}
|
||||
</ModelBadge>
|
||||
)}
|
||||
{!!modelItem.model_properties.context_size && (
|
||||
<ModelBadge>
|
||||
{sizeFormat(modelItem.model_properties.context_size as number)}
|
||||
</ModelBadge>
|
||||
)}
|
||||
</div>
|
||||
{[ModelTypeEnum.textGeneration, ModelTypeEnum.textEmbedding, ModelTypeEnum.rerank].includes(modelItem.model_type as ModelTypeEnum)
|
||||
&& modelItem.features?.some(feature => [ModelFeatureEnum.vision, ModelFeatureEnum.audio, ModelFeatureEnum.video, ModelFeatureEnum.document].includes(feature))
|
||||
&& (
|
||||
<div className="pt-2">
|
||||
<div className="mb-1 system-2xs-medium-uppercase text-text-tertiary">{capabilitiesLabel}</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{modelItem.features?.map(feature => (
|
||||
<FeatureIcon
|
||||
key={feature}
|
||||
feature={feature}
|
||||
showFeaturesLabel
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PreviewCardContent>
|
||||
)
|
||||
}
|
||||
|
||||
export default Popup
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { FC } from 'react'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { RiAlertFill } from '@remixicon/react'
|
||||
import { camelCase } from 'es-toolkit/string'
|
||||
import * as React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { useTranslation } from '#i18n'
|
||||
import Link from '@/next/link'
|
||||
|
||||
type DeprecationNoticeProps = {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client'
|
||||
import type { Plugin } from '../types'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { RiAlertFill } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { useSelector } from '@/context/app-context'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { Group } from '@/app/components/base/icons/src/vender/other'
|
||||
import Line from './line'
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
'use client'
|
||||
import type { Plugin } from '@/app/components/plugins/types'
|
||||
import { useLocale, useTranslation } from '#i18n'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { RiArrowRightUpLine } from '@remixicon/react'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { useTheme } from 'next-themes'
|
||||
import * as React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useLocale, useTranslation } from '#i18n'
|
||||
import Card from '@/app/components/plugins/card'
|
||||
import CardMoreInfo from '@/app/components/plugins/card/card-more-info'
|
||||
import { useTags } from '@/app/components/plugins/hooks'
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
import type { MarketplaceCollection } from '../types'
|
||||
import type { Plugin } from '@/app/components/plugins/types'
|
||||
import { useLocale, useTranslation } from '#i18n'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { RiArrowRightSLine } from '@remixicon/react'
|
||||
import { useLocale, useTranslation } from '#i18n'
|
||||
import { getLanguage } from '@/i18n-config/language'
|
||||
import { useMarketplaceMoreClick } from '../atoms'
|
||||
import CardWrapper from './card-wrapper'
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
'use client'
|
||||
import type { ActivePluginType } from './constants'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
RiArchive2Line,
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
RiSpeakAiLine,
|
||||
} from '@remixicon/react'
|
||||
import { useSetAtom } from 'jotai'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { Trigger as TriggerIcon } from '@/app/components/base/icons/src/vender/plugin'
|
||||
import { searchModeAtom, useActivePluginType } from './atoms'
|
||||
import { PLUGIN_CATEGORY_WITH_COLLECTIONS, PLUGIN_TYPE_SEARCH_MAP } from './constants'
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
'use client'
|
||||
|
||||
import { useTranslation } from '#i18n'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@langgenius/dify-ui/popover'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from '#i18n'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { useTags } from '@/app/components/plugins/hooks'
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Tag } from '../../../hooks'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { RiArrowDownSLine, RiCloseCircleFill, RiFilter3Line } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from '#i18n'
|
||||
|
||||
type MarketplaceTriggerProps = {
|
||||
selectedTagsLength: number
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
'use client'
|
||||
import { useTranslation } from '#i18n'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -7,6 +6,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { useMarketplaceSort } from '../atoms'
|
||||
|
||||
const SortDropdown = () => {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { useMarketplace } from './hooks'
|
||||
import { useLocale } from '#i18n'
|
||||
import {
|
||||
RiArrowRightUpLine,
|
||||
RiArrowUpDoubleLine,
|
||||
} from '@remixicon/react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useLocale } from '#i18n'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import List from '@/app/components/plugins/marketplace/list'
|
||||
import { getMarketplaceUrl } from '@/utils/var'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { NodeDefault } from '../types'
|
||||
import type { BlockClassificationEnum } from './types'
|
||||
import {
|
||||
createPreviewCardHandle,
|
||||
PreviewCard,
|
||||
PreviewCardContent,
|
||||
PreviewCardTrigger,
|
||||
@@ -27,6 +28,10 @@ type BlocksProps = {
|
||||
availableBlocksTypes?: BlockEnum[]
|
||||
blocks?: NodeDefault[]
|
||||
}
|
||||
type BlockPreviewPayload = {
|
||||
block: NodeDefault
|
||||
}
|
||||
|
||||
const Blocks = ({
|
||||
searchText,
|
||||
onSelect,
|
||||
@@ -43,6 +48,7 @@ const Blocks = ({
|
||||
|
||||
return filterEvaluationWorkflowRestrictedBlockTypes(availableBlocksTypes)
|
||||
}, [appType, availableBlocksTypes])
|
||||
const previewCardHandle = useMemo(() => createPreviewCardHandle<BlockPreviewPayload>(), [])
|
||||
|
||||
// Use external blocks if provided, otherwise fallback to hook-based blocks
|
||||
const blocks = blocksFromProps || blocksFromHooks.map(block => ({
|
||||
@@ -110,51 +116,38 @@ const Blocks = ({
|
||||
// hover/focus-only activation is a11y-safe. See
|
||||
// packages/dify-ui/AGENTS.md → Overlay Primitive Selection.
|
||||
filteredList.map(block => (
|
||||
<PreviewCard key={block.metaData.type}>
|
||||
<PreviewCardTrigger
|
||||
delay={150}
|
||||
closeDelay={150}
|
||||
render={(
|
||||
<div
|
||||
className="flex h-8 w-full cursor-pointer items-center rounded-lg px-3 hover:bg-state-base-hover"
|
||||
onClick={() => onSelect(block.metaData.type)}
|
||||
>
|
||||
<BlockIcon
|
||||
className="mr-2 shrink-0"
|
||||
type={block.metaData.type}
|
||||
/>
|
||||
<div className="grow text-sm text-text-secondary">{block.metaData.title}</div>
|
||||
{
|
||||
block.metaData.type === BlockEnum.LoopEnd && (
|
||||
<Badge
|
||||
text={t('nodes.loop.loopNode', { ns: 'workflow' })}
|
||||
className="ml-2 shrink-0"
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<PreviewCardContent
|
||||
placement="right"
|
||||
popupClassName="w-[200px] border-none px-3 py-2"
|
||||
>
|
||||
<div>
|
||||
<PreviewCardTrigger
|
||||
key={block.metaData.type}
|
||||
delay={150}
|
||||
closeDelay={150}
|
||||
handle={previewCardHandle}
|
||||
payload={{ block }}
|
||||
render={(
|
||||
<div
|
||||
className="flex h-8 w-full cursor-pointer items-center rounded-lg px-3 hover:bg-state-base-hover"
|
||||
onClick={() => onSelect(block.metaData.type)}
|
||||
>
|
||||
<BlockIcon
|
||||
size="md"
|
||||
className="mb-2"
|
||||
className="mr-2 shrink-0"
|
||||
type={block.metaData.type}
|
||||
/>
|
||||
<div className="mb-1 system-md-medium text-text-primary">{block.metaData.title}</div>
|
||||
<div className="system-xs-regular text-text-tertiary">{block.metaData.description}</div>
|
||||
<div className="grow text-sm text-text-secondary">{block.metaData.title}</div>
|
||||
{
|
||||
block.metaData.type === BlockEnum.LoopEnd && (
|
||||
<Badge
|
||||
text={t('nodes.loop.loopNode', { ns: 'workflow' })}
|
||||
className="ml-2 shrink-0"
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</PreviewCardContent>
|
||||
</PreviewCard>
|
||||
)}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}, [groups, onSelect, t, store])
|
||||
}, [groups, onSelect, previewCardHandle, t, store])
|
||||
|
||||
return (
|
||||
<div className="max-h-[480px] max-w-[500px] overflow-y-auto p-1">
|
||||
@@ -166,8 +159,43 @@ const Blocks = ({
|
||||
{
|
||||
!isEmpty && BLOCK_CLASSIFICATIONS.map(renderGroup)
|
||||
}
|
||||
<PreviewCard handle={previewCardHandle}>
|
||||
{({ payload }) => (
|
||||
<BlockPreviewCard payload={payload as BlockPreviewPayload | undefined} />
|
||||
)}
|
||||
</PreviewCard>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type BlockPreviewCardProps = {
|
||||
payload?: BlockPreviewPayload
|
||||
}
|
||||
|
||||
function BlockPreviewCard({
|
||||
payload,
|
||||
}: BlockPreviewCardProps) {
|
||||
if (!payload)
|
||||
return null
|
||||
|
||||
const { block } = payload
|
||||
|
||||
return (
|
||||
<PreviewCardContent
|
||||
placement="right"
|
||||
popupClassName="w-[200px] border-none px-3 py-2"
|
||||
>
|
||||
<div>
|
||||
<BlockIcon
|
||||
size="md"
|
||||
className="mb-2"
|
||||
type={block.metaData.type}
|
||||
/>
|
||||
<div className="mb-1 system-md-medium text-text-primary">{block.metaData.title}</div>
|
||||
<div className="system-xs-regular wrap-break-word text-text-tertiary">{block.metaData.description}</div>
|
||||
</div>
|
||||
</PreviewCardContent>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Blocks)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
'use client'
|
||||
import type { TFunction } from 'i18next'
|
||||
import type { ToolWithProvider } from '../types'
|
||||
import type { ToolDefaultValue, ToolValue } from './types'
|
||||
import type { Plugin } from '@/app/components/plugins/types'
|
||||
import type { Locale } from '@/i18n-config'
|
||||
import { PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
|
||||
import { createPreviewCardHandle, PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
|
||||
import { RiMoreLine } from '@remixicon/react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -33,6 +34,11 @@ type FeaturedToolsProps = {
|
||||
isLoading?: boolean
|
||||
onInstallSuccess?: () => void
|
||||
}
|
||||
type FeaturedToolPreviewPayload = {
|
||||
plugin: Plugin
|
||||
label: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'workflow_tools_featured_collapsed'
|
||||
|
||||
@@ -46,7 +52,9 @@ const FeaturedTools = ({
|
||||
}: FeaturedToolsProps) => {
|
||||
const { t } = useTranslation()
|
||||
const language = useGetLanguage()
|
||||
const previewCardHandle = useMemo(() => createPreviewCardHandle<FeaturedToolPreviewPayload>(), [])
|
||||
const [visibleCount, setVisibleCount] = useState(INITIAL_VISIBLE_COUNT)
|
||||
const [visibleCountPlugins, setVisibleCountPlugins] = useState(plugins)
|
||||
const [isCollapsed, setIsCollapsed] = useState<boolean>(() => {
|
||||
if (isServer)
|
||||
return false
|
||||
@@ -54,23 +62,16 @@ const FeaturedTools = ({
|
||||
return stored === 'true'
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (isServer)
|
||||
return
|
||||
const stored = window.localStorage.getItem(STORAGE_KEY)
|
||||
if (stored !== null)
|
||||
setIsCollapsed(stored === 'true')
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (isServer)
|
||||
return
|
||||
window.localStorage.setItem(STORAGE_KEY, String(isCollapsed))
|
||||
}, [isCollapsed])
|
||||
|
||||
useEffect(() => {
|
||||
if (visibleCountPlugins !== plugins) {
|
||||
setVisibleCountPlugins(plugins)
|
||||
setVisibleCount(INITIAL_VISIBLE_COUNT)
|
||||
}, [plugins])
|
||||
}
|
||||
|
||||
const limitedPlugins = useMemo(
|
||||
() => plugins.slice(0, MAX_RECOMMENDED_COUNT),
|
||||
@@ -174,10 +175,11 @@ const FeaturedTools = ({
|
||||
key={plugin.plugin_id}
|
||||
plugin={plugin}
|
||||
language={language}
|
||||
previewCardHandle={previewCardHandle}
|
||||
onInstallSuccess={async () => {
|
||||
await onInstallSuccess?.()
|
||||
}}
|
||||
t={t as any}
|
||||
t={t}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -214,6 +216,11 @@ const FeaturedTools = ({
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<PreviewCard handle={previewCardHandle}>
|
||||
{({ payload }) => (
|
||||
<FeaturedToolPreviewCard payload={payload as FeaturedToolPreviewPayload | undefined} />
|
||||
)}
|
||||
</PreviewCard>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -221,13 +228,15 @@ const FeaturedTools = ({
|
||||
type FeaturedToolUninstalledItemProps = {
|
||||
plugin: Plugin
|
||||
language: Locale
|
||||
previewCardHandle: ReturnType<typeof createPreviewCardHandle<FeaturedToolPreviewPayload>>
|
||||
onInstallSuccess?: () => Promise<void> | void
|
||||
t: (key: string, options?: Record<string, any>) => string
|
||||
t: TFunction
|
||||
}
|
||||
|
||||
function FeaturedToolUninstalledItem({
|
||||
plugin,
|
||||
language,
|
||||
previewCardHandle,
|
||||
onInstallSuccess,
|
||||
t,
|
||||
}: FeaturedToolUninstalledItemProps) {
|
||||
@@ -296,16 +305,13 @@ function FeaturedToolUninstalledItem({
|
||||
// Preview is supplementary: icon / label / brief are all reachable from
|
||||
// the InstallFromMarketplace modal that opens on click, so hover/focus-only
|
||||
// activation is a11y-safe. See packages/dify-ui/AGENTS.md → Overlay Primitive Selection.
|
||||
<PreviewCard>
|
||||
<PreviewCardTrigger delay={150} closeDelay={150} render={row} />
|
||||
<PreviewCardContent placement="right" popupClassName="w-[224px] px-3 py-2.5">
|
||||
<div>
|
||||
<BlockIcon size="md" className="mb-2" type={BlockEnum.Tool} toolIcon={plugin.icon} />
|
||||
<div className="mb-1 text-sm leading-5 text-text-primary">{label}</div>
|
||||
<div className="text-xs leading-[18px] text-text-secondary">{description}</div>
|
||||
</div>
|
||||
</PreviewCardContent>
|
||||
</PreviewCard>
|
||||
<PreviewCardTrigger
|
||||
delay={150}
|
||||
closeDelay={150}
|
||||
handle={previewCardHandle}
|
||||
payload={{ plugin, label, description }}
|
||||
render={row}
|
||||
/>
|
||||
)
|
||||
: row}
|
||||
{isInstallModalOpen && (
|
||||
@@ -325,4 +331,25 @@ function FeaturedToolUninstalledItem({
|
||||
)
|
||||
}
|
||||
|
||||
type FeaturedToolPreviewCardProps = {
|
||||
payload?: FeaturedToolPreviewPayload
|
||||
}
|
||||
|
||||
function FeaturedToolPreviewCard({
|
||||
payload,
|
||||
}: FeaturedToolPreviewCardProps) {
|
||||
if (!payload)
|
||||
return null
|
||||
|
||||
return (
|
||||
<PreviewCardContent placement="right" popupClassName="w-[224px] px-3 py-2.5">
|
||||
<div>
|
||||
<BlockIcon size="md" className="mb-2" type={BlockEnum.Tool} toolIcon={payload.plugin.icon} />
|
||||
<div className="mb-1 text-sm leading-5 text-text-primary">{payload.label}</div>
|
||||
<div className="text-xs leading-[18px] wrap-break-word text-text-secondary">{payload.description}</div>
|
||||
</div>
|
||||
</PreviewCardContent>
|
||||
)
|
||||
}
|
||||
|
||||
export default FeaturedTools
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
'use client'
|
||||
import type { TFunction } from 'i18next'
|
||||
import type { TriggerPluginActionPreviewPayload } from './trigger-plugin/action-item'
|
||||
import type { TriggerDefaultValue, TriggerWithProvider } from './types'
|
||||
import type { Plugin } from '@/app/components/plugins/types'
|
||||
import type { Locale } from '@/i18n-config'
|
||||
import { PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
|
||||
import { createPreviewCardHandle, PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
|
||||
import { RiMoreLine } from '@remixicon/react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -17,6 +19,7 @@ import { formatNumber } from '@/utils/format'
|
||||
import { getMarketplaceUrl } from '@/utils/var'
|
||||
import BlockIcon from '../block-icon'
|
||||
import { BlockEnum } from '../types'
|
||||
import { TriggerPluginActionPreviewCard } from './trigger-plugin/action-item'
|
||||
import TriggerPluginItem from './trigger-plugin/item'
|
||||
|
||||
const MAX_RECOMMENDED_COUNT = 15
|
||||
@@ -29,6 +32,11 @@ type FeaturedTriggersProps = {
|
||||
isLoading?: boolean
|
||||
onInstallSuccess?: () => void | Promise<void>
|
||||
}
|
||||
type FeaturedTriggerPreviewPayload = {
|
||||
plugin: Plugin
|
||||
label: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'workflow_triggers_featured_collapsed'
|
||||
|
||||
@@ -41,7 +49,10 @@ const FeaturedTriggers = ({
|
||||
}: FeaturedTriggersProps) => {
|
||||
const { t } = useTranslation()
|
||||
const language = useGetLanguage()
|
||||
const previewCardHandle = useMemo(() => createPreviewCardHandle<FeaturedTriggerPreviewPayload>(), [])
|
||||
const triggerActionPreviewCardHandle = useMemo(() => createPreviewCardHandle<TriggerPluginActionPreviewPayload>(), [])
|
||||
const [visibleCount, setVisibleCount] = useState(INITIAL_VISIBLE_COUNT)
|
||||
const [visibleCountPlugins, setVisibleCountPlugins] = useState(plugins)
|
||||
const [isCollapsed, setIsCollapsed] = useState<boolean>(() => {
|
||||
if (isServer)
|
||||
return false
|
||||
@@ -49,23 +60,16 @@ const FeaturedTriggers = ({
|
||||
return stored === 'true'
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (isServer)
|
||||
return
|
||||
const stored = window.localStorage.getItem(STORAGE_KEY)
|
||||
if (stored !== null)
|
||||
setIsCollapsed(stored === 'true')
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (isServer)
|
||||
return
|
||||
window.localStorage.setItem(STORAGE_KEY, String(isCollapsed))
|
||||
}, [isCollapsed])
|
||||
|
||||
useEffect(() => {
|
||||
if (visibleCountPlugins !== plugins) {
|
||||
setVisibleCountPlugins(plugins)
|
||||
setVisibleCount(INITIAL_VISIBLE_COUNT)
|
||||
}, [plugins])
|
||||
}
|
||||
|
||||
const limitedPlugins = useMemo(
|
||||
() => plugins.slice(0, MAX_RECOMMENDED_COUNT),
|
||||
@@ -156,6 +160,7 @@ const FeaturedTriggers = ({
|
||||
key={provider.id}
|
||||
payload={provider}
|
||||
hasSearchText={false}
|
||||
previewCardHandle={triggerActionPreviewCardHandle}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
))}
|
||||
@@ -169,10 +174,11 @@ const FeaturedTriggers = ({
|
||||
key={plugin.plugin_id}
|
||||
plugin={plugin}
|
||||
language={language}
|
||||
previewCardHandle={previewCardHandle}
|
||||
onInstallSuccess={async () => {
|
||||
await onInstallSuccess?.()
|
||||
}}
|
||||
t={t as any}
|
||||
t={t}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -209,6 +215,16 @@ const FeaturedTriggers = ({
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<PreviewCard handle={previewCardHandle}>
|
||||
{({ payload }) => (
|
||||
<FeaturedTriggerPreviewCard payload={payload as FeaturedTriggerPreviewPayload | undefined} />
|
||||
)}
|
||||
</PreviewCard>
|
||||
<PreviewCard handle={triggerActionPreviewCardHandle}>
|
||||
{({ payload }) => (
|
||||
<TriggerPluginActionPreviewCard payload={payload as TriggerPluginActionPreviewPayload | undefined} />
|
||||
)}
|
||||
</PreviewCard>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -216,13 +232,15 @@ const FeaturedTriggers = ({
|
||||
type FeaturedTriggerUninstalledItemProps = {
|
||||
plugin: Plugin
|
||||
language: Locale
|
||||
previewCardHandle: ReturnType<typeof createPreviewCardHandle<FeaturedTriggerPreviewPayload>>
|
||||
onInstallSuccess?: () => Promise<void> | void
|
||||
t: (key: string, options?: Record<string, any>) => string
|
||||
t: TFunction
|
||||
}
|
||||
|
||||
function FeaturedTriggerUninstalledItem({
|
||||
plugin,
|
||||
language,
|
||||
previewCardHandle,
|
||||
onInstallSuccess,
|
||||
t,
|
||||
}: FeaturedTriggerUninstalledItemProps) {
|
||||
@@ -291,16 +309,13 @@ function FeaturedTriggerUninstalledItem({
|
||||
// Preview is supplementary: icon / label / brief are all reachable from
|
||||
// the InstallFromMarketplace modal that opens on click, so hover/focus-only
|
||||
// activation is a11y-safe. See packages/dify-ui/AGENTS.md → Overlay Primitive Selection.
|
||||
<PreviewCard>
|
||||
<PreviewCardTrigger delay={150} closeDelay={150} render={row} />
|
||||
<PreviewCardContent placement="right" popupClassName="w-[224px] px-3 py-2.5">
|
||||
<div>
|
||||
<BlockIcon size="md" className="mb-2" type={BlockEnum.TriggerPlugin} toolIcon={plugin.icon} />
|
||||
<div className="mb-1 text-sm leading-5 text-text-primary">{label}</div>
|
||||
<div className="text-xs leading-[18px] text-text-secondary">{description}</div>
|
||||
</div>
|
||||
</PreviewCardContent>
|
||||
</PreviewCard>
|
||||
<PreviewCardTrigger
|
||||
delay={150}
|
||||
closeDelay={150}
|
||||
handle={previewCardHandle}
|
||||
payload={{ plugin, label, description }}
|
||||
render={row}
|
||||
/>
|
||||
)
|
||||
: row}
|
||||
{isInstallModalOpen && (
|
||||
@@ -320,4 +335,25 @@ function FeaturedTriggerUninstalledItem({
|
||||
)
|
||||
}
|
||||
|
||||
type FeaturedTriggerPreviewCardProps = {
|
||||
payload?: FeaturedTriggerPreviewPayload
|
||||
}
|
||||
|
||||
function FeaturedTriggerPreviewCard({
|
||||
payload,
|
||||
}: FeaturedTriggerPreviewCardProps) {
|
||||
if (!payload)
|
||||
return null
|
||||
|
||||
return (
|
||||
<PreviewCardContent placement="right" popupClassName="w-[224px] px-3 py-2.5">
|
||||
<div>
|
||||
<BlockIcon size="md" className="mb-2" type={BlockEnum.TriggerPlugin} toolIcon={payload.plugin.icon} />
|
||||
<div className="mb-1 text-sm leading-5 text-text-primary">{payload.label}</div>
|
||||
<div className="text-xs leading-[18px] wrap-break-word text-text-secondary">{payload.description}</div>
|
||||
</div>
|
||||
</PreviewCardContent>
|
||||
)
|
||||
}
|
||||
|
||||
export default FeaturedTriggers
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import type { BlockEnum, ToolWithProvider } from '../../types'
|
||||
import type { ToolActionPreviewPayload } from '../tool/action-item'
|
||||
import type { ToolDefaultValue } from '../types'
|
||||
import type { Plugin } from '@/app/components/plugins/types'
|
||||
import type { OnSelectBlock } from '@/app/components/workflow/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { createPreviewCardHandle, PreviewCard } from '@langgenius/dify-ui/preview-card'
|
||||
import { useCallback, useMemo, useRef } from 'react'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import { groupItems } from '../index-bar'
|
||||
import { ToolActionPreviewCard } from '../tool/action-item'
|
||||
import ToolListFlatView from '../tool/tool-list-flat-view/list'
|
||||
import ToolListTreeView from '../tool/tool-list-tree-view/list'
|
||||
import { ViewType } from '../view-type-select'
|
||||
@@ -27,6 +30,7 @@ const List = ({
|
||||
className,
|
||||
}: ListProps) => {
|
||||
const language = useGetLanguage()
|
||||
const previewCardHandle = useMemo(() => createPreviewCardHandle<ToolActionPreviewPayload>(), [])
|
||||
const isFlatView = viewType === ViewType.flat
|
||||
|
||||
const { letters, groups: withLetterAndGroupViewToolsData } = groupItems(tools, tool => tool.label[language]![0]!)
|
||||
@@ -58,7 +62,7 @@ const List = ({
|
||||
return result
|
||||
}, [withLetterAndGroupViewToolsData, letters])
|
||||
|
||||
const toolRefs = useRef({})
|
||||
const toolRefsRef = useRef<Record<string, HTMLDivElement | null>>({})
|
||||
|
||||
const handleSelect = useCallback((type: BlockEnum, tool: ToolDefaultValue) => {
|
||||
onSelect(type, tool)
|
||||
@@ -70,9 +74,10 @@ const List = ({
|
||||
isFlatView
|
||||
? (
|
||||
<ToolListFlatView
|
||||
toolRefs={toolRefs}
|
||||
toolRefs={toolRefsRef}
|
||||
letters={letters}
|
||||
payload={listViewToolData}
|
||||
previewCardHandle={previewCardHandle}
|
||||
isShowLetterIndex={false}
|
||||
hasSearchText={false}
|
||||
onSelect={handleSelect}
|
||||
@@ -83,12 +88,18 @@ const List = ({
|
||||
: (
|
||||
<ToolListTreeView
|
||||
payload={treeViewToolsData}
|
||||
previewCardHandle={previewCardHandle}
|
||||
hasSearchText={false}
|
||||
onSelect={handleSelect}
|
||||
canNotSelectMultiple
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<PreviewCard handle={previewCardHandle}>
|
||||
{({ payload }) => (
|
||||
<ToolActionPreviewCard payload={payload as ToolActionPreviewPayload | undefined} />
|
||||
)}
|
||||
</PreviewCard>
|
||||
{
|
||||
unInstalledPlugins.map((item) => {
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { BlockEnum, CommonNodeType } from '../types'
|
||||
import type { TriggerDefaultValue } from './types'
|
||||
import {
|
||||
createPreviewCardHandle,
|
||||
PreviewCard,
|
||||
PreviewCardContent,
|
||||
PreviewCardTrigger,
|
||||
@@ -25,6 +26,9 @@ type StartBlocksProps = {
|
||||
onContentStateChange?: (hasContent: boolean) => void
|
||||
hideUserInput?: boolean
|
||||
}
|
||||
type StartBlockPreviewPayload = {
|
||||
block: typeof START_BLOCKS[number]
|
||||
}
|
||||
|
||||
const StartBlocks = ({
|
||||
searchText,
|
||||
@@ -35,6 +39,7 @@ const StartBlocks = ({
|
||||
}: StartBlocksProps) => {
|
||||
const { t } = useTranslation()
|
||||
const nodes = useNodes()
|
||||
const previewCardHandle = useMemo(() => createPreviewCardHandle<StartBlockPreviewPayload>(), [])
|
||||
// const nodeMetaData = useNodeMetaData()
|
||||
|
||||
const filteredBlocks = useMemo(() => {
|
||||
@@ -74,54 +79,31 @@ const StartBlocks = ({
|
||||
// the start node, so hover/focus-only activation is a11y-safe. See
|
||||
// packages/dify-ui/AGENTS.md → Overlay Primitive Selection.
|
||||
const renderBlock = useCallback((block: typeof START_BLOCKS[number]) => (
|
||||
<PreviewCard key={block.type}>
|
||||
<PreviewCardTrigger
|
||||
delay={150}
|
||||
closeDelay={150}
|
||||
render={(
|
||||
<div
|
||||
className="flex h-8 w-full cursor-pointer items-center rounded-lg px-3 hover:bg-state-base-hover"
|
||||
onClick={() => onSelect(block.type)}
|
||||
>
|
||||
<BlockIcon
|
||||
className="mr-2 shrink-0"
|
||||
type={block.type}
|
||||
/>
|
||||
<div className="flex w-0 grow items-center justify-between text-sm text-text-secondary">
|
||||
<span className="truncate">{t(`blocks.${block.type}`, { ns: 'workflow' })}</span>
|
||||
{block.type === BlockEnumValues.Start && (
|
||||
<span className="ml-2 shrink-0 system-xs-regular text-text-quaternary">{t('blocks.originalStartNode', { ns: 'workflow' })}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<PreviewCardContent placement="right" popupClassName="w-[224px] px-3 py-2.5">
|
||||
<div>
|
||||
<PreviewCardTrigger
|
||||
key={block.type}
|
||||
delay={150}
|
||||
closeDelay={150}
|
||||
handle={previewCardHandle}
|
||||
payload={{ block }}
|
||||
render={(
|
||||
<div
|
||||
className="flex h-8 w-full cursor-pointer items-center rounded-lg px-3 hover:bg-state-base-hover"
|
||||
onClick={() => onSelect(block.type)}
|
||||
>
|
||||
<BlockIcon
|
||||
size="md"
|
||||
className="mb-2"
|
||||
className="mr-2 shrink-0"
|
||||
type={block.type}
|
||||
/>
|
||||
<div className="mb-1 system-md-medium text-text-primary">
|
||||
{block.type === BlockEnumValues.TriggerWebhook
|
||||
? t('customWebhook', { ns: 'workflow' })
|
||||
: t(`blocks.${block.type}`, { ns: 'workflow' })}
|
||||
<div className="flex w-0 grow items-center justify-between text-sm text-text-secondary">
|
||||
<span className="truncate">{t(`blocks.${block.type}`, { ns: 'workflow' })}</span>
|
||||
{block.type === BlockEnumValues.Start && (
|
||||
<span className="ml-2 shrink-0 system-xs-regular text-text-quaternary">{t('blocks.originalStartNode', { ns: 'workflow' })}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="system-xs-regular text-text-secondary">
|
||||
{t(`blocksAbout.${block.type}`, { ns: 'workflow' })}
|
||||
</div>
|
||||
{(block.type === BlockEnumValues.TriggerWebhook || block.type === BlockEnumValues.TriggerSchedule) && (
|
||||
<div className="mt-1 mb-1 system-xs-regular text-text-tertiary">
|
||||
{t('author', { ns: 'tools' })}
|
||||
{' '}
|
||||
{t('difyTeam', { ns: 'workflow' })}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PreviewCardContent>
|
||||
</PreviewCard>
|
||||
), [onSelect, t])
|
||||
)}
|
||||
/>
|
||||
), [onSelect, previewCardHandle, t])
|
||||
|
||||
if (isEmpty)
|
||||
return null
|
||||
@@ -140,8 +122,58 @@ const StartBlocks = ({
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<PreviewCard handle={previewCardHandle}>
|
||||
{({ payload }) => (
|
||||
<StartBlockPreviewCard
|
||||
payload={payload as StartBlockPreviewPayload | undefined}
|
||||
t={t}
|
||||
/>
|
||||
)}
|
||||
</PreviewCard>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type StartBlockPreviewCardProps = {
|
||||
payload?: StartBlockPreviewPayload
|
||||
t: ReturnType<typeof useTranslation>['t']
|
||||
}
|
||||
|
||||
function StartBlockPreviewCard({
|
||||
payload,
|
||||
t,
|
||||
}: StartBlockPreviewCardProps) {
|
||||
if (!payload)
|
||||
return null
|
||||
|
||||
const { block } = payload
|
||||
|
||||
return (
|
||||
<PreviewCardContent placement="right" popupClassName="w-[224px] px-3 py-2.5">
|
||||
<div>
|
||||
<BlockIcon
|
||||
size="md"
|
||||
className="mb-2"
|
||||
type={block.type}
|
||||
/>
|
||||
<div className="mb-1 system-md-medium text-text-primary">
|
||||
{block.type === BlockEnumValues.TriggerWebhook
|
||||
? t('customWebhook', { ns: 'workflow' })
|
||||
: t(`blocks.${block.type}`, { ns: 'workflow' })}
|
||||
</div>
|
||||
<div className="system-xs-regular wrap-break-word text-text-secondary">
|
||||
{t(`blocksAbout.${block.type}`, { ns: 'workflow' })}
|
||||
</div>
|
||||
{(block.type === BlockEnumValues.TriggerWebhook || block.type === BlockEnumValues.TriggerSchedule) && (
|
||||
<div className="mt-1 mb-1 system-xs-regular text-text-tertiary">
|
||||
{t('author', { ns: 'tools' })}
|
||||
{' '}
|
||||
{t('difyTeam', { ns: 'workflow' })}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PreviewCardContent>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(StartBlocks)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createPreviewCardHandle } from '@langgenius/dify-ui/preview-card'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
@@ -51,6 +52,7 @@ describe('Tool', () => {
|
||||
createTool('tool-b', 'Tool B'),
|
||||
],
|
||||
})}
|
||||
previewCardHandle={createPreviewCardHandle()}
|
||||
viewType={ViewType.flat}
|
||||
hasSearchText={false}
|
||||
onSelect={onSelect}
|
||||
@@ -82,6 +84,7 @@ describe('Tool', () => {
|
||||
type: CollectionType.workflow,
|
||||
tools: [createTool('workflow-tool', 'Workflow Tool')],
|
||||
})}
|
||||
previewCardHandle={createPreviewCardHandle()}
|
||||
viewType={ViewType.flat}
|
||||
hasSearchText={false}
|
||||
onSelect={onSelect}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { ComponentProps, FC } from 'react'
|
||||
import type { ToolWithProvider } from '../../types'
|
||||
import type { ToolDefaultValue } from '../types'
|
||||
import type { Tool } from '@/app/components/tools/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
|
||||
import { PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
|
||||
import * as React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -27,14 +27,25 @@ const normalizeProviderIcon = (icon?: ToolWithProvider['icon']) => {
|
||||
type Props = {
|
||||
provider: ToolWithProvider
|
||||
payload: Tool
|
||||
previewCardHandle: PreviewCardHandle
|
||||
disabled?: boolean
|
||||
isAdded?: boolean
|
||||
onSelect: (type: BlockEnum, tool: ToolDefaultValue) => void
|
||||
}
|
||||
|
||||
export type ToolActionPreviewPayload = {
|
||||
providerIcon: ToolWithProvider['icon']
|
||||
payload: Tool
|
||||
language: ReturnType<typeof useGetLanguage>
|
||||
}
|
||||
|
||||
type PreviewCardHandle = NonNullable<ComponentProps<typeof PreviewCardTrigger>['handle']>
|
||||
export type ToolActionPreviewCardHandle = PreviewCardHandle
|
||||
|
||||
const ToolItem: FC<Props> = ({
|
||||
provider,
|
||||
payload,
|
||||
previewCardHandle,
|
||||
onSelect,
|
||||
disabled,
|
||||
isAdded,
|
||||
@@ -107,21 +118,45 @@ const ToolItem: FC<Props> = ({
|
||||
// reachable from the node inspector after the row is clicked to add the tool,
|
||||
// so hover/focus-only activation is a11y-safe. See
|
||||
// packages/dify-ui/AGENTS.md → Overlay Primitive Selection.
|
||||
<PreviewCard key={payload.name}>
|
||||
<PreviewCardTrigger delay={150} closeDelay={150} render={row} />
|
||||
<PreviewCardContent placement="right" popupClassName="w-[200px] px-3 py-2.5">
|
||||
<div>
|
||||
<BlockIcon
|
||||
size="md"
|
||||
className="mb-2"
|
||||
type={BlockEnum.Tool}
|
||||
toolIcon={providerIcon}
|
||||
/>
|
||||
<div className="mb-1 text-sm leading-5 text-text-primary">{payload.label[language]}</div>
|
||||
<div className="text-xs leading-[18px] text-text-secondary">{payload.description[language]}</div>
|
||||
</div>
|
||||
</PreviewCardContent>
|
||||
</PreviewCard>
|
||||
<PreviewCardTrigger
|
||||
key={payload.name}
|
||||
delay={150}
|
||||
closeDelay={150}
|
||||
handle={previewCardHandle}
|
||||
payload={{
|
||||
providerIcon,
|
||||
payload,
|
||||
language,
|
||||
}}
|
||||
render={row}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type ToolActionPreviewCardProps = {
|
||||
payload?: ToolActionPreviewPayload
|
||||
}
|
||||
|
||||
export function ToolActionPreviewCard({
|
||||
payload,
|
||||
}: ToolActionPreviewCardProps) {
|
||||
if (!payload)
|
||||
return null
|
||||
|
||||
return (
|
||||
<PreviewCardContent placement="right" popupClassName="w-[200px] px-3 py-2.5">
|
||||
<div>
|
||||
<BlockIcon
|
||||
size="md"
|
||||
className="mb-2"
|
||||
type={BlockEnum.Tool}
|
||||
toolIcon={payload.providerIcon}
|
||||
/>
|
||||
<div className="mb-1 text-sm leading-5 text-text-primary">{payload.payload.label[payload.language]}</div>
|
||||
<div className="text-xs leading-[18px] wrap-break-word text-text-secondary">{payload.payload.description[payload.language]}</div>
|
||||
</div>
|
||||
</PreviewCardContent>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ToolItem)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createPreviewCardHandle } from '@langgenius/dify-ui/preview-card'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
@@ -37,6 +38,7 @@ describe('ToolListFlatView', () => {
|
||||
render(
|
||||
<List
|
||||
letters={['A', 'B']}
|
||||
previewCardHandle={createPreviewCardHandle()}
|
||||
payload={[
|
||||
createToolProvider({
|
||||
id: 'provider-a',
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { FC, RefObject } from 'react'
|
||||
import type { BlockEnum, ToolWithProvider } from '../../../types'
|
||||
import type { ToolDefaultValue, ToolValue } from '../../types'
|
||||
import type { ToolActionPreviewCardHandle } from '../action-item'
|
||||
import * as React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { ViewType } from '../../view-type-select'
|
||||
@@ -9,6 +10,7 @@ import Tool from '../tool'
|
||||
|
||||
type Props = {
|
||||
payload: ToolWithProvider[]
|
||||
previewCardHandle: ToolActionPreviewCardHandle
|
||||
isShowLetterIndex: boolean
|
||||
indexBar: React.ReactNode
|
||||
hasSearchText: boolean
|
||||
@@ -16,13 +18,14 @@ type Props = {
|
||||
canNotSelectMultiple?: boolean
|
||||
onSelectMultiple?: (type: BlockEnum, tools: ToolDefaultValue[]) => void
|
||||
letters: string[]
|
||||
toolRefs: any
|
||||
toolRefs: RefObject<Record<string, HTMLDivElement | null>>
|
||||
selectedTools?: ToolValue[]
|
||||
}
|
||||
|
||||
const ToolViewFlatView: FC<Props> = ({
|
||||
letters,
|
||||
payload,
|
||||
previewCardHandle,
|
||||
isShowLetterIndex,
|
||||
indexBar,
|
||||
hasSearchText,
|
||||
@@ -55,6 +58,7 @@ const ToolViewFlatView: FC<Props> = ({
|
||||
>
|
||||
<Tool
|
||||
payload={tool}
|
||||
previewCardHandle={previewCardHandle}
|
||||
viewType={ViewType.flat}
|
||||
hasSearchText={hasSearchText}
|
||||
onSelect={onSelect}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createPreviewCardHandle } from '@langgenius/dify-ui/preview-card'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
@@ -36,6 +37,7 @@ describe('ToolListTreeView Item', () => {
|
||||
toolList={[createToolProvider({
|
||||
label: { en_US: 'Provider Alpha', zh_Hans: 'Provider Alpha' },
|
||||
})]}
|
||||
previewCardHandle={createPreviewCardHandle()}
|
||||
hasSearchText={false}
|
||||
onSelect={vi.fn()}
|
||||
/>,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createPreviewCardHandle } from '@langgenius/dify-ui/preview-card'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
@@ -43,6 +44,7 @@ describe('ToolListTreeView', () => {
|
||||
label: { en_US: 'Custom Provider', zh_Hans: 'Custom Provider' },
|
||||
})],
|
||||
}}
|
||||
previewCardHandle={createPreviewCardHandle()}
|
||||
hasSearchText={false}
|
||||
onSelect={vi.fn()}
|
||||
/>,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { FC } from 'react'
|
||||
import type { BlockEnum, ToolWithProvider } from '../../../types'
|
||||
import type { ToolDefaultValue, ToolValue } from '../../types'
|
||||
import type { ToolActionPreviewCardHandle } from '../action-item'
|
||||
import * as React from 'react'
|
||||
import { ViewType } from '../../view-type-select'
|
||||
import Tool from '../tool'
|
||||
@@ -9,6 +10,7 @@ import Tool from '../tool'
|
||||
type Props = {
|
||||
groupName: string
|
||||
toolList: ToolWithProvider[]
|
||||
previewCardHandle: ToolActionPreviewCardHandle
|
||||
hasSearchText: boolean
|
||||
onSelect: (type: BlockEnum, tool: ToolDefaultValue) => void
|
||||
canNotSelectMultiple?: boolean
|
||||
@@ -19,6 +21,7 @@ type Props = {
|
||||
const Item: FC<Props> = ({
|
||||
groupName,
|
||||
toolList,
|
||||
previewCardHandle,
|
||||
hasSearchText,
|
||||
onSelect,
|
||||
canNotSelectMultiple,
|
||||
@@ -35,6 +38,7 @@ const Item: FC<Props> = ({
|
||||
<Tool
|
||||
key={tool.id}
|
||||
payload={tool}
|
||||
previewCardHandle={previewCardHandle}
|
||||
viewType={ViewType.tree}
|
||||
hasSearchText={hasSearchText}
|
||||
onSelect={onSelect}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { FC } from 'react'
|
||||
import type { BlockEnum, ToolWithProvider } from '../../../types'
|
||||
import type { ToolDefaultValue, ToolValue } from '../../types'
|
||||
import type { ToolActionPreviewCardHandle } from '../action-item'
|
||||
import * as React from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -10,6 +11,7 @@ import Item from './item'
|
||||
|
||||
type Props = {
|
||||
payload: Record<string, ToolWithProvider[]>
|
||||
previewCardHandle: ToolActionPreviewCardHandle
|
||||
hasSearchText: boolean
|
||||
onSelect: (type: BlockEnum, tool: ToolDefaultValue) => void
|
||||
canNotSelectMultiple?: boolean
|
||||
@@ -19,6 +21,7 @@ type Props = {
|
||||
|
||||
const ToolListTreeView: FC<Props> = ({
|
||||
payload,
|
||||
previewCardHandle,
|
||||
hasSearchText,
|
||||
onSelect,
|
||||
canNotSelectMultiple,
|
||||
@@ -49,6 +52,7 @@ const ToolListTreeView: FC<Props> = ({
|
||||
key={groupName}
|
||||
groupName={getI18nGroupName(groupName)}
|
||||
toolList={payload[groupName]!}
|
||||
previewCardHandle={previewCardHandle}
|
||||
hasSearchText={hasSearchText}
|
||||
onSelect={onSelect}
|
||||
canNotSelectMultiple={canNotSelectMultiple}
|
||||
|
||||
@@ -3,11 +3,12 @@ import type { FC } from 'react'
|
||||
import type { Tool as ToolType } from '../../../tools/types'
|
||||
import type { ToolWithProvider } from '../../types'
|
||||
import type { ToolDefaultValue, ToolValue } from '../types'
|
||||
import type { ToolActionPreviewCardHandle } from './action-item'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react'
|
||||
import { useHover } from 'ahooks'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||
import { useCallback, useMemo, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Mcp } from '@/app/components/base/icons/src/vender/other'
|
||||
import { useMCPToolAvailability } from '@/app/components/workflow/nodes/_base/components/mcp-tool-availability'
|
||||
@@ -33,6 +34,7 @@ const normalizeProviderIcon = (icon?: ToolWithProvider['icon']) => {
|
||||
type Props = {
|
||||
className?: string
|
||||
payload: ToolWithProvider
|
||||
previewCardHandle: ToolActionPreviewCardHandle
|
||||
viewType: ViewType
|
||||
hasSearchText: boolean
|
||||
onSelect: (type: BlockEnum, tool: ToolDefaultValue) => void
|
||||
@@ -45,6 +47,7 @@ type Props = {
|
||||
const Tool: FC<Props> = ({
|
||||
className,
|
||||
payload,
|
||||
previewCardHandle,
|
||||
viewType,
|
||||
hasSearchText,
|
||||
onSelect,
|
||||
@@ -59,7 +62,8 @@ const Tool: FC<Props> = ({
|
||||
const notShowProvider = payload.type === CollectionType.workflow
|
||||
const actions = payload.tools
|
||||
const hasAction = !notShowProvider
|
||||
const [isFold, setFold] = React.useState<boolean>(true)
|
||||
const [isFold, setIsFold] = React.useState<boolean>(true)
|
||||
const [isFoldHasSearchText, setIsFoldHasSearchText] = React.useState(hasSearchText)
|
||||
const ref = useRef(null)
|
||||
const isHovering = useHover(ref)
|
||||
const isMCPTool = payload.type === CollectionType.mcp
|
||||
@@ -146,14 +150,10 @@ const Tool: FC<Props> = ({
|
||||
)
|
||||
}, [actions, getIsDisabled, isAllSelected, isHovering, language, onSelectMultiple, payload.id, payload.is_team_authorization, payload.name, payload.type, selectedToolsNum, t, totalToolsNum])
|
||||
|
||||
useEffect(() => {
|
||||
if (hasSearchText && isFold) {
|
||||
setFold(false)
|
||||
return
|
||||
}
|
||||
if (!hasSearchText && !isFold)
|
||||
setFold(true)
|
||||
}, [hasSearchText])
|
||||
if (isFoldHasSearchText !== hasSearchText) {
|
||||
setIsFoldHasSearchText(hasSearchText)
|
||||
setIsFold(!hasSearchText)
|
||||
}
|
||||
|
||||
const FoldIcon = isFold ? RiArrowRightSLine : RiArrowDownSLine
|
||||
|
||||
@@ -181,7 +181,7 @@ const Tool: FC<Props> = ({
|
||||
className="group/item flex w-full cursor-pointer items-center justify-between rounded-lg pr-1 pl-3 select-none hover:bg-state-base-hover"
|
||||
onClick={() => {
|
||||
if (hasAction) {
|
||||
setFold(!isFold)
|
||||
setIsFold(!isFold)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -240,6 +240,7 @@ const Tool: FC<Props> = ({
|
||||
key={action.name}
|
||||
provider={payload}
|
||||
payload={action}
|
||||
previewCardHandle={previewCardHandle}
|
||||
onSelect={onSelect}
|
||||
disabled={getIsDisabled(action) || isShowCanNotChooseMCPTip}
|
||||
isAdded={getIsDisabled(action)}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import type { BlockEnum, ToolWithProvider } from '../types'
|
||||
import type { ToolActionPreviewPayload } from './tool/action-item'
|
||||
import type { ToolDefaultValue, ToolTypeEnum, ToolValue } from './types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { createPreviewCardHandle, PreviewCard } from '@langgenius/dify-ui/preview-card'
|
||||
import { memo, useMemo, useRef } from 'react'
|
||||
import Empty from '@/app/components/tools/provider/empty'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import IndexBar, { groupItems } from './index-bar'
|
||||
import { ToolActionPreviewCard } from './tool/action-item'
|
||||
import ToolListFlatView from './tool/tool-list-flat-view/list'
|
||||
import ToolListTreeView from './tool/tool-list-tree-view/list'
|
||||
import { ViewType } from './view-type-select'
|
||||
@@ -35,8 +38,8 @@ const Tools = ({
|
||||
indexBarClassName,
|
||||
selectedTools,
|
||||
}: ToolsProps) => {
|
||||
// const tools: any = []
|
||||
const language = useGetLanguage()
|
||||
const previewCardHandle = useMemo(() => createPreviewCardHandle<ToolActionPreviewPayload>(), [])
|
||||
const isFlatView = viewType === ViewType.flat
|
||||
const isShowLetterIndex = isFlatView && tools.length > 10
|
||||
|
||||
@@ -85,7 +88,7 @@ const Tools = ({
|
||||
return result
|
||||
}, [withLetterAndGroupViewToolsData, letters])
|
||||
|
||||
const toolRefs = useRef({})
|
||||
const toolRefsRef = useRef<Record<string, HTMLDivElement | null>>({})
|
||||
|
||||
return (
|
||||
<div className={cn('max-w-full p-1', className)}>
|
||||
@@ -98,21 +101,23 @@ const Tools = ({
|
||||
isFlatView
|
||||
? (
|
||||
<ToolListFlatView
|
||||
toolRefs={toolRefs}
|
||||
toolRefs={toolRefsRef}
|
||||
letters={letters}
|
||||
payload={listViewToolData}
|
||||
previewCardHandle={previewCardHandle}
|
||||
isShowLetterIndex={isShowLetterIndex}
|
||||
hasSearchText={hasSearchText}
|
||||
onSelect={onSelect}
|
||||
canNotSelectMultiple={canNotSelectMultiple}
|
||||
onSelectMultiple={onSelectMultiple}
|
||||
selectedTools={selectedTools}
|
||||
indexBar={<IndexBar letters={letters} itemRefs={toolRefs} className={indexBarClassName} />}
|
||||
indexBar={<IndexBar letters={letters} itemRefs={toolRefsRef} className={indexBarClassName} />}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<ToolListTreeView
|
||||
payload={treeViewToolsData}
|
||||
previewCardHandle={previewCardHandle}
|
||||
hasSearchText={hasSearchText}
|
||||
onSelect={onSelect}
|
||||
canNotSelectMultiple={canNotSelectMultiple}
|
||||
@@ -121,6 +126,11 @@ const Tools = ({
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<PreviewCard handle={previewCardHandle}>
|
||||
{({ payload }) => (
|
||||
<ToolActionPreviewCard payload={payload as ToolActionPreviewPayload | undefined} />
|
||||
)}
|
||||
</PreviewCard>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { ComponentProps, FC } from 'react'
|
||||
import type { TriggerDefaultValue, TriggerWithProvider } from '../types'
|
||||
import type { Event } from '@/app/components/tools/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
|
||||
import { PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
@@ -13,14 +13,25 @@ import { BlockEnum } from '../../types'
|
||||
type Props = {
|
||||
provider: TriggerWithProvider
|
||||
payload: Event
|
||||
previewCardHandle: TriggerPluginActionPreviewCardHandle
|
||||
disabled?: boolean
|
||||
isAdded?: boolean
|
||||
onSelect: (type: BlockEnum, trigger?: TriggerDefaultValue) => void
|
||||
}
|
||||
|
||||
export type TriggerPluginActionPreviewPayload = {
|
||||
provider: TriggerWithProvider
|
||||
payload: Event
|
||||
language: ReturnType<typeof useGetLanguage>
|
||||
}
|
||||
|
||||
type PreviewCardHandle = NonNullable<ComponentProps<typeof PreviewCardTrigger>['handle']>
|
||||
export type TriggerPluginActionPreviewCardHandle = PreviewCardHandle
|
||||
|
||||
const TriggerPluginActionItem: FC<Props> = ({
|
||||
provider,
|
||||
payload,
|
||||
previewCardHandle,
|
||||
onSelect,
|
||||
disabled,
|
||||
isAdded,
|
||||
@@ -37,7 +48,7 @@ const TriggerPluginActionItem: FC<Props> = ({
|
||||
return
|
||||
const params: Record<string, string> = {}
|
||||
if (payload.parameters) {
|
||||
payload.parameters.forEach((item: any) => {
|
||||
payload.parameters.forEach((item) => {
|
||||
params[item.name] = ''
|
||||
})
|
||||
}
|
||||
@@ -73,21 +84,41 @@ const TriggerPluginActionItem: FC<Props> = ({
|
||||
// reachable from the node inspector after the row is clicked to add the trigger,
|
||||
// so hover/focus-only activation is a11y-safe. See
|
||||
// packages/dify-ui/AGENTS.md → Overlay Primitive Selection.
|
||||
<PreviewCard key={payload.name}>
|
||||
<PreviewCardTrigger delay={150} closeDelay={150} render={row} />
|
||||
<PreviewCardContent placement="right" popupClassName="w-[224px] px-3 py-2.5">
|
||||
<div>
|
||||
<BlockIcon
|
||||
size="md"
|
||||
className="mb-2"
|
||||
type={BlockEnum.TriggerPlugin}
|
||||
toolIcon={provider.icon}
|
||||
/>
|
||||
<div className="mb-1 text-sm leading-5 text-text-primary">{payload.label[language]}</div>
|
||||
<div className="text-xs leading-[18px] text-text-secondary">{payload.description[language]}</div>
|
||||
</div>
|
||||
</PreviewCardContent>
|
||||
</PreviewCard>
|
||||
<PreviewCardTrigger
|
||||
key={payload.name}
|
||||
delay={150}
|
||||
closeDelay={150}
|
||||
handle={previewCardHandle}
|
||||
payload={{ provider, payload, language }}
|
||||
render={row}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type TriggerPluginActionPreviewCardProps = {
|
||||
payload?: TriggerPluginActionPreviewPayload
|
||||
}
|
||||
|
||||
export function TriggerPluginActionPreviewCard({
|
||||
payload,
|
||||
}: TriggerPluginActionPreviewCardProps) {
|
||||
if (!payload)
|
||||
return null
|
||||
|
||||
return (
|
||||
<PreviewCardContent placement="right" popupClassName="w-[224px] px-3 py-2.5">
|
||||
<div>
|
||||
<BlockIcon
|
||||
size="md"
|
||||
className="mb-2"
|
||||
type={BlockEnum.TriggerPlugin}
|
||||
toolIcon={payload.provider.icon}
|
||||
/>
|
||||
<div className="mb-1 text-sm leading-5 text-text-primary">{payload.payload.label[payload.language]}</div>
|
||||
<div className="text-xs leading-[18px] wrap-break-word text-text-secondary">{payload.payload.description[payload.language]}</div>
|
||||
</div>
|
||||
</PreviewCardContent>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(TriggerPluginActionItem)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { TriggerPluginActionPreviewCardHandle } from './action-item'
|
||||
import type { TriggerDefaultValue, TriggerWithProvider } from '@/app/components/workflow/block-selector/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import { useEffect, useMemo, useRef } from 'react'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
@@ -27,6 +28,7 @@ type Props = {
|
||||
className?: string
|
||||
payload: TriggerWithProvider
|
||||
hasSearchText: boolean
|
||||
previewCardHandle: TriggerPluginActionPreviewCardHandle
|
||||
onSelect: (type: BlockEnum, trigger?: TriggerDefaultValue) => void
|
||||
}
|
||||
|
||||
@@ -34,6 +36,7 @@ const TriggerPluginItem: FC<Props> = ({
|
||||
className,
|
||||
payload,
|
||||
hasSearchText,
|
||||
previewCardHandle,
|
||||
onSelect,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
@@ -42,17 +45,14 @@ const TriggerPluginItem: FC<Props> = ({
|
||||
const notShowProvider = payload.type === CollectionType.workflow
|
||||
const actions = payload.events
|
||||
const hasAction = !notShowProvider
|
||||
const [isFold, setFold] = React.useState<boolean>(true)
|
||||
const [isFold, setIsFold] = React.useState<boolean>(true)
|
||||
const [isFoldHasSearchText, setIsFoldHasSearchText] = React.useState(hasSearchText)
|
||||
const ref = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (hasSearchText && isFold) {
|
||||
setFold(false)
|
||||
return
|
||||
}
|
||||
if (!hasSearchText && !isFold)
|
||||
setFold(true)
|
||||
}, [hasSearchText])
|
||||
if (isFoldHasSearchText !== hasSearchText) {
|
||||
setIsFoldHasSearchText(hasSearchText)
|
||||
setIsFold(!hasSearchText)
|
||||
}
|
||||
|
||||
const FoldIcon = isFold ? RiArrowRightSLine : RiArrowDownSLine
|
||||
|
||||
@@ -97,14 +97,14 @@ const TriggerPluginItem: FC<Props> = ({
|
||||
className="group/item flex w-full cursor-pointer items-center justify-between rounded-lg pr-1 pl-3 select-none hover:bg-state-base-hover"
|
||||
onClick={() => {
|
||||
if (hasAction) {
|
||||
setFold(!isFold)
|
||||
setIsFold(!isFold)
|
||||
return
|
||||
}
|
||||
|
||||
const event = actions[0]
|
||||
const params: Record<string, string> = {}
|
||||
if (event!.parameters) {
|
||||
event!.parameters.forEach((item: any) => {
|
||||
event!.parameters.forEach((item) => {
|
||||
params[item.name] = ''
|
||||
})
|
||||
}
|
||||
@@ -150,6 +150,7 @@ const TriggerPluginItem: FC<Props> = ({
|
||||
key={action.name}
|
||||
provider={providerWithResolvedIcon}
|
||||
payload={action}
|
||||
previewCardHandle={previewCardHandle}
|
||||
onSelect={onSelect}
|
||||
disabled={false}
|
||||
isAdded={false}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
'use client'
|
||||
import type { BlockEnum } from '../../types'
|
||||
import type { TriggerDefaultValue, TriggerWithProvider } from '../types'
|
||||
import type { TriggerPluginActionPreviewPayload } from './action-item'
|
||||
import { createPreviewCardHandle, PreviewCard } from '@langgenius/dify-ui/preview-card'
|
||||
import { memo, useEffect, useMemo } from 'react'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import { useAllTriggerPlugins } from '@/service/use-triggers'
|
||||
import { TriggerPluginActionPreviewCard } from './action-item'
|
||||
import TriggerPluginItem from './item'
|
||||
|
||||
type TriggerPluginListProps = {
|
||||
@@ -20,6 +23,7 @@ const TriggerPluginList = ({
|
||||
}: TriggerPluginListProps) => {
|
||||
const { data: triggerPluginsData } = useAllTriggerPlugins()
|
||||
const language = useGetLanguage()
|
||||
const previewCardHandle = useMemo(() => createPreviewCardHandle<TriggerPluginActionPreviewPayload>(), [])
|
||||
|
||||
const normalizedSearch = searchText.trim().toLowerCase()
|
||||
const triggerPlugins = useMemo(() => {
|
||||
@@ -96,8 +100,14 @@ const TriggerPluginList = ({
|
||||
payload={plugin}
|
||||
onSelect={onSelect}
|
||||
hasSearchText={!!searchText}
|
||||
previewCardHandle={previewCardHandle}
|
||||
/>
|
||||
))}
|
||||
<PreviewCard handle={previewCardHandle}>
|
||||
{({ payload }) => (
|
||||
<TriggerPluginActionPreviewCard payload={payload as TriggerPluginActionPreviewPayload | undefined} />
|
||||
)}
|
||||
</PreviewCard>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -101,8 +101,8 @@ const FormContent: FC<FormContentProps> = ({
|
||||
acc[node.id] = {
|
||||
title: node.data.title,
|
||||
type: node.data.type,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
width: node.width ?? undefined,
|
||||
height: node.height ?? undefined,
|
||||
position: node.position,
|
||||
}
|
||||
if (node.data.type === BlockEnum.Start) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { DocPathMap } from './i18n'
|
||||
import type { DocPathWithoutLang } from '@/types/doc-paths'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { getDocLanguage } from '@/i18n-config/language'
|
||||
import { defaultDocBaseUrl, useDocLink } from './i18n'
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Locale } from '@/i18n-config/language'
|
||||
import type { DocPathWithoutLang } from '@/types/doc-paths'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { getDocLanguage, getLanguage, getPricingPageLanguage } from '@/i18n-config/language'
|
||||
import { apiReferencePathTranslations } from '@/types/doc-paths'
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@ export default antfu(
|
||||
{
|
||||
files: [...GLOB_TESTS, GLOB_MARKDOWN_CODE, 'vitest.setup.ts', 'test/i18n-mock.ts'],
|
||||
rules: {
|
||||
'react/component-hook-factories': 'off',
|
||||
'react/no-unnecessary-use-prefix': 'off',
|
||||
},
|
||||
},
|
||||
@@ -157,12 +156,6 @@ export default antfu(
|
||||
'dify/consistent-placeholders': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['package.json'],
|
||||
rules: {
|
||||
'hyoban/no-dependency-version-prefix': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'dify/restricted-imports',
|
||||
files: [GLOB_TS, GLOB_TSX],
|
||||
|
||||
Reference in New Issue
Block a user