From 28478cdc4147da511c87a4dce60b37afb9299858 Mon Sep 17 00:00:00 2001 From: kenwoodjw Date: Wed, 30 Jul 2025 16:13:45 +0800 Subject: [PATCH 01/32] feat: support metadata condition filter string array (#23111) Signed-off-by: kenwoodjw --- api/core/app/app_config/entities.py | 2 ++ api/core/rag/entities/metadata_entities.py | 2 ++ .../nodes/knowledge_retrieval/entities.py | 2 ++ .../knowledge_retrieval_node.py | 22 +++++++++++++++++++ .../metadata/condition-list/utils.ts | 2 ++ web/i18n/zh-Hans/workflow.ts | 4 ++-- 6 files changed, 32 insertions(+), 2 deletions(-) diff --git a/api/core/app/app_config/entities.py b/api/core/app/app_config/entities.py index 75bd2f677a..0df0aa59b2 100644 --- a/api/core/app/app_config/entities.py +++ b/api/core/app/app_config/entities.py @@ -148,6 +148,8 @@ SupportedComparisonOperator = Literal[ "is not", "empty", "not empty", + "in", + "not in", # for number "=", "≠", diff --git a/api/core/rag/entities/metadata_entities.py b/api/core/rag/entities/metadata_entities.py index 6ef932ad22..1f054bccdb 100644 --- a/api/core/rag/entities/metadata_entities.py +++ b/api/core/rag/entities/metadata_entities.py @@ -13,6 +13,8 @@ SupportedComparisonOperator = Literal[ "is not", "empty", "not empty", + "in", + "not in", # for number "=", "≠", diff --git a/api/core/workflow/nodes/knowledge_retrieval/entities.py b/api/core/workflow/nodes/knowledge_retrieval/entities.py index f1767bdf9e..b71271abeb 100644 --- a/api/core/workflow/nodes/knowledge_retrieval/entities.py +++ b/api/core/workflow/nodes/knowledge_retrieval/entities.py @@ -74,6 +74,8 @@ SupportedComparisonOperator = Literal[ "is not", "empty", "not empty", + "in", + "not in", # for number "=", "≠", diff --git a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py index e041e217ca..7303b68501 100644 --- a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py +++ b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py @@ -602,6 +602,28 @@ class KnowledgeRetrievalNode(BaseNode): **{key: metadata_name, key_value: f"%{value}"} ) ) + case "in": + if isinstance(value, str): + escaped_values = [v.strip().replace("'", "''") for v in str(value).split(",")] + escaped_value_str = ",".join(escaped_values) + else: + escaped_value_str = str(value) + filters.append( + (text(f"documents.doc_metadata ->> :{key} = any(string_to_array(:{key_value},','))")).params( + **{key: metadata_name, key_value: escaped_value_str} + ) + ) + case "not in": + if isinstance(value, str): + escaped_values = [v.strip().replace("'", "''") for v in str(value).split(",")] + escaped_value_str = ",".join(escaped_values) + else: + escaped_value_str = str(value) + filters.append( + (text(f"documents.doc_metadata ->> :{key} != all(string_to_array(:{key_value},','))")).params( + **{key: metadata_name, key_value: escaped_value_str} + ) + ) case "=" | "is": if isinstance(value, str): filters.append(Document.doc_metadata[metadata_name] == f'"{value}"') diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/utils.ts b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/utils.ts index 6397023991..10ee1aff1f 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/utils.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/utils.ts @@ -32,6 +32,8 @@ export const getOperators = (type?: MetadataFilteringVariableType) => { ComparisonOperator.endWith, ComparisonOperator.empty, ComparisonOperator.notEmpty, + ComparisonOperator.in, + ComparisonOperator.notIn, ] case MetadataFilteringVariableType.number: return [ diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index 1f0300ae2a..b1c28c4666 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -591,8 +591,8 @@ const translation = { 'not empty': '不为空', 'null': '空', 'not null': '不为空', - 'in': '是', - 'not in': '不是', + 'in': '在', + 'not in': '不在', 'all of': '全部是', 'exists': '存在', 'not exists': '不存在', From bbdeb15501b1d122d4e87261b26baa123bc01c3e Mon Sep 17 00:00:00 2001 From: Sn0rt Date: Wed, 30 Jul 2025 16:39:54 +0800 Subject: [PATCH 02/32] fix: Support URL-encoded passwords with special characters in CELERY_BROKER_URL (#23163) Signed-off-by: Sn0rt --- api/schedule/queue_monitor_task.py | 19 +++--- .../unit_tests/configs/test_dify_config.py | 59 +++++++++++++++++++ 2 files changed, 67 insertions(+), 11 deletions(-) diff --git a/api/schedule/queue_monitor_task.py b/api/schedule/queue_monitor_task.py index a05e1358ed..4d517e5498 100644 --- a/api/schedule/queue_monitor_task.py +++ b/api/schedule/queue_monitor_task.py @@ -1,8 +1,8 @@ import logging from datetime import datetime -from urllib.parse import urlparse import click +from kombu.utils.url import parse_url # type: ignore from redis import Redis import app @@ -10,16 +10,13 @@ from configs import dify_config from extensions.ext_database import db from libs.email_i18n import EmailType, get_email_i18n_service -# Create a dedicated Redis connection (using the same configuration as Celery) -celery_broker_url = dify_config.CELERY_BROKER_URL - -parsed = urlparse(celery_broker_url) -host = parsed.hostname or "localhost" -port = parsed.port or 6379 -password = parsed.password or None -redis_db = parsed.path.strip("/") or "1" # type: ignore - -celery_redis = Redis(host=host, port=port, password=password, db=redis_db) +redis_config = parse_url(dify_config.CELERY_BROKER_URL) +celery_redis = Redis( + host=redis_config["hostname"], + port=redis_config["port"], + password=redis_config["password"], + db=int(redis_config["virtual_host"]) if redis_config["virtual_host"] else 1, +) @app.celery.task(queue="monitor") diff --git a/api/tests/unit_tests/configs/test_dify_config.py b/api/tests/unit_tests/configs/test_dify_config.py index e9d4ee1935..0ae6a09f5b 100644 --- a/api/tests/unit_tests/configs/test_dify_config.py +++ b/api/tests/unit_tests/configs/test_dify_config.py @@ -1,5 +1,6 @@ import os +import pytest from flask import Flask from packaging.version import Version from yarl import URL @@ -137,3 +138,61 @@ def test_db_extras_options_merging(monkeypatch): options = engine_options["connect_args"]["options"] assert "search_path=myschema" in options assert "timezone=UTC" in options + + +@pytest.mark.parametrize( + ("broker_url", "expected_host", "expected_port", "expected_username", "expected_password", "expected_db"), + [ + ("redis://localhost:6379/1", "localhost", 6379, None, None, "1"), + ("redis://:password@localhost:6379/1", "localhost", 6379, None, "password", "1"), + ("redis://:mypass%23123@localhost:6379/1", "localhost", 6379, None, "mypass#123", "1"), + ("redis://user:pass%40word@redis-host:6380/2", "redis-host", 6380, "user", "pass@word", "2"), + ("redis://admin:complex%23pass%40word@127.0.0.1:6379/0", "127.0.0.1", 6379, "admin", "complex#pass@word", "0"), + ( + "redis://user%40domain:secret%23123@redis.example.com:6380/3", + "redis.example.com", + 6380, + "user@domain", + "secret#123", + "3", + ), + # Password containing %23 substring (double encoding scenario) + ("redis://:mypass%2523@localhost:6379/1", "localhost", 6379, None, "mypass%23", "1"), + # Username and password both containing encoded characters + ("redis://user%2525%40:pass%2523@localhost:6379/1", "localhost", 6379, "user%25@", "pass%23", "1"), + ], +) +def test_celery_broker_url_with_special_chars_password( + monkeypatch, broker_url, expected_host, expected_port, expected_username, expected_password, expected_db +): + """Test that CELERY_BROKER_URL with various formats are handled correctly.""" + from kombu.utils.url import parse_url + + # clear system environment variables + os.environ.clear() + + # Set up basic required environment variables (following existing pattern) + monkeypatch.setenv("CONSOLE_API_URL", "https://example.com") + monkeypatch.setenv("CONSOLE_WEB_URL", "https://example.com") + monkeypatch.setenv("DB_USERNAME", "postgres") + monkeypatch.setenv("DB_PASSWORD", "postgres") + monkeypatch.setenv("DB_HOST", "localhost") + monkeypatch.setenv("DB_PORT", "5432") + monkeypatch.setenv("DB_DATABASE", "dify") + + # Set the CELERY_BROKER_URL to test + monkeypatch.setenv("CELERY_BROKER_URL", broker_url) + + # Create config and verify the URL is stored correctly + config = DifyConfig() + assert broker_url == config.CELERY_BROKER_URL + + # Test actual parsing behavior using kombu's parse_url (same as production) + redis_config = parse_url(config.CELERY_BROKER_URL) + + # Verify the parsing results match expectations (using kombu's field names) + assert redis_config["hostname"] == expected_host + assert redis_config["port"] == expected_port + assert redis_config["userid"] == expected_username # kombu uses 'userid' not 'username' + assert redis_config["password"] == expected_password + assert redis_config["virtual_host"] == expected_db # kombu uses 'virtual_host' not 'db' From 070379a900347001f2bce8630e059493ce9e9004 Mon Sep 17 00:00:00 2001 From: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com> Date: Wed, 30 Jul 2025 17:04:31 +0800 Subject: [PATCH 03/32] minor fix: fix wrong check of annotation_ids (#23164) --- api/controllers/console/app/annotation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/controllers/console/app/annotation.py b/api/controllers/console/app/annotation.py index 2af7136f14..472c694c36 100644 --- a/api/controllers/console/app/annotation.py +++ b/api/controllers/console/app/annotation.py @@ -137,7 +137,8 @@ class AnnotationListApi(Resource): # If annotation_ids are provided, handle batch deletion if annotation_ids: - if not annotation_ids: + # Check if any annotation_ids contain empty strings or invalid values + if not all(annotation_id.strip() for annotation_id in annotation_ids if annotation_id): return { "code": "bad_request", "message": "annotation_ids are required if the parameter is provided.", From 07cff1ed2c6259623fe2b50b780d10182b36987d Mon Sep 17 00:00:00 2001 From: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com> Date: Wed, 30 Jul 2025 17:05:02 +0800 Subject: [PATCH 04/32] minor fix: fix flask api resources only accept one resource for same url (#23168) --- api/controllers/console/app/annotation.py | 41 +++++++++++------------ 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/api/controllers/console/app/annotation.py b/api/controllers/console/app/annotation.py index 472c694c36..007b1f6d3d 100644 --- a/api/controllers/console/app/annotation.py +++ b/api/controllers/console/app/annotation.py @@ -100,7 +100,7 @@ class AnnotationReplyActionStatusApi(Resource): return {"job_id": job_id, "job_status": job_status, "error_msg": error_msg}, 200 -class AnnotationListApi(Resource): +class AnnotationApi(Resource): @setup_required @login_required @account_initialization_required @@ -123,6 +123,23 @@ class AnnotationListApi(Resource): } return response, 200 + @setup_required + @login_required + @account_initialization_required + @cloud_edition_billing_resource_check("annotation") + @marshal_with(annotation_fields) + def post(self, app_id): + if not current_user.is_editor: + raise Forbidden() + + app_id = str(app_id) + parser = reqparse.RequestParser() + parser.add_argument("question", required=True, type=str, location="json") + parser.add_argument("answer", required=True, type=str, location="json") + args = parser.parse_args() + annotation = AppAnnotationService.insert_app_annotation_directly(args, app_id) + return annotation + @setup_required @login_required @account_initialization_required @@ -166,25 +183,6 @@ class AnnotationExportApi(Resource): return response, 200 -class AnnotationCreateApi(Resource): - @setup_required - @login_required - @account_initialization_required - @cloud_edition_billing_resource_check("annotation") - @marshal_with(annotation_fields) - def post(self, app_id): - if not current_user.is_editor: - raise Forbidden() - - app_id = str(app_id) - parser = reqparse.RequestParser() - parser.add_argument("question", required=True, type=str, location="json") - parser.add_argument("answer", required=True, type=str, location="json") - args = parser.parse_args() - annotation = AppAnnotationService.insert_app_annotation_directly(args, app_id) - return annotation - - class AnnotationUpdateDeleteApi(Resource): @setup_required @login_required @@ -293,9 +291,8 @@ api.add_resource(AnnotationReplyActionApi, "/apps//annotation-reply api.add_resource( AnnotationReplyActionStatusApi, "/apps//annotation-reply//status/" ) -api.add_resource(AnnotationListApi, "/apps//annotations") +api.add_resource(AnnotationApi, "/apps//annotations") api.add_resource(AnnotationExportApi, "/apps//annotations/export") -api.add_resource(AnnotationCreateApi, "/apps//annotations") api.add_resource(AnnotationUpdateDeleteApi, "/apps//annotations/") api.add_resource(AnnotationBatchImportApi, "/apps//annotations/batch-import") api.add_resource(AnnotationBatchImportStatusApi, "/apps//annotations/batch-import-status/") From 4e2129d74f298ae3d1d91a917e0b0032be2a2cb2 Mon Sep 17 00:00:00 2001 From: Will Date: Wed, 30 Jul 2025 18:00:15 +0800 Subject: [PATCH 05/32] fix: Error processing trace tasks (#23170) --- api/core/ops/ops_trace_manager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/core/ops/ops_trace_manager.py b/api/core/ops/ops_trace_manager.py index a607c76beb..b769934a9b 100644 --- a/api/core/ops/ops_trace_manager.py +++ b/api/core/ops/ops_trace_manager.py @@ -407,7 +407,6 @@ class TraceTask: def __init__( self, trace_type: Any, - trace_id: Optional[str] = None, message_id: Optional[str] = None, workflow_execution: Optional[WorkflowExecution] = None, conversation_id: Optional[str] = None, @@ -423,7 +422,7 @@ class TraceTask: self.timer = timer self.file_base_url = os.getenv("FILES_URL", "http://127.0.0.1:5001") self.app_id = None - + self.trace_id = None self.kwargs = kwargs external_trace_id = kwargs.get("external_trace_id") if external_trace_id: From 270dd955d01b2fc9aa9e2a9fd9d48f68224bd68c Mon Sep 17 00:00:00 2001 From: lyzno1 <92089059+lyzno1@users.noreply.github.com> Date: Wed, 30 Jul 2025 18:00:41 +0800 Subject: [PATCH 06/32] chore(i18n): sync missing keys in zh-Hans and ja-JP (#23175) --- web/i18n/ja-JP/app-annotation.ts | 10 ++++++++++ web/i18n/zh-Hans/time.ts | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/web/i18n/ja-JP/app-annotation.ts b/web/i18n/ja-JP/app-annotation.ts index 801be7c672..6d7edf7077 100644 --- a/web/i18n/ja-JP/app-annotation.ts +++ b/web/i18n/ja-JP/app-annotation.ts @@ -83,6 +83,16 @@ const translation = { configConfirmBtn: '保存', }, embeddingModelSwitchTip: '注釈テキストのベクトル化モデルです。モデルを切り替えると再埋め込みが行われ、追加のコストが発生します。', + list: { + delete: { + title: '本当に削除しますか?', + }, + }, + batchAction: { + cancel: 'キャンセル', + delete: '削除する', + selected: '選択された', + }, } export default translation diff --git a/web/i18n/zh-Hans/time.ts b/web/i18n/zh-Hans/time.ts index 8a223d9dd1..a7a1c6e574 100644 --- a/web/i18n/zh-Hans/time.ts +++ b/web/i18n/zh-Hans/time.ts @@ -26,11 +26,11 @@ const translation = { now: '此刻', ok: '确定', cancel: '取消', + pickDate: '选择日期', }, title: { pickTime: '选择时间', }, - pickDate: '选择日期', defaultPlaceholder: '请选择时间...', } From 8c6d87f08a6e1a555459346172465901a83416cf Mon Sep 17 00:00:00 2001 From: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Date: Wed, 30 Jul 2025 21:31:23 +0800 Subject: [PATCH 07/32] chore: Update vulnerable eslint dependencies (#23192) --- web/package.json | 7 +- web/pnpm-lock.yaml | 567 ++++++++++++++++++++++----------------------- 2 files changed, 283 insertions(+), 291 deletions(-) diff --git a/web/package.json b/web/package.json index d93788a368..a03334e4a4 100644 --- a/web/package.json +++ b/web/package.json @@ -160,7 +160,7 @@ "@faker-js/faker": "^9.0.3", "@happy-dom/jest-environment": "^17.4.4", "@next/bundle-analyzer": "^15.4.1", - "@next/eslint-plugin-next": "~15.4.4", + "@next/eslint-plugin-next": "~15.4.5", "@rgrove/parse-xml": "^4.1.0", "@storybook/addon-essentials": "8.5.0", "@storybook/addon-interactions": "8.5.0", @@ -196,8 +196,8 @@ "bing-translate-api": "^4.0.2", "code-inspector-plugin": "^0.18.1", "cross-env": "^7.0.3", - "eslint": "^9.20.1", - "eslint-config-next": "~15.4.4", + "eslint": "^9.32.0", + "eslint-config-next": "~15.4.5", "eslint-plugin-oxlint": "^1.6.0", "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-refresh": "^0.4.19", @@ -234,6 +234,7 @@ }, "pnpm": { "overrides": { + "@eslint/plugin-kit@<0.3.4": "0.3.4", "esbuild@<0.25.0": "0.25.0", "pbkdf2@<3.1.3": "3.1.3", "vite@<6.2.7": "6.2.7", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 58153b9fc1..1e36696a8a 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -8,6 +8,7 @@ overrides: '@types/react': ~19.1.8 '@types/react-dom': ~19.1.6 string-width: 4.2.3 + '@eslint/plugin-kit@<0.3.4': 0.3.4 esbuild@<0.25.0: 0.25.0 pbkdf2@<3.1.3: 3.1.3 vite@<6.2.7: 6.2.7 @@ -58,7 +59,7 @@ importers: version: 1.2.1 '@eslint/compat': specifier: ^1.2.4 - version: 1.3.1(eslint@9.31.0(jiti@1.21.7)) + version: 1.3.1(eslint@9.32.0(jiti@1.21.7)) '@floating-ui/react': specifier: ^0.26.25 version: 0.26.28(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -380,13 +381,13 @@ importers: devDependencies: '@antfu/eslint-config': specifier: ^5.0.0 - version: 5.0.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.31.0(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@next/eslint-plugin-next@15.4.4)(@vue/compiler-sfc@3.5.17)(eslint-plugin-react-hooks@5.2.0(eslint@9.31.0(jiti@1.21.7)))(eslint-plugin-react-refresh@0.4.20(eslint@9.31.0(jiti@1.21.7)))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + version: 5.0.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@next/eslint-plugin-next@15.4.5)(@vue/compiler-sfc@3.5.17)(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@1.21.7)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@1.21.7)))(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@chromatic-com/storybook': specifier: ^3.1.0 version: 3.2.7(react@19.1.0)(storybook@8.5.0) '@eslint-react/eslint-plugin': specifier: ^1.15.0 - version: 1.52.3(eslint@9.31.0(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3) + version: 1.52.3(eslint@9.32.0(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3) '@eslint/eslintrc': specifier: ^3.1.0 version: 3.3.1 @@ -403,8 +404,8 @@ importers: specifier: ^15.4.1 version: 15.4.1 '@next/eslint-plugin-next': - specifier: ~15.4.4 - version: 15.4.4 + specifier: ~15.4.5 + version: 15.4.5 '@rgrove/parse-xml': specifier: ^4.1.0 version: 4.2.0 @@ -511,26 +512,26 @@ importers: specifier: ^7.0.3 version: 7.0.3 eslint: - specifier: ^9.20.1 - version: 9.31.0(jiti@1.21.7) + specifier: ^9.32.0 + version: 9.32.0(jiti@1.21.7) eslint-config-next: - specifier: ~15.4.4 - version: 15.4.4(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + specifier: ~15.4.5 + version: 15.4.5(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) eslint-plugin-oxlint: specifier: ^1.6.0 version: 1.6.0 eslint-plugin-react-hooks: specifier: ^5.1.0 - version: 5.2.0(eslint@9.31.0(jiti@1.21.7)) + version: 5.2.0(eslint@9.32.0(jiti@1.21.7)) eslint-plugin-react-refresh: specifier: ^0.4.19 - version: 0.4.20(eslint@9.31.0(jiti@1.21.7)) + version: 0.4.20(eslint@9.32.0(jiti@1.21.7)) eslint-plugin-sonarjs: specifier: ^3.0.2 - version: 3.0.4(eslint@9.31.0(jiti@1.21.7)) + version: 3.0.4(eslint@9.32.0(jiti@1.21.7)) eslint-plugin-storybook: specifier: ^0.11.2 - version: 0.11.6(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + version: 0.11.6(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) eslint-plugin-tailwindcss: specifier: ^3.18.0 version: 3.18.2(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@18.15.0)(typescript@5.8.3))) @@ -572,7 +573,7 @@ importers: version: 5.8.3 typescript-eslint: specifier: ^8.38.0 - version: 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + version: 8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) uglify-js: specifier: ^3.19.3 version: 3.19.3 @@ -1578,6 +1579,10 @@ packages: resolution: {integrity: sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@9.32.0': + resolution: {integrity: sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/markdown@7.1.0': resolution: {integrity: sha512-Y+X1B1j+/zupKDVJfkKc8uYMjQkGzfnd8lt7vK3y8x9Br6H5dBuhAfFrQ6ff7HAMm/1BwgecyEiRFkYCWPRxmA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1586,10 +1591,6 @@ packages: resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.3.3': - resolution: {integrity: sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.3.4': resolution: {integrity: sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2110,8 +2111,8 @@ packages: '@next/env@15.3.5': resolution: {integrity: sha512-7g06v8BUVtN2njAX/r8gheoVffhiKFVt4nx74Tt6G4Hqw9HCLYQVx/GkH2qHvPtAHZaUNZ0VXAa0pQP6v1wk7g==} - '@next/eslint-plugin-next@15.4.4': - resolution: {integrity: sha512-1FDsyN//ai3Jd97SEd7scw5h1yLdzDACGOPRofr2GD3sEFsBylEEoL0MHSerd4n2dq9Zm/mFMqi4+NRMOreOKA==} + '@next/eslint-plugin-next@15.4.5': + resolution: {integrity: sha512-YhbrlbEt0m4jJnXHMY/cCUDBAWgd5SaTa5mJjzOt82QwflAFfW/h3+COp2TfVSzhmscIZ5sg2WXt3MLziqCSCw==} '@next/mdx@15.3.5': resolution: {integrity: sha512-/2rRCgPKNp2ttQscU13auI+cYYACdPa80Okgi/1+NNJJeWn9yVxwGnqZc3SX30T889bZbLqcY4oUjqYGAygL4g==} @@ -2726,9 +2727,6 @@ packages: '@storybook/csf@0.1.12': resolution: {integrity: sha512-9/exVhabisyIVL0VxTCxo01Tdm8wefIXKXfltAPTSr8cbLn5JAxGQ6QV3mjdecLGEOucfoVhAKtJfVHxEK1iqw==} - '@storybook/csf@0.1.13': - resolution: {integrity: sha512-7xOOwCLGB3ebM87eemep89MYRFTko+D8qE7EdAAq74lgdqRR5cOUtYWJLjO2dLtP94nqoOdHJo6MdLLKzg412Q==} - '@storybook/global@5.0.0': resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} @@ -4691,8 +4689,8 @@ packages: peerDependencies: eslint: ^9.5.0 - eslint-config-next@15.4.4: - resolution: {integrity: sha512-sK/lWLUVF5om18O5w76Jt3F8uzu/LP5mVa6TprCMWkjWHUmByq80iHGHcdH7k1dLiJlj+DRIWf98d5piwRsSuA==} + eslint-config-next@15.4.5: + resolution: {integrity: sha512-IMijiXaZ43qFB+Gcpnb374ipTKD8JIyVNR+6VsifFQ/LHyx+A9wgcgSIhCX5PYSjwOoSYD5LtNHKlM5uc23eww==} peerDependencies: eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 typescript: '>=3.3.1' @@ -4997,8 +4995,8 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.31.0: - resolution: {integrity: sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==} + eslint@9.32.0: + resolution: {integrity: sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -8285,50 +8283,50 @@ snapshots: '@jridgewell/gen-mapping': 0.3.12 '@jridgewell/trace-mapping': 0.3.29 - '@antfu/eslint-config@5.0.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.31.0(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@next/eslint-plugin-next@15.4.4)(@vue/compiler-sfc@3.5.17)(eslint-plugin-react-hooks@5.2.0(eslint@9.31.0(jiti@1.21.7)))(eslint-plugin-react-refresh@0.4.20(eslint@9.31.0(jiti@1.21.7)))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': + '@antfu/eslint-config@5.0.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@next/eslint-plugin-next@15.4.5)(@vue/compiler-sfc@3.5.17)(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@1.21.7)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@1.21.7)))(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3)': dependencies: '@antfu/install-pkg': 1.1.0 '@clack/prompts': 0.11.0 - '@eslint-community/eslint-plugin-eslint-comments': 4.5.0(eslint@9.31.0(jiti@1.21.7)) + '@eslint-community/eslint-plugin-eslint-comments': 4.5.0(eslint@9.32.0(jiti@1.21.7)) '@eslint/markdown': 7.1.0 - '@stylistic/eslint-plugin': 5.2.2(eslint@9.31.0(jiti@1.21.7)) - '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@typescript-eslint/parser': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@vitest/eslint-plugin': 1.3.4(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@stylistic/eslint-plugin': 5.2.2(eslint@9.32.0(jiti@1.21.7)) + '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@vitest/eslint-plugin': 1.3.4(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) ansis: 4.1.0 cac: 6.7.14 - eslint: 9.31.0(jiti@1.21.7) - eslint-config-flat-gitignore: 2.1.0(eslint@9.31.0(jiti@1.21.7)) + eslint: 9.32.0(jiti@1.21.7) + eslint-config-flat-gitignore: 2.1.0(eslint@9.32.0(jiti@1.21.7)) eslint-flat-config-utils: 2.1.0 - eslint-merge-processors: 2.0.0(eslint@9.31.0(jiti@1.21.7)) - eslint-plugin-antfu: 3.1.1(eslint@9.31.0(jiti@1.21.7)) - eslint-plugin-command: 3.3.1(eslint@9.31.0(jiti@1.21.7)) - eslint-plugin-import-lite: 0.3.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint-plugin-jsdoc: 51.4.1(eslint@9.31.0(jiti@1.21.7)) - eslint-plugin-jsonc: 2.20.1(eslint@9.31.0(jiti@1.21.7)) - eslint-plugin-n: 17.21.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + eslint-merge-processors: 2.0.0(eslint@9.32.0(jiti@1.21.7)) + eslint-plugin-antfu: 3.1.1(eslint@9.32.0(jiti@1.21.7)) + eslint-plugin-command: 3.3.1(eslint@9.32.0(jiti@1.21.7)) + eslint-plugin-import-lite: 0.3.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + eslint-plugin-jsdoc: 51.4.1(eslint@9.32.0(jiti@1.21.7)) + eslint-plugin-jsonc: 2.20.1(eslint@9.32.0(jiti@1.21.7)) + eslint-plugin-n: 17.21.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) eslint-plugin-no-only-tests: 3.3.0 - eslint-plugin-perfectionist: 4.15.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint-plugin-pnpm: 1.1.0(eslint@9.31.0(jiti@1.21.7)) - eslint-plugin-regexp: 2.9.0(eslint@9.31.0(jiti@1.21.7)) - eslint-plugin-toml: 0.12.0(eslint@9.31.0(jiti@1.21.7)) - eslint-plugin-unicorn: 60.0.0(eslint@9.31.0(jiti@1.21.7)) - eslint-plugin-unused-imports: 4.1.4(@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7)) - eslint-plugin-vue: 10.3.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(vue-eslint-parser@10.2.0(eslint@9.31.0(jiti@1.21.7))) - eslint-plugin-yml: 1.18.0(eslint@9.31.0(jiti@1.21.7)) - eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.17)(eslint@9.31.0(jiti@1.21.7)) + eslint-plugin-perfectionist: 4.15.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + eslint-plugin-pnpm: 1.1.0(eslint@9.32.0(jiti@1.21.7)) + eslint-plugin-regexp: 2.9.0(eslint@9.32.0(jiti@1.21.7)) + eslint-plugin-toml: 0.12.0(eslint@9.32.0(jiti@1.21.7)) + eslint-plugin-unicorn: 60.0.0(eslint@9.32.0(jiti@1.21.7)) + eslint-plugin-unused-imports: 4.1.4(@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.32.0(jiti@1.21.7)) + eslint-plugin-vue: 10.3.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.32.0(jiti@1.21.7))(vue-eslint-parser@10.2.0(eslint@9.32.0(jiti@1.21.7))) + eslint-plugin-yml: 1.18.0(eslint@9.32.0(jiti@1.21.7)) + eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.17)(eslint@9.32.0(jiti@1.21.7)) globals: 16.3.0 jsonc-eslint-parser: 2.4.0 local-pkg: 1.1.1 parse-gitignore: 2.0.0 toml-eslint-parser: 0.10.0 - vue-eslint-parser: 10.2.0(eslint@9.31.0(jiti@1.21.7)) + vue-eslint-parser: 10.2.0(eslint@9.32.0(jiti@1.21.7)) yaml-eslint-parser: 1.3.0 optionalDependencies: - '@eslint-react/eslint-plugin': 1.52.3(eslint@9.31.0(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3) - '@next/eslint-plugin-next': 15.4.4 - eslint-plugin-react-hooks: 5.2.0(eslint@9.31.0(jiti@1.21.7)) - eslint-plugin-react-refresh: 0.4.20(eslint@9.31.0(jiti@1.21.7)) + '@eslint-react/eslint-plugin': 1.52.3(eslint@9.32.0(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3) + '@next/eslint-plugin-next': 15.4.5 + eslint-plugin-react-hooks: 5.2.0(eslint@9.32.0(jiti@1.21.7)) + eslint-plugin-react-refresh: 0.4.20(eslint@9.32.0(jiti@1.21.7)) transitivePeerDependencies: - '@eslint/json' - '@vue/compiler-sfc' @@ -9323,25 +9321,25 @@ snapshots: '@esbuild/win32-x64@0.25.0': optional: true - '@eslint-community/eslint-plugin-eslint-comments@4.5.0(eslint@9.31.0(jiti@1.21.7))': + '@eslint-community/eslint-plugin-eslint-comments@4.5.0(eslint@9.32.0(jiti@1.21.7))': dependencies: escape-string-regexp: 4.0.0 - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) ignore: 5.3.2 - '@eslint-community/eslint-utils@4.7.0(eslint@9.31.0(jiti@1.21.7))': + '@eslint-community/eslint-utils@4.7.0(eslint@9.32.0(jiti@1.21.7))': dependencies: - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint-react/ast@1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': + '@eslint-react/ast@1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3)': dependencies: '@eslint-react/eff': 1.52.3 '@typescript-eslint/types': 8.37.0 '@typescript-eslint/typescript-estree': 8.37.0(typescript@5.8.3) - '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) string-ts: 2.2.1 ts-pattern: 5.7.1 transitivePeerDependencies: @@ -9349,17 +9347,17 @@ snapshots: - supports-color - typescript - '@eslint-react/core@1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': + '@eslint-react/core@1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3)': dependencies: - '@eslint-react/ast': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/ast': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@eslint-react/eff': 1.52.3 - '@eslint-react/kit': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/shared': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/var': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/kit': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/shared': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/var': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@typescript-eslint/scope-manager': 8.37.0 - '@typescript-eslint/type-utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/type-utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@typescript-eslint/types': 8.37.0 - '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) birecord: 0.1.1 ts-pattern: 5.7.1 transitivePeerDependencies: @@ -9369,32 +9367,32 @@ snapshots: '@eslint-react/eff@1.52.3': {} - '@eslint-react/eslint-plugin@1.52.3(eslint@9.31.0(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3)': + '@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3)': dependencies: '@eslint-react/eff': 1.52.3 - '@eslint-react/kit': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/shared': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/kit': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/shared': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@typescript-eslint/scope-manager': 8.37.0 - '@typescript-eslint/type-utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/type-utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@typescript-eslint/types': 8.37.0 - '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint: 9.31.0(jiti@1.21.7) - eslint-plugin-react-debug: 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint-plugin-react-dom: 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint-plugin-react-hooks-extra: 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint-plugin-react-naming-convention: 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint-plugin-react-web-api: 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint-plugin-react-x: 1.52.3(eslint@9.31.0(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3) + '@typescript-eslint/utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + eslint: 9.32.0(jiti@1.21.7) + eslint-plugin-react-debug: 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + eslint-plugin-react-dom: 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + eslint-plugin-react-hooks-extra: 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + eslint-plugin-react-naming-convention: 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + eslint-plugin-react-web-api: 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + eslint-plugin-react-x: 1.52.3(eslint@9.32.0(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3) optionalDependencies: typescript: 5.8.3 transitivePeerDependencies: - supports-color - ts-api-utils - '@eslint-react/kit@1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': + '@eslint-react/kit@1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3)': dependencies: '@eslint-react/eff': 1.52.3 - '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) ts-pattern: 5.7.1 zod: 4.0.5 transitivePeerDependencies: @@ -9402,11 +9400,11 @@ snapshots: - supports-color - typescript - '@eslint-react/shared@1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': + '@eslint-react/shared@1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3)': dependencies: '@eslint-react/eff': 1.52.3 - '@eslint-react/kit': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/kit': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) ts-pattern: 5.7.1 zod: 4.0.5 transitivePeerDependencies: @@ -9414,13 +9412,13 @@ snapshots: - supports-color - typescript - '@eslint-react/var@1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': + '@eslint-react/var@1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3)': dependencies: - '@eslint-react/ast': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/ast': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@eslint-react/eff': 1.52.3 '@typescript-eslint/scope-manager': 8.37.0 '@typescript-eslint/types': 8.37.0 - '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) string-ts: 2.2.1 ts-pattern: 5.7.1 transitivePeerDependencies: @@ -9428,9 +9426,9 @@ snapshots: - supports-color - typescript - '@eslint/compat@1.3.1(eslint@9.31.0(jiti@1.21.7))': + '@eslint/compat@1.3.1(eslint@9.32.0(jiti@1.21.7))': optionalDependencies: - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) '@eslint/config-array@0.21.0': dependencies: @@ -9462,6 +9460,8 @@ snapshots: '@eslint/js@9.31.0': {} + '@eslint/js@9.32.0': {} + '@eslint/markdown@7.1.0': dependencies: '@eslint/core': 0.15.1 @@ -9477,11 +9477,6 @@ snapshots: '@eslint/object-schema@2.1.6': {} - '@eslint/plugin-kit@0.3.3': - dependencies: - '@eslint/core': 0.15.1 - levn: 0.4.1 - '@eslint/plugin-kit@0.3.4': dependencies: '@eslint/core': 0.15.1 @@ -10186,7 +10181,7 @@ snapshots: '@next/env@15.3.5': {} - '@next/eslint-plugin-next@15.4.4': + '@next/eslint-plugin-next@15.4.5': dependencies: fast-glob: 3.3.1 @@ -10860,10 +10855,6 @@ snapshots: dependencies: type-fest: 2.19.0 - '@storybook/csf@0.1.13': - dependencies: - type-fest: 2.19.0 - '@storybook/global@5.0.0': {} '@storybook/icons@1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': @@ -11038,11 +11029,11 @@ snapshots: dependencies: storybook: 8.5.0 - '@stylistic/eslint-plugin@5.2.2(eslint@9.31.0(jiti@1.21.7))': + '@stylistic/eslint-plugin@5.2.2(eslint@9.32.0(jiti@1.21.7))': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.31.0(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@1.21.7)) '@typescript-eslint/types': 8.38.0 - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) eslint-visitor-keys: 4.2.1 espree: 10.4.0 estraverse: 5.3.0 @@ -11476,15 +11467,15 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': + '@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@typescript-eslint/scope-manager': 8.38.0 - '@typescript-eslint/type-utils': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@typescript-eslint/utils': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/type-utils': 8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/utils': 8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@typescript-eslint/visitor-keys': 8.38.0 - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 @@ -11493,14 +11484,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': + '@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3)': dependencies: '@typescript-eslint/scope-manager': 8.38.0 '@typescript-eslint/types': 8.38.0 '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3) '@typescript-eslint/visitor-keys': 8.38.0 debug: 4.4.1 - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -11541,25 +11532,25 @@ snapshots: dependencies: typescript: 5.8.3 - '@typescript-eslint/type-utils@8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': + '@typescript-eslint/type-utils@8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3)': dependencies: '@typescript-eslint/types': 8.37.0 '@typescript-eslint/typescript-estree': 8.37.0(typescript@5.8.3) - '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) debug: 4.4.1 - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) ts-api-utils: 2.1.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': + '@typescript-eslint/type-utils@8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3)': dependencies: '@typescript-eslint/types': 8.38.0 '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3) - '@typescript-eslint/utils': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/utils': 8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) debug: 4.4.1 - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) ts-api-utils: 2.1.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: @@ -11601,24 +11592,24 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': + '@typescript-eslint/utils@8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.31.0(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@1.21.7)) '@typescript-eslint/scope-manager': 8.37.0 '@typescript-eslint/types': 8.37.0 '@typescript-eslint/typescript-estree': 8.37.0(typescript@5.8.3) - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': + '@typescript-eslint/utils@8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.31.0(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@1.21.7)) '@typescript-eslint/scope-manager': 8.38.0 '@typescript-eslint/types': 8.38.0 '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3) - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -11694,10 +11685,10 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@vitest/eslint-plugin@1.3.4(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': + '@vitest/eslint-plugin@1.3.4(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3)': dependencies: - '@typescript-eslint/utils': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint: 9.31.0(jiti@1.21.7) + '@typescript-eslint/utils': 8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + eslint: 9.32.0(jiti@1.21.7) optionalDependencies: typescript: 5.8.3 transitivePeerDependencies: @@ -13076,34 +13067,34 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-compat-utils@0.5.1(eslint@9.31.0(jiti@1.21.7)): + eslint-compat-utils@0.5.1(eslint@9.32.0(jiti@1.21.7)): dependencies: - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) semver: 7.7.2 - eslint-compat-utils@0.6.5(eslint@9.31.0(jiti@1.21.7)): + eslint-compat-utils@0.6.5(eslint@9.32.0(jiti@1.21.7)): dependencies: - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) semver: 7.7.2 - eslint-config-flat-gitignore@2.1.0(eslint@9.31.0(jiti@1.21.7)): + eslint-config-flat-gitignore@2.1.0(eslint@9.32.0(jiti@1.21.7)): dependencies: - '@eslint/compat': 1.3.1(eslint@9.31.0(jiti@1.21.7)) - eslint: 9.31.0(jiti@1.21.7) + '@eslint/compat': 1.3.1(eslint@9.32.0(jiti@1.21.7)) + eslint: 9.32.0(jiti@1.21.7) - eslint-config-next@15.4.4(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3): + eslint-config-next@15.4.5(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3): dependencies: - '@next/eslint-plugin-next': 15.4.4 + '@next/eslint-plugin-next': 15.4.5 '@rushstack/eslint-patch': 1.12.0 - '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@typescript-eslint/parser': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint: 9.31.0(jiti@1.21.7) + '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + eslint: 9.32.0(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.31.0(jiti@1.21.7)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.31.0(jiti@1.21.7)) - eslint-plugin-jsx-a11y: 6.10.2(eslint@9.31.0(jiti@1.21.7)) - eslint-plugin-react: 7.37.5(eslint@9.31.0(jiti@1.21.7)) - eslint-plugin-react-hooks: 5.2.0(eslint@9.31.0(jiti@1.21.7)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.32.0(jiti@1.21.7)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@1.21.7)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.32.0(jiti@1.21.7)) + eslint-plugin-react: 7.37.5(eslint@9.32.0(jiti@1.21.7)) + eslint-plugin-react-hooks: 5.2.0(eslint@9.32.0(jiti@1.21.7)) optionalDependencies: typescript: 5.8.3 transitivePeerDependencies: @@ -13123,67 +13114,67 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.31.0(jiti@1.21.7)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.32.0(jiti@1.21.7)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1 - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) get-tsconfig: 4.10.1 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.14 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.31.0(jiti@1.21.7)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@1.21.7)) transitivePeerDependencies: - supports-color - eslint-json-compat-utils@0.2.1(eslint@9.31.0(jiti@1.21.7))(jsonc-eslint-parser@2.4.0): + eslint-json-compat-utils@0.2.1(eslint@9.32.0(jiti@1.21.7))(jsonc-eslint-parser@2.4.0): dependencies: - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) esquery: 1.6.0 jsonc-eslint-parser: 2.4.0 - eslint-merge-processors@2.0.0(eslint@9.31.0(jiti@1.21.7)): + eslint-merge-processors@2.0.0(eslint@9.32.0(jiti@1.21.7)): dependencies: - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.31.0(jiti@1.21.7)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@1.21.7)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint: 9.31.0(jiti@1.21.7) + '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + eslint: 9.32.0(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.31.0(jiti@1.21.7)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.32.0(jiti@1.21.7)) transitivePeerDependencies: - supports-color - eslint-plugin-antfu@3.1.1(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-antfu@3.1.1(eslint@9.32.0(jiti@1.21.7)): dependencies: - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) - eslint-plugin-command@3.3.1(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-command@3.3.1(eslint@9.32.0(jiti@1.21.7)): dependencies: '@es-joy/jsdoccomment': 0.50.2 - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) - eslint-plugin-es-x@7.8.0(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-es-x@7.8.0(eslint@9.32.0(jiti@1.21.7)): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.31.0(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@1.21.7)) '@eslint-community/regexpp': 4.12.1 - eslint: 9.31.0(jiti@1.21.7) - eslint-compat-utils: 0.5.1(eslint@9.31.0(jiti@1.21.7)) + eslint: 9.32.0(jiti@1.21.7) + eslint-compat-utils: 0.5.1(eslint@9.32.0(jiti@1.21.7)) - eslint-plugin-import-lite@0.3.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3): + eslint-plugin-import-lite@0.3.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.31.0(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@1.21.7)) '@typescript-eslint/types': 8.38.0 - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) optionalDependencies: typescript: 5.8.3 - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@1.21.7)): dependencies: '@rtsao/scc': 1.1.0 array-includes: '@nolyfill/array-includes@1.0.44' @@ -13192,9 +13183,9 @@ snapshots: array.prototype.flatmap: '@nolyfill/array.prototype.flatmap@1.0.44' debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.31.0(jiti@1.21.7)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@1.21.7)) hasown: '@nolyfill/hasown@1.0.44' is-core-module: '@nolyfill/is-core-module@1.0.39' is-glob: 4.0.3 @@ -13206,20 +13197,20 @@ snapshots: string.prototype.trimend: '@nolyfill/string.prototype.trimend@1.0.44' tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsdoc@51.4.1(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-jsdoc@51.4.1(eslint@9.32.0(jiti@1.21.7)): dependencies: '@es-joy/jsdoccomment': 0.52.0 are-docs-informative: 0.0.2 comment-parser: 1.4.1 debug: 4.4.1 escape-string-regexp: 4.0.0 - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) espree: 10.4.0 esquery: 1.6.0 parse-imports-exports: 0.2.4 @@ -13228,12 +13219,12 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-jsonc@2.20.1(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-jsonc@2.20.1(eslint@9.32.0(jiti@1.21.7)): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.31.0(jiti@1.21.7)) - eslint: 9.31.0(jiti@1.21.7) - eslint-compat-utils: 0.6.5(eslint@9.31.0(jiti@1.21.7)) - eslint-json-compat-utils: 0.2.1(eslint@9.31.0(jiti@1.21.7))(jsonc-eslint-parser@2.4.0) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@1.21.7)) + eslint: 9.32.0(jiti@1.21.7) + eslint-compat-utils: 0.6.5(eslint@9.32.0(jiti@1.21.7)) + eslint-json-compat-utils: 0.2.1(eslint@9.32.0(jiti@1.21.7))(jsonc-eslint-parser@2.4.0) espree: 10.4.0 graphemer: 1.4.0 jsonc-eslint-parser: 2.4.0 @@ -13242,7 +13233,7 @@ snapshots: transitivePeerDependencies: - '@eslint/json' - eslint-plugin-jsx-a11y@6.10.2(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-jsx-a11y@6.10.2(eslint@9.32.0(jiti@1.21.7)): dependencies: aria-query: 5.3.2 array-includes: '@nolyfill/array-includes@1.0.44' @@ -13252,7 +13243,7 @@ snapshots: axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) hasown: '@nolyfill/hasown@1.0.44' jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -13261,12 +13252,12 @@ snapshots: safe-regex-test: '@nolyfill/safe-regex-test@1.0.44' string.prototype.includes: '@nolyfill/string.prototype.includes@1.0.44' - eslint-plugin-n@17.21.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3): + eslint-plugin-n@17.21.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.31.0(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@1.21.7)) enhanced-resolve: 5.18.2 - eslint: 9.31.0(jiti@1.21.7) - eslint-plugin-es-x: 7.8.0(eslint@9.31.0(jiti@1.21.7)) + eslint: 9.32.0(jiti@1.21.7) + eslint-plugin-es-x: 7.8.0(eslint@9.32.0(jiti@1.21.7)) get-tsconfig: 4.10.1 globals: 15.15.0 ignore: 5.3.2 @@ -13282,19 +13273,19 @@ snapshots: dependencies: jsonc-parser: 3.3.1 - eslint-plugin-perfectionist@4.15.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3): + eslint-plugin-perfectionist@4.15.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3): dependencies: '@typescript-eslint/types': 8.38.0 - '@typescript-eslint/utils': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint: 9.31.0(jiti@1.21.7) + '@typescript-eslint/utils': 8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + eslint: 9.32.0(jiti@1.21.7) natural-orderby: 5.0.0 transitivePeerDependencies: - supports-color - typescript - eslint-plugin-pnpm@1.1.0(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-pnpm@1.1.0(eslint@9.32.0(jiti@1.21.7)): dependencies: - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) find-up-simple: 1.0.1 jsonc-eslint-parser: 2.4.0 pathe: 2.0.3 @@ -13302,19 +13293,19 @@ snapshots: tinyglobby: 0.2.14 yaml-eslint-parser: 1.3.0 - eslint-plugin-react-debug@1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3): + eslint-plugin-react-debug@1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3): dependencies: - '@eslint-react/ast': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/core': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/ast': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/core': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@eslint-react/eff': 1.52.3 - '@eslint-react/kit': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/shared': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/var': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/kit': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/shared': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/var': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@typescript-eslint/scope-manager': 8.37.0 - '@typescript-eslint/type-utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/type-utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@typescript-eslint/types': 8.37.0 - '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint: 9.31.0(jiti@1.21.7) + '@typescript-eslint/utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + eslint: 9.32.0(jiti@1.21.7) string-ts: 2.2.1 ts-pattern: 5.7.1 optionalDependencies: @@ -13322,19 +13313,19 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react-dom@1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3): + eslint-plugin-react-dom@1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3): dependencies: - '@eslint-react/ast': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/core': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/ast': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/core': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@eslint-react/eff': 1.52.3 - '@eslint-react/kit': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/shared': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/var': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/kit': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/shared': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/var': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@typescript-eslint/scope-manager': 8.37.0 '@typescript-eslint/types': 8.37.0 - '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) compare-versions: 6.1.1 - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) string-ts: 2.2.1 ts-pattern: 5.7.1 optionalDependencies: @@ -13342,19 +13333,19 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react-hooks-extra@1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3): + eslint-plugin-react-hooks-extra@1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3): dependencies: - '@eslint-react/ast': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/core': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/ast': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/core': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@eslint-react/eff': 1.52.3 - '@eslint-react/kit': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/shared': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/var': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/kit': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/shared': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/var': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@typescript-eslint/scope-manager': 8.37.0 - '@typescript-eslint/type-utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/type-utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@typescript-eslint/types': 8.37.0 - '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint: 9.31.0(jiti@1.21.7) + '@typescript-eslint/utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + eslint: 9.32.0(jiti@1.21.7) string-ts: 2.2.1 ts-pattern: 5.7.1 optionalDependencies: @@ -13362,23 +13353,23 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react-hooks@5.2.0(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@1.21.7)): dependencies: - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) - eslint-plugin-react-naming-convention@1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3): + eslint-plugin-react-naming-convention@1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3): dependencies: - '@eslint-react/ast': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/core': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/ast': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/core': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@eslint-react/eff': 1.52.3 - '@eslint-react/kit': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/shared': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/var': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/kit': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/shared': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/var': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@typescript-eslint/scope-manager': 8.37.0 - '@typescript-eslint/type-utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/type-utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@typescript-eslint/types': 8.37.0 - '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint: 9.31.0(jiti@1.21.7) + '@typescript-eslint/utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + eslint: 9.32.0(jiti@1.21.7) string-ts: 2.2.1 ts-pattern: 5.7.1 optionalDependencies: @@ -13386,22 +13377,22 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react-refresh@0.4.20(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@1.21.7)): dependencies: - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) - eslint-plugin-react-web-api@1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3): + eslint-plugin-react-web-api@1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3): dependencies: - '@eslint-react/ast': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/core': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/ast': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/core': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@eslint-react/eff': 1.52.3 - '@eslint-react/kit': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/shared': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/var': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/kit': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/shared': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/var': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@typescript-eslint/scope-manager': 8.37.0 '@typescript-eslint/types': 8.37.0 - '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint: 9.31.0(jiti@1.21.7) + '@typescript-eslint/utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + eslint: 9.32.0(jiti@1.21.7) string-ts: 2.2.1 ts-pattern: 5.7.1 optionalDependencies: @@ -13409,21 +13400,21 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react-x@1.52.3(eslint@9.31.0(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3): + eslint-plugin-react-x@1.52.3(eslint@9.32.0(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3): dependencies: - '@eslint-react/ast': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/core': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/ast': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/core': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@eslint-react/eff': 1.52.3 - '@eslint-react/kit': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/shared': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@eslint-react/var': 1.52.3(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/kit': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/shared': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint-react/var': 1.52.3(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@typescript-eslint/scope-manager': 8.37.0 - '@typescript-eslint/type-utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/type-utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@typescript-eslint/types': 8.37.0 - '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) compare-versions: 6.1.1 - eslint: 9.31.0(jiti@1.21.7) - is-immutable-type: 5.0.1(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + eslint: 9.32.0(jiti@1.21.7) + is-immutable-type: 5.0.1(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) string-ts: 2.2.1 ts-pattern: 5.7.1 optionalDependencies: @@ -13432,7 +13423,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react@7.37.5(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-react@7.37.5(eslint@9.32.0(jiti@1.21.7)): dependencies: array-includes: '@nolyfill/array-includes@1.0.44' array.prototype.findlast: '@nolyfill/array.prototype.findlast@1.0.44' @@ -13440,7 +13431,7 @@ snapshots: array.prototype.tosorted: '@nolyfill/array.prototype.tosorted@1.0.44' doctrine: 2.1.0 es-iterator-helpers: '@nolyfill/es-iterator-helpers@1.0.21' - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) estraverse: 5.3.0 hasown: '@nolyfill/hasown@1.0.44' jsx-ast-utils: 3.3.5 @@ -13454,23 +13445,23 @@ snapshots: string.prototype.matchall: '@nolyfill/string.prototype.matchall@1.0.44' string.prototype.repeat: '@nolyfill/string.prototype.repeat@1.0.44' - eslint-plugin-regexp@2.9.0(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-regexp@2.9.0(eslint@9.32.0(jiti@1.21.7)): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.31.0(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@1.21.7)) '@eslint-community/regexpp': 4.12.1 comment-parser: 1.4.1 - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) jsdoc-type-pratt-parser: 4.1.0 refa: 0.12.1 regexp-ast-analysis: 0.7.1 scslre: 0.3.0 - eslint-plugin-sonarjs@3.0.4(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-sonarjs@3.0.4(eslint@9.32.0(jiti@1.21.7)): dependencies: '@eslint-community/regexpp': 4.12.1 builtin-modules: 3.3.0 bytes: 3.1.2 - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) functional-red-black-tree: 1.0.1 jsx-ast-utils: 3.3.5 lodash.merge: 4.6.2 @@ -13479,11 +13470,11 @@ snapshots: semver: 7.7.2 typescript: 5.8.3 - eslint-plugin-storybook@0.11.6(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3): + eslint-plugin-storybook@0.11.6(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3): dependencies: - '@storybook/csf': 0.1.13 - '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint: 9.31.0(jiti@1.21.7) + '@storybook/csf': 0.1.12 + '@typescript-eslint/utils': 8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + eslint: 9.32.0(jiti@1.21.7) ts-dedent: 2.2.0 transitivePeerDependencies: - supports-color @@ -13495,26 +13486,26 @@ snapshots: postcss: 8.5.6 tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@18.15.0)(typescript@5.8.3)) - eslint-plugin-toml@0.12.0(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-toml@0.12.0(eslint@9.32.0(jiti@1.21.7)): dependencies: debug: 4.4.1 - eslint: 9.31.0(jiti@1.21.7) - eslint-compat-utils: 0.6.5(eslint@9.31.0(jiti@1.21.7)) + eslint: 9.32.0(jiti@1.21.7) + eslint-compat-utils: 0.6.5(eslint@9.32.0(jiti@1.21.7)) lodash: 4.17.21 toml-eslint-parser: 0.10.0 transitivePeerDependencies: - supports-color - eslint-plugin-unicorn@60.0.0(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-unicorn@60.0.0(eslint@9.32.0(jiti@1.21.7)): dependencies: '@babel/helper-validator-identifier': 7.27.1 - '@eslint-community/eslint-utils': 4.7.0(eslint@9.31.0(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@1.21.7)) '@eslint/plugin-kit': 0.3.4 change-case: 5.4.4 ci-info: 4.3.0 clean-regexp: 1.0.0 core-js-compat: 3.44.0 - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) esquery: 1.6.0 find-up-simple: 1.0.1 globals: 16.3.0 @@ -13527,40 +13518,40 @@ snapshots: semver: 7.7.2 strip-indent: 4.0.0 - eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.32.0(jiti@1.21.7)): dependencies: - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) - eslint-plugin-vue@10.3.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(vue-eslint-parser@10.2.0(eslint@9.31.0(jiti@1.21.7))): + eslint-plugin-vue@10.3.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.32.0(jiti@1.21.7))(vue-eslint-parser@10.2.0(eslint@9.32.0(jiti@1.21.7))): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.31.0(jiti@1.21.7)) - eslint: 9.31.0(jiti@1.21.7) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@1.21.7)) + eslint: 9.32.0(jiti@1.21.7) natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 6.1.2 semver: 7.7.2 - vue-eslint-parser: 10.2.0(eslint@9.31.0(jiti@1.21.7)) + vue-eslint-parser: 10.2.0(eslint@9.32.0(jiti@1.21.7)) xml-name-validator: 4.0.0 optionalDependencies: - '@typescript-eslint/parser': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) - eslint-plugin-yml@1.18.0(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-yml@1.18.0(eslint@9.32.0(jiti@1.21.7)): dependencies: debug: 4.4.1 escape-string-regexp: 4.0.0 - eslint: 9.31.0(jiti@1.21.7) - eslint-compat-utils: 0.6.5(eslint@9.31.0(jiti@1.21.7)) + eslint: 9.32.0(jiti@1.21.7) + eslint-compat-utils: 0.6.5(eslint@9.32.0(jiti@1.21.7)) natural-compare: 1.4.0 yaml-eslint-parser: 1.3.0 transitivePeerDependencies: - supports-color - eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.17)(eslint@9.31.0(jiti@1.21.7)): + eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.17)(eslint@9.32.0(jiti@1.21.7)): dependencies: '@vue/compiler-sfc': 3.5.17 - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) eslint-scope@5.1.1: dependencies: @@ -13576,16 +13567,16 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.31.0(jiti@1.21.7): + eslint@9.32.0(jiti@1.21.7): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.31.0(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@1.21.7)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.21.0 '@eslint/config-helpers': 0.3.0 '@eslint/core': 0.15.1 '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.31.0 - '@eslint/plugin-kit': 0.3.3 + '@eslint/js': 9.32.0 + '@eslint/plugin-kit': 0.3.4 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 @@ -14328,10 +14319,10 @@ snapshots: is-hexadecimal@2.0.1: {} - is-immutable-type@5.0.1(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3): + is-immutable-type@5.0.1(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3): dependencies: - '@typescript-eslint/type-utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint: 9.31.0(jiti@1.21.7) + '@typescript-eslint/type-utils': 8.37.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + eslint: 9.32.0(jiti@1.21.7) ts-api-utils: 2.1.0(typescript@5.8.3) ts-declaration-location: 1.0.7(typescript@5.8.3) typescript: 5.8.3 @@ -17208,13 +17199,13 @@ snapshots: type-fest@2.19.0: {} - typescript-eslint@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3): + typescript-eslint@8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@typescript-eslint/parser': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3) - '@typescript-eslint/utils': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint: 9.31.0(jiti@1.21.7) + '@typescript-eslint/utils': 8.38.0(eslint@9.32.0(jiti@1.21.7))(typescript@5.8.3) + eslint: 9.32.0(jiti@1.21.7) typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -17427,10 +17418,10 @@ snapshots: vscode-uri@3.0.8: {} - vue-eslint-parser@10.2.0(eslint@9.31.0(jiti@1.21.7)): + vue-eslint-parser@10.2.0(eslint@9.32.0(jiti@1.21.7)): dependencies: debug: 4.4.1 - eslint: 9.31.0(jiti@1.21.7) + eslint: 9.32.0(jiti@1.21.7) eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 espree: 10.4.0 From ffddabde43d541d380b46922596c6dbf0e575140 Mon Sep 17 00:00:00 2001 From: Aurelius Huang Date: Wed, 30 Jul 2025 21:35:20 +0800 Subject: [PATCH 08/32] feat(notion): Notion Database extracts Rows content `in row order` and appends `Row Page URL` (#22646) Co-authored-by: Aurelius Huang --- api/core/rag/extractor/notion_extractor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/core/rag/extractor/notion_extractor.py b/api/core/rag/extractor/notion_extractor.py index 875626eb34..17f4d1af2d 100644 --- a/api/core/rag/extractor/notion_extractor.py +++ b/api/core/rag/extractor/notion_extractor.py @@ -1,5 +1,6 @@ import json import logging +import operator from typing import Any, Optional, cast import requests @@ -130,13 +131,15 @@ class NotionExtractor(BaseExtractor): data[property_name] = value row_dict = {k: v for k, v in data.items() if v} row_content = "" - for key, value in row_dict.items(): + for key, value in sorted(row_dict.items(), key=operator.itemgetter(0)): if isinstance(value, dict): value_dict = {k: v for k, v in value.items() if v} value_content = "".join(f"{k}:{v} " for k, v in value_dict.items()) row_content = row_content + f"{key}:{value_content}\n" else: row_content = row_content + f"{key}:{value}\n" + if "url" in result: + row_content = row_content + f"Row Page URL:{result.get('url', '')}\n" database_content.append(row_content) has_more = response_data.get("has_more", False) From 142ab7478415fd128931931da52647cde54b36bd Mon Sep 17 00:00:00 2001 From: Ali Saleh Date: Thu, 31 Jul 2025 03:58:26 +0500 Subject: [PATCH 09/32] feat: Enable Tracing Support For Phoenix Cloud Instance (#23196) --- .../arize_phoenix_trace/arize_phoenix_trace.py | 11 +++++++++-- api/core/ops/entities/config_entity.py | 2 +- .../unit_tests/core/ops/test_config_entity.py | 17 ++++++++++++----- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/api/core/ops/arize_phoenix_trace/arize_phoenix_trace.py b/api/core/ops/arize_phoenix_trace/arize_phoenix_trace.py index a20f2485c8..e7c90c1229 100644 --- a/api/core/ops/arize_phoenix_trace/arize_phoenix_trace.py +++ b/api/core/ops/arize_phoenix_trace/arize_phoenix_trace.py @@ -4,6 +4,7 @@ import logging import os from datetime import datetime, timedelta from typing import Any, Optional, Union, cast +from urllib.parse import urlparse from openinference.semconv.trace import OpenInferenceSpanKindValues, SpanAttributes from opentelemetry import trace @@ -40,8 +41,14 @@ def setup_tracer(arize_phoenix_config: ArizeConfig | PhoenixConfig) -> tuple[tra try: # Choose the appropriate exporter based on config type exporter: Union[GrpcOTLPSpanExporter, HttpOTLPSpanExporter] + + # Inspect the provided endpoint to determine its structure + parsed = urlparse(arize_phoenix_config.endpoint) + base_endpoint = f"{parsed.scheme}://{parsed.netloc}" + path = parsed.path.rstrip("/") + if isinstance(arize_phoenix_config, ArizeConfig): - arize_endpoint = f"{arize_phoenix_config.endpoint}/v1" + arize_endpoint = f"{base_endpoint}/v1" arize_headers = { "api_key": arize_phoenix_config.api_key or "", "space_id": arize_phoenix_config.space_id or "", @@ -53,7 +60,7 @@ def setup_tracer(arize_phoenix_config: ArizeConfig | PhoenixConfig) -> tuple[tra timeout=30, ) else: - phoenix_endpoint = f"{arize_phoenix_config.endpoint}/v1/traces" + phoenix_endpoint = f"{base_endpoint}{path}/v1/traces" phoenix_headers = { "api_key": arize_phoenix_config.api_key or "", "authorization": f"Bearer {arize_phoenix_config.api_key or ''}", diff --git a/api/core/ops/entities/config_entity.py b/api/core/ops/entities/config_entity.py index 626782cee5..851a77fbc1 100644 --- a/api/core/ops/entities/config_entity.py +++ b/api/core/ops/entities/config_entity.py @@ -87,7 +87,7 @@ class PhoenixConfig(BaseTracingConfig): @field_validator("endpoint") @classmethod def endpoint_validator(cls, v, info: ValidationInfo): - return cls.validate_endpoint_url(v, "https://app.phoenix.arize.com") + return validate_url_with_path(v, "https://app.phoenix.arize.com") class LangfuseConfig(BaseTracingConfig): diff --git a/api/tests/unit_tests/core/ops/test_config_entity.py b/api/tests/unit_tests/core/ops/test_config_entity.py index 209f8b7c57..1dc380ad0b 100644 --- a/api/tests/unit_tests/core/ops/test_config_entity.py +++ b/api/tests/unit_tests/core/ops/test_config_entity.py @@ -102,9 +102,14 @@ class TestPhoenixConfig: assert config.project == "default" def test_endpoint_validation_with_path(self): - """Test endpoint validation normalizes URL by removing path""" - config = PhoenixConfig(endpoint="https://custom.phoenix.com/api/v1") - assert config.endpoint == "https://custom.phoenix.com" + """Test endpoint validation with path""" + config = PhoenixConfig(endpoint="https://app.phoenix.arize.com/s/dify-integration") + assert config.endpoint == "https://app.phoenix.arize.com/s/dify-integration" + + def test_endpoint_validation_without_path(self): + """Test endpoint validation without path""" + config = PhoenixConfig(endpoint="https://app.phoenix.arize.com") + assert config.endpoint == "https://app.phoenix.arize.com" class TestLangfuseConfig: @@ -368,13 +373,15 @@ class TestConfigIntegration: """Test that URL normalization works consistently across configs""" # Test that paths are removed from endpoints arize_config = ArizeConfig(endpoint="https://arize.com/api/v1/test") - phoenix_config = PhoenixConfig(endpoint="https://phoenix.com/api/v2/") + phoenix_with_path_config = PhoenixConfig(endpoint="https://app.phoenix.arize.com/s/dify-integration") + phoenix_without_path_config = PhoenixConfig(endpoint="https://app.phoenix.arize.com") aliyun_config = AliyunConfig( license_key="test_license", endpoint="https://tracing-analysis-dc-hz.aliyuncs.com/api/v1/traces" ) assert arize_config.endpoint == "https://arize.com" - assert phoenix_config.endpoint == "https://phoenix.com" + assert phoenix_with_path_config.endpoint == "https://app.phoenix.arize.com/s/dify-integration" + assert phoenix_without_path_config.endpoint == "https://app.phoenix.arize.com" assert aliyun_config.endpoint == "https://tracing-analysis-dc-hz.aliyuncs.com" def test_project_default_values(self): From 646900b00c36ec968a3d713e8364e34d7f0b139c Mon Sep 17 00:00:00 2001 From: znn Date: Thu, 31 Jul 2025 07:33:03 +0530 Subject: [PATCH 10/32] fixing embedded chat styling (#23198) --- web/public/embed.js | 2 ++ web/public/embed.min.js | 66 ++++++++++++++++++++++++++++------------- 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/web/public/embed.js b/web/public/embed.js index e41405dbf8..54aa6a95b1 100644 --- a/web/public/embed.js +++ b/web/public/embed.js @@ -38,6 +38,7 @@ height: 43.75rem; max-height: calc(100vh - 6rem); border: none; + border-radius: 1rem; z-index: 2147483640; overflow: hidden; user-select: none; @@ -62,6 +63,7 @@ height: 88%; max-height: calc(100vh - 6rem); border: none; + border-radius: 1rem; z-index: 2147483640; overflow: hidden; user-select: none; diff --git a/web/public/embed.min.js b/web/public/embed.min.js index b1d6f56920..42132e0359 100644 --- a/web/public/embed.min.js +++ b/web/public/embed.min.js @@ -1,42 +1,66 @@ -(()=>{let t="difyChatbotConfig",h="dify-chatbot-bubble-button",m="dify-chatbot-bubble-window",y=window[t],a=!1,l=` +(function(){const configKey="difyChatbotConfig";const buttonId="dify-chatbot-bubble-button";const iframeId="dify-chatbot-bubble-window";const config=window[configKey];let isExpanded=false;const svgIcons=` + + + + `;const originalIframeStyleText=` position: absolute; display: flex; flex-direction: column; justify-content: space-between; top: unset; - right: var(--${h}-right, 1rem); /* Align with dify-chatbot-bubble-button. */ - bottom: var(--${h}-bottom, 1rem); /* Align with dify-chatbot-bubble-button. */ + right: var(--${buttonId}-right, 1rem); /* Align with dify-chatbot-bubble-button. */ + bottom: var(--${buttonId}-bottom, 1rem); /* Align with dify-chatbot-bubble-button. */ left: unset; width: 24rem; max-width: calc(100vw - 2rem); height: 43.75rem; max-height: calc(100vh - 6rem); border: none; + border-radius: 1rem; z-index: 2147483640; overflow: hidden; user-select: none; transition-property: width, height; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms; - `;async function e(){let u=!1;if(y&&y.token){var e=new URLSearchParams({...await(async()=>{var e=y?.inputs||{};let n={};return await Promise.all(Object.entries(e).map(async([e,t])=>{n[e]=await i(t)})),n})(),...await(async()=>{var e=y?.systemVariables||{};let n={};return await Promise.all(Object.entries(e).map(async([e,t])=>{n["sys."+e]=await i(t)})),n})(),...await(async()=>{var e=y?.userVariables||{};let n={};return await Promise.all(Object.entries(e).map(async([e,t])=>{n["user."+e]=await i(t)})),n})()}),n=y.baseUrl||`https://${y.isDev?"dev.":""}udify.app`;let o=new URL(n).origin,t=`${n}/chatbot/${y.token}?`+e;n=s();async function i(e){e=(new TextEncoder).encode(e),e=new Response(new Blob([e]).stream().pipeThrough(new CompressionStream("gzip"))).arrayBuffer(),e=new Uint8Array(await e);return btoa(String.fromCharCode(...e))}function s(){var e=document.createElement("iframe");return e.allow="fullscreen;microphone",e.title="dify chatbot bubble window",e.id=m,e.src=t,e.style.cssText=l,e}function r(){var e,t,n;window.innerWidth<=640||(e=document.getElementById(m),t=document.getElementById(h),e&&t&&(t=t.getBoundingClientRect(),n=window.innerHeight/2,t.top+t.height/2{"className"===e?n.classList.add(...t.split(" ")):"style"===e?"object"==typeof t?Object.assign(n.style,t):n.style.cssText=t:"function"==typeof t?n.addEventListener(e.replace(/^on/,"").toLowerCase(),t):n[e]=t}),n.id=h;var e=document.createElement("style"),e=(document.head.appendChild(e),e.sheet.insertRule(` - #${n.id} { + `;const expandedIframeStyleText=` + position: absolute; + display: flex; + flex-direction: column; + justify-content: space-between; + top: unset; + right: var(--${buttonId}-right, 1rem); /* Align with dify-chatbot-bubble-button. */ + bottom: var(--${buttonId}-bottom, 1rem); /* Align with dify-chatbot-bubble-button. */ + left: unset; + min-width: 24rem; + width: 48%; + max-width: 40rem; /* Match mobile breakpoint*/ + min-height: 43.75rem; + height: 88%; + max-height: calc(100vh - 6rem); + border: none; + border-radius: 1rem; + z-index: 2147483640; + overflow: hidden; + user-select: none; + transition-property: width, height; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + `;async function embedChatbot(){let isDragging=false;if(!config||!config.token){console.error(`${configKey} is empty or token is not provided`);return}async function compressAndEncodeBase64(input){const uint8Array=(new TextEncoder).encode(input);const compressedStream=new Response(new Blob([uint8Array]).stream().pipeThrough(new CompressionStream("gzip"))).arrayBuffer();const compressedUint8Array=new Uint8Array(await compressedStream);return btoa(String.fromCharCode(...compressedUint8Array))}async function getCompressedInputsFromConfig(){const inputs=config?.inputs||{};const compressedInputs={};await Promise.all(Object.entries(inputs).map(async([key,value])=>{compressedInputs[key]=await compressAndEncodeBase64(value)}));return compressedInputs}async function getCompressedSystemVariablesFromConfig(){const systemVariables=config?.systemVariables||{};const compressedSystemVariables={};await Promise.all(Object.entries(systemVariables).map(async([key,value])=>{compressedSystemVariables[`sys.${key}`]=await compressAndEncodeBase64(value)}));return compressedSystemVariables}async function getCompressedUserVariablesFromConfig(){const userVariables=config?.userVariables||{};const compressedUserVariables={};await Promise.all(Object.entries(userVariables).map(async([key,value])=>{compressedUserVariables[`user.${key}`]=await compressAndEncodeBase64(value)}));return compressedUserVariables}const params=new URLSearchParams({...await getCompressedInputsFromConfig(),...await getCompressedSystemVariablesFromConfig(),...await getCompressedUserVariablesFromConfig()});const baseUrl=config.baseUrl||`https://${config.isDev?"dev.":""}udify.app`;const targetOrigin=new URL(baseUrl).origin;const iframeUrl=`${baseUrl}/chatbot/${config.token}?${params}`;const preloadedIframe=createIframe();preloadedIframe.style.display="none";document.body.appendChild(preloadedIframe);if(iframeUrl.length>2048){console.error("The URL is too long, please reduce the number of inputs to prevent the bot from failing to load")}function createIframe(){const iframe=document.createElement("iframe");iframe.allow="fullscreen;microphone";iframe.title="dify chatbot bubble window";iframe.id=iframeId;iframe.src=iframeUrl;iframe.style.cssText=originalIframeStyleText;return iframe}function resetIframePosition(){if(window.innerWidth<=640)return;const targetIframe=document.getElementById(iframeId);const targetButton=document.getElementById(buttonId);if(targetIframe&&targetButton){const buttonRect=targetButton.getBoundingClientRect();const viewportCenterY=window.innerHeight/2;const buttonCenterY=buttonRect.top+buttonRect.height/2;if(buttonCenterY{if(event.origin!==targetOrigin)return;const targetIframe=document.getElementById(iframeId);if(!targetIframe||event.source!==targetIframe.contentWindow)return;if(event.data.type==="dify-chatbot-iframe-ready"){targetIframe.contentWindow?.postMessage({type:"dify-chatbot-config",payload:{isToggledByButton:true,isDraggable:!!config.draggable}},targetOrigin)}if(event.data.type==="dify-chatbot-expand-change"){toggleExpand()}});function createButton(){const containerDiv=document.createElement("div");Object.entries(config.containerProps||{}).forEach(([key,value])=>{if(key==="className"){containerDiv.classList.add(...value.split(" "))}else if(key==="style"){if(typeof value==="object"){Object.assign(containerDiv.style,value)}else{containerDiv.style.cssText=value}}else if(typeof value==="function"){containerDiv.addEventListener(key.replace(/^on/,"").toLowerCase(),value)}else{containerDiv[key]=value}});containerDiv.id=buttonId;const styleSheet=document.createElement("style");document.head.appendChild(styleSheet);styleSheet.sheet.insertRule(` + #${containerDiv.id} { position: fixed; - bottom: var(--${n.id}-bottom, 1rem); - right: var(--${n.id}-right, 1rem); - left: var(--${n.id}-left, unset); - top: var(--${n.id}-top, unset); - width: var(--${n.id}-width, 48px); - height: var(--${n.id}-height, 48px); - border-radius: var(--${n.id}-border-radius, 25px); - background-color: var(--${n.id}-bg-color, #155EEF); - box-shadow: var(--${n.id}-box-shadow, rgba(0, 0, 0, 0.2) 0px 4px 8px 0px); + bottom: var(--${containerDiv.id}-bottom, 1rem); + right: var(--${containerDiv.id}-right, 1rem); + left: var(--${containerDiv.id}-left, unset); + top: var(--${containerDiv.id}-top, unset); + width: var(--${containerDiv.id}-width, 48px); + height: var(--${containerDiv.id}-height, 48px); + border-radius: var(--${containerDiv.id}-border-radius, 25px); + background-color: var(--${containerDiv.id}-bg-color, #155EEF); + box-shadow: var(--${containerDiv.id}-box-shadow, rgba(0, 0, 0, 0.2) 0px 4px 8px 0px); cursor: pointer; z-index: 2147483647; } - `),document.createElement("div"));function t(){var e;u||((e=document.getElementById(m))?(e.style.display="none"===e.style.display?"block":"none","none"===e.style.display?p("open"):p("close"),"none"===e.style.display?document.removeEventListener("keydown",b):document.addEventListener("keydown",b),r()):(n.appendChild(s()),r(),this.title="Exit (ESC)",p("close"),document.addEventListener("keydown",b)))}if(e.style.cssText="position: relative; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;",e.innerHTML=` - - - - `,n.appendChild(e),document.body.appendChild(n),n.addEventListener("click",t),n.addEventListener("touchend",e=>{e.preventDefault(),t()},{passive:!1}),y.draggable){var a=n;var l=y.dragAxis||"both";let s,r,t,d;function o(e){u=!1,d=("touchstart"===e.type?(s=e.touches[0].clientX-a.offsetLeft,r=e.touches[0].clientY-a.offsetTop,t=e.touches[0].clientX,e.touches[0]):(s=e.clientX-a.offsetLeft,r=e.clientY-a.offsetTop,t=e.clientX,e)).clientY,document.addEventListener("mousemove",i),document.addEventListener("touchmove",i,{passive:!1}),document.addEventListener("mouseup",c),document.addEventListener("touchend",c),e.preventDefault()}function i(n){var o="touchmove"===n.type?n.touches[0]:n,i=o.clientX-t,o=o.clientY-d;if(u=8{u=!1},0),a.style.transition="",a.style.cursor="pointer",document.removeEventListener("mousemove",i),document.removeEventListener("touchmove",i),document.removeEventListener("mouseup",c),document.removeEventListener("touchend",c)}a.addEventListener("mousedown",o),a.addEventListener("touchstart",o)}}n.style.display="none",document.body.appendChild(n),2048{var t,n;e.origin===o&&(t=document.getElementById(m))&&e.source===t.contentWindow&&("dify-chatbot-iframe-ready"===e.data.type&&t.contentWindow?.postMessage({type:"dify-chatbot-config",payload:{isToggledByButton:!0,isDraggable:!!y.draggable}},o),"dify-chatbot-expand-change"===e.data.type)&&(a=!a,n=document.getElementById(m))&&(a?n.style.cssText="\n position: absolute;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n top: unset;\n right: var(--dify-chatbot-bubble-button-right, 1rem); /* Align with dify-chatbot-bubble-button. */\n bottom: var(--dify-chatbot-bubble-button-bottom, 1rem); /* Align with dify-chatbot-bubble-button. */\n left: unset;\n min-width: 24rem;\n width: 48%;\n max-width: 40rem; /* Match mobile breakpoint*/\n min-height: 43.75rem;\n height: 88%;\n max-height: calc(100vh - 6rem);\n border: none;\n z-index: 2147483640;\n overflow: hidden;\n user-select: none;\n transition-property: width, height;\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n transition-duration: 150ms;\n ":n.style.cssText=l,r())}),document.getElementById(h)||d()}else console.error(t+" is empty or token is not provided")}function p(e="open"){"open"===e?(document.getElementById("openIcon").style.display="block",document.getElementById("closeIcon").style.display="none"):(document.getElementById("openIcon").style.display="none",document.getElementById("closeIcon").style.display="block")}function b(e){"Escape"===e.key&&(e=document.getElementById(m))&&"none"!==e.style.display&&(e.style.display="none",p("open"))}h,h,document.addEventListener("keydown",b),y?.dynamicScript?e():document.body.onload=e})(); \ No newline at end of file + `);const displayDiv=document.createElement("div");displayDiv.style.cssText="position: relative; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;";displayDiv.innerHTML=svgIcons;containerDiv.appendChild(displayDiv);document.body.appendChild(containerDiv);containerDiv.addEventListener("click",handleClick);containerDiv.addEventListener("touchend",event=>{event.preventDefault();handleClick()},{passive:false});function handleClick(){if(isDragging)return;const targetIframe=document.getElementById(iframeId);if(!targetIframe){containerDiv.appendChild(createIframe());resetIframePosition();this.title="Exit (ESC)";setSvgIcon("close");document.addEventListener("keydown",handleEscKey);return}targetIframe.style.display=targetIframe.style.display==="none"?"block":"none";targetIframe.style.display==="none"?setSvgIcon("open"):setSvgIcon("close");if(targetIframe.style.display==="none"){document.removeEventListener("keydown",handleEscKey)}else{document.addEventListener("keydown",handleEscKey)}resetIframePosition()}if(config.draggable){enableDragging(containerDiv,config.dragAxis||"both")}}function enableDragging(element,axis){let startX,startY,startClientX,startClientY;element.addEventListener("mousedown",startDragging);element.addEventListener("touchstart",startDragging);function startDragging(e){isDragging=false;if(e.type==="touchstart"){startX=e.touches[0].clientX-element.offsetLeft;startY=e.touches[0].clientY-element.offsetTop;startClientX=e.touches[0].clientX;startClientY=e.touches[0].clientY}else{startX=e.clientX-element.offsetLeft;startY=e.clientY-element.offsetTop;startClientX=e.clientX;startClientY=e.clientY}document.addEventListener("mousemove",drag);document.addEventListener("touchmove",drag,{passive:false});document.addEventListener("mouseup",stopDragging);document.addEventListener("touchend",stopDragging);e.preventDefault()}function drag(e){const touch=e.type==="touchmove"?e.touches[0]:e;const deltaX=touch.clientX-startClientX;const deltaY=touch.clientY-startClientY;if(Math.abs(deltaX)>8||Math.abs(deltaY)>8){isDragging=true}if(!isDragging)return;element.style.transition="none";element.style.cursor="grabbing";const targetIframe=document.getElementById(iframeId);if(targetIframe){targetIframe.style.display="none";setSvgIcon("open")}let newLeft,newBottom;if(e.type==="touchmove"){newLeft=e.touches[0].clientX-startX;newBottom=window.innerHeight-e.touches[0].clientY-startY}else{newLeft=e.clientX-startX;newBottom=window.innerHeight-e.clientY-startY}const elementRect=element.getBoundingClientRect();const maxX=window.innerWidth-elementRect.width;const maxY=window.innerHeight-elementRect.height;if(axis==="x"||axis==="both"){element.style.setProperty(`--${buttonId}-left`,`${Math.max(0,Math.min(newLeft,maxX))}px`)}if(axis==="y"||axis==="both"){element.style.setProperty(`--${buttonId}-bottom`,`${Math.max(0,Math.min(newBottom,maxY))}px`)}}function stopDragging(){setTimeout(()=>{isDragging=false},0);element.style.transition="";element.style.cursor="pointer";document.removeEventListener("mousemove",drag);document.removeEventListener("touchmove",drag);document.removeEventListener("mouseup",stopDragging);document.removeEventListener("touchend",stopDragging)}}if(!document.getElementById(buttonId)){createButton()}}function setSvgIcon(type="open"){if(type==="open"){document.getElementById("openIcon").style.display="block";document.getElementById("closeIcon").style.display="none"}else{document.getElementById("openIcon").style.display="none";document.getElementById("closeIcon").style.display="block"}}function handleEscKey(event){if(event.key==="Escape"){const targetIframe=document.getElementById(iframeId);if(targetIframe&&targetIframe.style.display!=="none"){targetIframe.style.display="none";setSvgIcon("open")}}}document.addEventListener("keydown",handleEscKey);if(config?.dynamicScript){embedChatbot()}else{document.body.onload=embedChatbot}})(); \ No newline at end of file From 1b2046da3f6d63653ac778ce2239ca31f257ba06 Mon Sep 17 00:00:00 2001 From: lyzno1 <92089059+lyzno1@users.noreply.github.com> Date: Thu, 31 Jul 2025 10:03:33 +0800 Subject: [PATCH 11/32] fix: prevent client-side crashes from null/undefined plugin data in workflow (#23154) (#23182) --- web/__tests__/check-i18n.test.ts | 11 +- .../plugin-tool-workflow-error.test.tsx | 207 ++++++++++++++++++ .../components/switch-plugin-version.tsx | 8 +- .../nodes/agent/use-single-run-form-params.ts | 8 +- .../nodes/http/use-single-run-form-params.ts | 8 +- .../nodes/llm/use-single-run-form-params.ts | 8 +- .../use-single-run-form-params.ts | 8 +- .../use-single-run-form-params.ts | 8 +- .../nodes/tool/use-single-run-form-params.ts | 8 +- 9 files changed, 260 insertions(+), 14 deletions(-) create mode 100644 web/__tests__/plugin-tool-workflow-error.test.tsx diff --git a/web/__tests__/check-i18n.test.ts b/web/__tests__/check-i18n.test.ts index 173aa96118..3bde095f4b 100644 --- a/web/__tests__/check-i18n.test.ts +++ b/web/__tests__/check-i18n.test.ts @@ -49,9 +49,9 @@ describe('check-i18n script functionality', () => { } vm.runInNewContext(transpile(content), context) - const translationObj = moduleExports.default || moduleExports + const translationObj = (context.module.exports as any).default || context.module.exports - if(!translationObj || typeof translationObj !== 'object') + if (!translationObj || typeof translationObj !== 'object') throw new Error(`Error parsing file: ${filePath}`) const nestedKeys: string[] = [] @@ -62,7 +62,7 @@ describe('check-i18n script functionality', () => { // This is an object (but not array), recurse into it but don't add it as a key iterateKeys(obj[key], nestedKey) } - else { + else { // This is a leaf node (string, number, boolean, array, etc.), add it as a key nestedKeys.push(nestedKey) } @@ -73,7 +73,7 @@ describe('check-i18n script functionality', () => { const fileKeys = nestedKeys.map(key => `${camelCaseFileName}.${key}`) allKeys.push(...fileKeys) } - catch (error) { + catch (error) { reject(error) } }) @@ -272,9 +272,6 @@ export default translation const filteredEnKeys = allEnKeys.filter(key => key.startsWith(targetFile.replace(/[-_](.)/g, (_, c) => c.toUpperCase())), ) - const filteredZhKeys = allZhKeys.filter(key => - key.startsWith(targetFile.replace(/[-_](.)/g, (_, c) => c.toUpperCase())), - ) expect(allEnKeys).toHaveLength(4) // 2 keys from each file expect(filteredEnKeys).toHaveLength(2) // only components keys diff --git a/web/__tests__/plugin-tool-workflow-error.test.tsx b/web/__tests__/plugin-tool-workflow-error.test.tsx new file mode 100644 index 0000000000..370052bc80 --- /dev/null +++ b/web/__tests__/plugin-tool-workflow-error.test.tsx @@ -0,0 +1,207 @@ +/** + * Test cases to reproduce the plugin tool workflow error + * Issue: #23154 - Application error when loading plugin tools in workflow + * Root cause: split() operation called on null/undefined values + */ + +describe('Plugin Tool Workflow Error Reproduction', () => { + /** + * Mock function to simulate the problematic code in switch-plugin-version.tsx:29 + * const [pluginId] = uniqueIdentifier.split(':') + */ + const mockSwitchPluginVersionLogic = (uniqueIdentifier: string | null | undefined) => { + // This directly reproduces the problematic line from switch-plugin-version.tsx:29 + const [pluginId] = uniqueIdentifier!.split(':') + return pluginId + } + + /** + * Test case 1: Simulate null uniqueIdentifier + * This should reproduce the error mentioned in the issue + */ + it('should reproduce error when uniqueIdentifier is null', () => { + expect(() => { + mockSwitchPluginVersionLogic(null) + }).toThrow('Cannot read properties of null (reading \'split\')') + }) + + /** + * Test case 2: Simulate undefined uniqueIdentifier + */ + it('should reproduce error when uniqueIdentifier is undefined', () => { + expect(() => { + mockSwitchPluginVersionLogic(undefined) + }).toThrow('Cannot read properties of undefined (reading \'split\')') + }) + + /** + * Test case 3: Simulate empty string uniqueIdentifier + */ + it('should handle empty string uniqueIdentifier', () => { + expect(() => { + const result = mockSwitchPluginVersionLogic('') + expect(result).toBe('') // Empty string split by ':' returns [''] + }).not.toThrow() + }) + + /** + * Test case 4: Simulate malformed uniqueIdentifier without colon separator + */ + it('should handle malformed uniqueIdentifier without colon separator', () => { + expect(() => { + const result = mockSwitchPluginVersionLogic('malformed-identifier-without-colon') + expect(result).toBe('malformed-identifier-without-colon') // No colon means full string returned + }).not.toThrow() + }) + + /** + * Test case 5: Simulate valid uniqueIdentifier + */ + it('should work correctly with valid uniqueIdentifier', () => { + expect(() => { + const result = mockSwitchPluginVersionLogic('valid-plugin-id:1.0.0') + expect(result).toBe('valid-plugin-id') + }).not.toThrow() + }) +}) + +/** + * Test for the variable processing split error in use-single-run-form-params + */ +describe('Variable Processing Split Error', () => { + /** + * Mock function to simulate the problematic code in use-single-run-form-params.ts:91 + * const getDependentVars = () => { + * return varInputs.map(item => item.variable.slice(1, -1).split('.')) + * } + */ + const mockGetDependentVars = (varInputs: Array<{ variable: string | null | undefined }>) => { + return varInputs.map((item) => { + // Guard against null/undefined variable to prevent app crash + if (!item.variable || typeof item.variable !== 'string') + return [] + + return item.variable.slice(1, -1).split('.') + }).filter(arr => arr.length > 0) // Filter out empty arrays + } + + /** + * Test case 1: Variable processing with null variable + */ + it('should handle null variable safely', () => { + const varInputs = [{ variable: null }] + + expect(() => { + mockGetDependentVars(varInputs) + }).not.toThrow() + + const result = mockGetDependentVars(varInputs) + expect(result).toEqual([]) // null variables are filtered out + }) + + /** + * Test case 2: Variable processing with undefined variable + */ + it('should handle undefined variable safely', () => { + const varInputs = [{ variable: undefined }] + + expect(() => { + mockGetDependentVars(varInputs) + }).not.toThrow() + + const result = mockGetDependentVars(varInputs) + expect(result).toEqual([]) // undefined variables are filtered out + }) + + /** + * Test case 3: Variable processing with empty string + */ + it('should handle empty string variable', () => { + const varInputs = [{ variable: '' }] + + expect(() => { + mockGetDependentVars(varInputs) + }).not.toThrow() + + const result = mockGetDependentVars(varInputs) + expect(result).toEqual([]) // Empty string is filtered out, so result is empty array + }) + + /** + * Test case 4: Variable processing with valid variable format + */ + it('should work correctly with valid variable format', () => { + const varInputs = [{ variable: '{{workflow.node.output}}' }] + + expect(() => { + mockGetDependentVars(varInputs) + }).not.toThrow() + + const result = mockGetDependentVars(varInputs) + expect(result[0]).toEqual(['{workflow', 'node', 'output}']) + }) +}) + +/** + * Integration test to simulate the complete workflow scenario + */ +describe('Plugin Tool Workflow Integration', () => { + /** + * Simulate the scenario where plugin metadata is incomplete or corrupted + * This can happen when: + * 1. Plugin is being loaded from marketplace but metadata request fails + * 2. Plugin configuration is corrupted in database + * 3. Network issues during plugin loading + */ + it('should reproduce the client-side exception scenario', () => { + // Mock incomplete plugin data that could cause the error + const incompletePluginData = { + // Missing or null uniqueIdentifier + uniqueIdentifier: null, + meta: null, + minimum_dify_version: undefined, + } + + // This simulates the error path that leads to the white screen + expect(() => { + // Simulate the code path in switch-plugin-version.tsx:29 + // The actual problematic code doesn't use optional chaining + const _pluginId = (incompletePluginData.uniqueIdentifier as any).split(':')[0] + }).toThrow('Cannot read properties of null (reading \'split\')') + }) + + /** + * Test the scenario mentioned in the issue where plugin tools are loaded in workflow + */ + it('should simulate plugin tool loading in workflow context', () => { + // Mock the workflow context where plugin tools are being loaded + const workflowPluginTools = [ + { + provider_name: 'test-plugin', + uniqueIdentifier: null, // This is the problematic case + tool_name: 'test-tool', + }, + { + provider_name: 'valid-plugin', + uniqueIdentifier: 'valid-plugin:1.0.0', + tool_name: 'valid-tool', + }, + ] + + // Process each plugin tool + workflowPluginTools.forEach((tool, _index) => { + if (tool.uniqueIdentifier === null) { + // This reproduces the exact error scenario + expect(() => { + const _pluginId = (tool.uniqueIdentifier as any).split(':')[0] + }).toThrow() + } + else { + // Valid tools should work fine + expect(() => { + const _pluginId = tool.uniqueIdentifier.split(':')[0] + }).not.toThrow() + } + }) + }) +}) diff --git a/web/app/components/workflow/nodes/_base/components/switch-plugin-version.tsx b/web/app/components/workflow/nodes/_base/components/switch-plugin-version.tsx index 94b3ce7bfc..7ecbbd5602 100644 --- a/web/app/components/workflow/nodes/_base/components/switch-plugin-version.tsx +++ b/web/app/components/workflow/nodes/_base/components/switch-plugin-version.tsx @@ -26,7 +26,8 @@ export type SwitchPluginVersionProps = { export const SwitchPluginVersion: FC = (props) => { const { uniqueIdentifier, tooltip, onChange, className } = props - const [pluginId] = uniqueIdentifier.split(':') + + const [pluginId] = uniqueIdentifier?.split(':') || [''] const [isShow, setIsShow] = useState(false) const [isShowUpdateModal, { setTrue: showUpdateModal, setFalse: hideUpdateModal }] = useBoolean(false) const [target, setTarget] = useState<{ @@ -60,6 +61,11 @@ export const SwitchPluginVersion: FC = (props) => { }) } const { t } = useTranslation() + + // Guard against null/undefined uniqueIdentifier to prevent app crash + if (!uniqueIdentifier || !pluginId) + return null + return
e.stopPropagation()}> {isShowUpdateModal && pluginDetail && { - return varInputs.map(item => item.variable.slice(1, -1).split('.')) + return varInputs.map((item) => { + // Guard against null/undefined variable to prevent app crash + if (!item.variable || typeof item.variable !== 'string') + return [] + + return item.variable.slice(1, -1).split('.') + }).filter(arr => arr.length > 0) } return { diff --git a/web/app/components/workflow/nodes/http/use-single-run-form-params.ts b/web/app/components/workflow/nodes/http/use-single-run-form-params.ts index c5d65634c4..42f39c4d32 100644 --- a/web/app/components/workflow/nodes/http/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/http/use-single-run-form-params.ts @@ -62,7 +62,13 @@ const useSingleRunFormParams = ({ }, [inputVarValues, setInputVarValues, varInputs]) const getDependentVars = () => { - return varInputs.map(item => item.variable.slice(1, -1).split('.')) + return varInputs.map((item) => { + // Guard against null/undefined variable to prevent app crash + if (!item.variable || typeof item.variable !== 'string') + return [] + + return item.variable.slice(1, -1).split('.') + }).filter(arr => arr.length > 0) } return { diff --git a/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts b/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts index 93a8638d05..2480bbee31 100644 --- a/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts @@ -168,7 +168,13 @@ const useSingleRunFormParams = ({ })() const getDependentVars = () => { - const promptVars = varInputs.map(item => item.variable.slice(1, -1).split('.')) + const promptVars = varInputs.map((item) => { + // Guard against null/undefined variable to prevent app crash + if (!item.variable || typeof item.variable !== 'string') + return [] + + return item.variable.slice(1, -1).split('.') + }).filter(arr => arr.length > 0) const contextVar = payload.context.variable_selector const vars = [...promptVars, contextVar] if (isVisionModel && payload.vision?.enabled && payload.vision?.configs?.variable_selector) { diff --git a/web/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params.ts b/web/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params.ts index 178f9e3ed8..f920ff1555 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params.ts @@ -120,7 +120,13 @@ const useSingleRunFormParams = ({ })() const getDependentVars = () => { - const promptVars = varInputs.map(item => item.variable.slice(1, -1).split('.')) + const promptVars = varInputs.map((item) => { + // Guard against null/undefined variable to prevent app crash + if (!item.variable || typeof item.variable !== 'string') + return [] + + return item.variable.slice(1, -1).split('.') + }).filter(arr => arr.length > 0) const vars = [payload.query, ...promptVars] if (isVisionModel && payload.vision?.enabled && payload.vision?.configs?.variable_selector) { const visionVar = payload.vision.configs.variable_selector diff --git a/web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts b/web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts index 66755abb6e..9bbb3e1d5d 100644 --- a/web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts @@ -118,7 +118,13 @@ const useSingleRunFormParams = ({ })() const getDependentVars = () => { - const promptVars = varInputs.map(item => item.variable.slice(1, -1).split('.')) + const promptVars = varInputs.map((item) => { + // Guard against null/undefined variable to prevent app crash + if (!item.variable || typeof item.variable !== 'string') + return [] + + return item.variable.slice(1, -1).split('.') + }).filter(arr => arr.length > 0) const vars = [payload.query_variable_selector, ...promptVars] if (isVisionModel && payload.vision?.enabled && payload.vision?.configs?.variable_selector) { const visionVar = payload.vision.configs.variable_selector diff --git a/web/app/components/workflow/nodes/tool/use-single-run-form-params.ts b/web/app/components/workflow/nodes/tool/use-single-run-form-params.ts index 6fc79beebe..13b1da6b01 100644 --- a/web/app/components/workflow/nodes/tool/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/tool/use-single-run-form-params.ts @@ -88,7 +88,13 @@ const useSingleRunFormParams = ({ const toolIcon = useToolIcon(payload) const getDependentVars = () => { - return varInputs.map(item => item.variable.slice(1, -1).split('.')) + return varInputs.map((item) => { + // Guard against null/undefined variable to prevent app crash + if (!item.variable || typeof item.variable !== 'string') + return [] + + return item.variable.slice(1, -1).split('.') + }).filter(arr => arr.length > 0) } return { From 4251515b4e0b02a9e8228b53830465fc58cdc7ae Mon Sep 17 00:00:00 2001 From: kenwoodjw Date: Thu, 31 Jul 2025 10:30:54 +0800 Subject: [PATCH 12/32] fix remote file (#23127) Signed-off-by: kenwoodjw --- api/factories/file_factory.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/api/factories/file_factory.py b/api/factories/file_factory.py index 512a9cb608..b2bcee5dcd 100644 --- a/api/factories/file_factory.py +++ b/api/factories/file_factory.py @@ -1,4 +1,6 @@ import mimetypes +import os +import urllib.parse import uuid from collections.abc import Callable, Mapping, Sequence from typing import Any, cast @@ -240,16 +242,21 @@ def _build_from_remote_url( def _get_remote_file_info(url: str): file_size = -1 - filename = url.split("/")[-1].split("?")[0] or "unknown_file" - mime_type = mimetypes.guess_type(filename)[0] or "" + parsed_url = urllib.parse.urlparse(url) + url_path = parsed_url.path + filename = os.path.basename(url_path) + + # Initialize mime_type from filename as fallback + mime_type, _ = mimetypes.guess_type(filename) resp = ssrf_proxy.head(url, follow_redirects=True) resp = cast(httpx.Response, resp) if resp.status_code == httpx.codes.OK: if content_disposition := resp.headers.get("Content-Disposition"): filename = str(content_disposition.split("filename=")[-1].strip('"')) + # Re-guess mime_type from updated filename + mime_type, _ = mimetypes.guess_type(filename) file_size = int(resp.headers.get("Content-Length", file_size)) - mime_type = mime_type or str(resp.headers.get("Content-Type", "")) return mime_type, filename, file_size From afac1fe590e3cbff9b8fd6c743ba8f34ec88b21a Mon Sep 17 00:00:00 2001 From: Jason Young <44939412+farion1231@users.noreply.github.com> Date: Thu, 31 Jul 2025 10:32:16 +0800 Subject: [PATCH 13/32] Add comprehensive security tests for file upload controller (#23102) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../console/test_files_security.py | 278 ++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 api/tests/unit_tests/controllers/console/test_files_security.py diff --git a/api/tests/unit_tests/controllers/console/test_files_security.py b/api/tests/unit_tests/controllers/console/test_files_security.py new file mode 100644 index 0000000000..cb5562d345 --- /dev/null +++ b/api/tests/unit_tests/controllers/console/test_files_security.py @@ -0,0 +1,278 @@ +import io +from unittest.mock import patch + +import pytest +from werkzeug.exceptions import Forbidden + +from controllers.common.errors import FilenameNotExistsError +from controllers.console.error import ( + FileTooLargeError, + NoFileUploadedError, + TooManyFilesError, + UnsupportedFileTypeError, +) +from services.errors.file import FileTooLargeError as ServiceFileTooLargeError +from services.errors.file import UnsupportedFileTypeError as ServiceUnsupportedFileTypeError + + +class TestFileUploadSecurity: + """Test file upload security logic without complex framework setup""" + + # Test 1: Basic file validation + def test_should_validate_file_presence(self): + """Test that missing file is detected""" + from flask import Flask, request + + app = Flask(__name__) + + with app.test_request_context(method="POST", data={}): + # Simulate the check in FileApi.post() + if "file" not in request.files: + with pytest.raises(NoFileUploadedError): + raise NoFileUploadedError() + + def test_should_validate_multiple_files(self): + """Test that multiple files are rejected""" + from flask import Flask, request + + app = Flask(__name__) + + file_data = { + "file": (io.BytesIO(b"content1"), "file1.txt", "text/plain"), + "file2": (io.BytesIO(b"content2"), "file2.txt", "text/plain"), + } + + with app.test_request_context(method="POST", data=file_data, content_type="multipart/form-data"): + # Simulate the check in FileApi.post() + if len(request.files) > 1: + with pytest.raises(TooManyFilesError): + raise TooManyFilesError() + + def test_should_validate_empty_filename(self): + """Test that empty filename is rejected""" + from flask import Flask, request + + app = Flask(__name__) + + file_data = {"file": (io.BytesIO(b"content"), "", "text/plain")} + + with app.test_request_context(method="POST", data=file_data, content_type="multipart/form-data"): + file = request.files["file"] + if not file.filename: + with pytest.raises(FilenameNotExistsError): + raise FilenameNotExistsError + + # Test 2: Security - Filename sanitization + def test_should_detect_path_traversal_in_filename(self): + """Test protection against directory traversal attacks""" + dangerous_filenames = [ + "../../../etc/passwd", + "..\\..\\windows\\system32\\config\\sam", + "../../../../etc/shadow", + "./../../../sensitive.txt", + ] + + for filename in dangerous_filenames: + # Any filename containing .. should be considered dangerous + assert ".." in filename, f"Filename {filename} should be detected as path traversal" + + def test_should_detect_null_byte_injection(self): + """Test protection against null byte injection""" + dangerous_filenames = [ + "file.jpg\x00.php", + "document.pdf\x00.exe", + "image.png\x00.sh", + ] + + for filename in dangerous_filenames: + # Null bytes should be detected + assert "\x00" in filename, f"Filename {filename} should be detected as null byte injection" + + def test_should_sanitize_special_characters(self): + """Test that special characters in filenames are handled safely""" + # Characters that could be problematic in various contexts + dangerous_chars = ["/", "\\", ":", "*", "?", '"', "<", ">", "|", "\x00"] + + for char in dangerous_chars: + filename = f"file{char}name.txt" + # These characters should be detected or sanitized + assert any(c in filename for c in dangerous_chars) + + # Test 3: Permission validation + def test_should_validate_dataset_permissions(self): + """Test dataset upload permission logic""" + + class MockUser: + is_dataset_editor = False + + user = MockUser() + source = "datasets" + + # Simulate the permission check in FileApi.post() + if source == "datasets" and not user.is_dataset_editor: + with pytest.raises(Forbidden): + raise Forbidden() + + def test_should_allow_general_upload_without_permission(self): + """Test general upload doesn't require dataset permission""" + + class MockUser: + is_dataset_editor = False + + user = MockUser() + source = None # General upload + + # This should not raise an exception + if source == "datasets" and not user.is_dataset_editor: + raise Forbidden() + # Test passes if no exception is raised + + # Test 4: Service error handling + @patch("services.file_service.FileService.upload_file") + def test_should_handle_file_too_large_error(self, mock_upload): + """Test that service FileTooLargeError is properly converted""" + mock_upload.side_effect = ServiceFileTooLargeError("File too large") + + try: + mock_upload(filename="test.txt", content=b"data", mimetype="text/plain", user=None, source=None) + except ServiceFileTooLargeError as e: + # Simulate the error conversion in FileApi.post() + with pytest.raises(FileTooLargeError): + raise FileTooLargeError(e.description) + + @patch("services.file_service.FileService.upload_file") + def test_should_handle_unsupported_file_type_error(self, mock_upload): + """Test that service UnsupportedFileTypeError is properly converted""" + mock_upload.side_effect = ServiceUnsupportedFileTypeError() + + try: + mock_upload( + filename="test.exe", content=b"data", mimetype="application/octet-stream", user=None, source=None + ) + except ServiceUnsupportedFileTypeError: + # Simulate the error conversion in FileApi.post() + with pytest.raises(UnsupportedFileTypeError): + raise UnsupportedFileTypeError() + + # Test 5: File type security + def test_should_identify_dangerous_file_extensions(self): + """Test detection of potentially dangerous file extensions""" + dangerous_extensions = [ + ".php", + ".PHP", + ".pHp", # PHP files (case variations) + ".exe", + ".EXE", # Executables + ".sh", + ".SH", # Shell scripts + ".bat", + ".BAT", # Batch files + ".cmd", + ".CMD", # Command files + ".ps1", + ".PS1", # PowerShell + ".jar", + ".JAR", # Java archives + ".vbs", + ".VBS", # VBScript + ] + + safe_extensions = [".txt", ".pdf", ".jpg", ".png", ".doc", ".docx"] + + # Just verify our test data is correct + for ext in dangerous_extensions: + assert ext.lower() in [".php", ".exe", ".sh", ".bat", ".cmd", ".ps1", ".jar", ".vbs"] + + for ext in safe_extensions: + assert ext.lower() not in [".php", ".exe", ".sh", ".bat", ".cmd", ".ps1", ".jar", ".vbs"] + + def test_should_detect_double_extensions(self): + """Test detection of double extension attacks""" + suspicious_filenames = [ + "image.jpg.php", + "document.pdf.exe", + "photo.png.sh", + "file.txt.bat", + ] + + for filename in suspicious_filenames: + # Check that these have multiple extensions + parts = filename.split(".") + assert len(parts) > 2, f"Filename {filename} should have multiple extensions" + + # Test 6: Configuration validation + def test_upload_configuration_structure(self): + """Test that upload configuration has correct structure""" + # Simulate the configuration returned by FileApi.get() + config = { + "file_size_limit": 15, + "batch_count_limit": 5, + "image_file_size_limit": 10, + "video_file_size_limit": 500, + "audio_file_size_limit": 50, + "workflow_file_upload_limit": 10, + } + + # Verify all required fields are present + required_fields = [ + "file_size_limit", + "batch_count_limit", + "image_file_size_limit", + "video_file_size_limit", + "audio_file_size_limit", + "workflow_file_upload_limit", + ] + + for field in required_fields: + assert field in config, f"Missing required field: {field}" + assert isinstance(config[field], int), f"Field {field} should be an integer" + assert config[field] > 0, f"Field {field} should be positive" + + # Test 7: Source parameter handling + def test_source_parameter_normalization(self): + """Test that source parameter is properly normalized""" + test_cases = [ + ("datasets", "datasets"), + ("other", None), + ("", None), + (None, None), + ] + + for input_source, expected in test_cases: + # Simulate the source normalization in FileApi.post() + source = "datasets" if input_source == "datasets" else None + if source not in ("datasets", None): + source = None + assert source == expected + + # Test 8: Boundary conditions + def test_should_handle_edge_case_file_sizes(self): + """Test handling of boundary file sizes""" + test_cases = [ + (0, "Empty file"), # 0 bytes + (1, "Single byte"), # 1 byte + (15 * 1024 * 1024 - 1, "Just under limit"), # Just under 15MB + (15 * 1024 * 1024, "At limit"), # Exactly 15MB + (15 * 1024 * 1024 + 1, "Just over limit"), # Just over 15MB + ] + + for size, description in test_cases: + # Just verify our test data + assert isinstance(size, int), f"{description}: Size should be integer" + assert size >= 0, f"{description}: Size should be non-negative" + + def test_should_handle_special_mime_types(self): + """Test handling of various MIME types""" + mime_type_tests = [ + ("application/octet-stream", "Generic binary"), + ("text/plain", "Plain text"), + ("image/jpeg", "JPEG image"), + ("application/pdf", "PDF document"), + ("", "Empty MIME type"), + (None, "None MIME type"), + ] + + for mime_type, description in mime_type_tests: + # Verify test data structure + if mime_type is not None: + assert isinstance(mime_type, str), f"{description}: MIME type should be string or None" From 5febd668089a41cbb01085ca6e53c9b148d2aef2 Mon Sep 17 00:00:00 2001 From: GuanMu Date: Thu, 31 Jul 2025 11:47:34 +0800 Subject: [PATCH 14/32] Fix: Fix style issues (#23209) --- web/app/components/app/annotation/index.tsx | 2 +- web/app/components/app/log/list.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/app/components/app/annotation/index.tsx b/web/app/components/app/annotation/index.tsx index 0b0691eb7d..bb2a95b0b5 100644 --- a/web/app/components/app/annotation/index.tsx +++ b/web/app/components/app/annotation/index.tsx @@ -146,7 +146,7 @@ const Annotation: FC = (props) => { return (

{t('appLog.description')}

-
+
{isChatApp && ( diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index b04148d484..b83e9e6a2a 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -688,7 +688,7 @@ const ConversationList: FC = ({ logs, appDetail, onRefresh }) return return ( -
+
From f5e177db89307a1f311803ebe3cd845d408f0cbb Mon Sep 17 00:00:00 2001 From: NFish Date: Thu, 31 Jul 2025 14:18:54 +0800 Subject: [PATCH 15/32] fix: call checkOrSetAccessToken when app access mode is PUBLIC (#23195) Co-authored-by: crazywoola <427733928@qq.com> --- .../app/overview/embedded/index.tsx | 6 +++++ web/context/web-app-context.tsx | 23 ++++++++----------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/web/app/components/app/overview/embedded/index.tsx b/web/app/components/app/overview/embedded/index.tsx index 9d97eae38d..cd25c4ca65 100644 --- a/web/app/components/app/overview/embedded/index.tsx +++ b/web/app/components/app/overview/embedded/index.tsx @@ -46,6 +46,12 @@ const OPTION_MAP = { ? `, baseUrl: '${url}${basePath}'` : ''}, + inputs: { + // You can define the inputs from the Start node here + // key is the variable name + // e.g. + // name: "NAME" + }, systemVariables: { // user_id: 'YOU CAN DEFINE USER ID HERE', // conversation_id: 'YOU CAN DEFINE CONVERSATION ID HERE, IT MUST BE A VALID UUID', diff --git a/web/context/web-app-context.tsx b/web/context/web-app-context.tsx index db1c5158dd..e78ef81bbc 100644 --- a/web/context/web-app-context.tsx +++ b/web/context/web-app-context.tsx @@ -61,22 +61,18 @@ const WebAppStoreProvider: FC = ({ children }) => { const pathname = usePathname() const searchParams = useSearchParams() const redirectUrlParam = searchParams.get('redirect_url') - const session = searchParams.get('session') - const sysUserId = searchParams.get('sys.user_id') - const [shareCode, setShareCode] = useState(null) - useEffect(() => { - const shareCodeFromRedirect = getShareCodeFromRedirectUrl(redirectUrlParam) - const shareCodeFromPathname = getShareCodeFromPathname(pathname) - const newShareCode = shareCodeFromRedirect || shareCodeFromPathname - setShareCode(newShareCode) - updateShareCode(newShareCode) - }, [pathname, redirectUrlParam, updateShareCode]) + + // Compute shareCode directly + const shareCode = getShareCodeFromRedirectUrl(redirectUrlParam) || getShareCodeFromPathname(pathname) + updateShareCode(shareCode) + const { isFetching, data: accessModeResult } = useGetWebAppAccessModeByCode(shareCode) - const [isFetchingAccessToken, setIsFetchingAccessToken] = useState(true) + const [isFetchingAccessToken, setIsFetchingAccessToken] = useState(false) + useEffect(() => { if (accessModeResult?.accessMode) { updateWebAppAccessMode(accessModeResult.accessMode) - if (accessModeResult?.accessMode === AccessMode.PUBLIC && session && sysUserId) { + if (accessModeResult.accessMode === AccessMode.PUBLIC) { setIsFetchingAccessToken(true) checkOrSetAccessToken(shareCode).finally(() => { setIsFetchingAccessToken(false) @@ -86,7 +82,8 @@ const WebAppStoreProvider: FC = ({ children }) => { setIsFetchingAccessToken(false) } } - }, [accessModeResult, updateWebAppAccessMode, setIsFetchingAccessToken, shareCode, session, sysUserId]) + }, [accessModeResult, updateWebAppAccessMode, shareCode]) + if (isFetching || isFetchingAccessToken) { return
From a434f6240ffca455da88d2e2c4884464adae0e4b Mon Sep 17 00:00:00 2001 From: Yongtao Huang Date: Thu, 31 Jul 2025 15:33:39 +0800 Subject: [PATCH 16/32] Chroe: some misc cleanup (#23203) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- api/core/ops/ops_trace_manager.py | 2 +- .../time/tools/timezone_conversion.py | 2 +- .../workflow/nodes/document_extractor/node.py | 2 +- api/core/workflow/nodes/llm/node.py | 19 +------------------ 4 files changed, 4 insertions(+), 21 deletions(-) diff --git a/api/core/ops/ops_trace_manager.py b/api/core/ops/ops_trace_manager.py index b769934a9b..7eb5da7e3a 100644 --- a/api/core/ops/ops_trace_manager.py +++ b/api/core/ops/ops_trace_manager.py @@ -322,7 +322,7 @@ class OpsTraceManager: :return: """ # auth check - if enabled == True: + if enabled: try: provider_config_map[tracing_provider] except KeyError: diff --git a/api/core/tools/builtin_tool/providers/time/tools/timezone_conversion.py b/api/core/tools/builtin_tool/providers/time/tools/timezone_conversion.py index f9b776b3b9..91316b859a 100644 --- a/api/core/tools/builtin_tool/providers/time/tools/timezone_conversion.py +++ b/api/core/tools/builtin_tool/providers/time/tools/timezone_conversion.py @@ -27,7 +27,7 @@ class TimezoneConversionTool(BuiltinTool): target_time = self.timezone_convert(current_time, current_timezone, target_timezone) # type: ignore if not target_time: yield self.create_text_message( - f"Invalid datatime and timezone: {current_time},{current_timezone},{target_timezone}" + f"Invalid datetime and timezone: {current_time},{current_timezone},{target_timezone}" ) return diff --git a/api/core/workflow/nodes/document_extractor/node.py b/api/core/workflow/nodes/document_extractor/node.py index f3061f7d96..23512c8ce4 100644 --- a/api/core/workflow/nodes/document_extractor/node.py +++ b/api/core/workflow/nodes/document_extractor/node.py @@ -597,7 +597,7 @@ def _extract_text_from_vtt(vtt_bytes: bytes) -> str: for i in range(1, len(raw_results)): spk, txt = raw_results[i] - if spk == None: + if spk is None: merged_results.append((None, current_text)) continue diff --git a/api/core/workflow/nodes/llm/node.py b/api/core/workflow/nodes/llm/node.py index 90a0397b67..dfc2a0000b 100644 --- a/api/core/workflow/nodes/llm/node.py +++ b/api/core/workflow/nodes/llm/node.py @@ -3,7 +3,7 @@ import io import json import logging from collections.abc import Generator, Mapping, Sequence -from typing import TYPE_CHECKING, Any, Optional, cast +from typing import TYPE_CHECKING, Any, Optional from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEntity from core.file import FileType, file_manager @@ -33,12 +33,10 @@ from core.model_runtime.entities.message_entities import ( UserPromptMessage, ) from core.model_runtime.entities.model_entities import ( - AIModelEntity, ModelFeature, ModelPropertyKey, ModelType, ) -from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel from core.model_runtime.utils.encoders import jsonable_encoder from core.prompt.entities.advanced_prompt_entities import CompletionModelPromptTemplate, MemoryConfig from core.prompt.utils.prompt_message_util import PromptMessageUtil @@ -1006,21 +1004,6 @@ class LLMNode(BaseNode): ) return saved_file - def _fetch_model_schema(self, provider: str) -> AIModelEntity | None: - """ - Fetch model schema - """ - model_name = self._node_data.model.name - model_manager = ModelManager() - model_instance = model_manager.get_model_instance( - tenant_id=self.tenant_id, model_type=ModelType.LLM, provider=provider, model=model_name - ) - model_type_instance = model_instance.model_type_instance - model_type_instance = cast(LargeLanguageModel, model_type_instance) - model_credentials = model_instance.credentials - model_schema = model_type_instance.get_model_schema(model_name, model_credentials) - return model_schema - @staticmethod def fetch_structured_output_schema( *, From a82b55005b8f3d4532d17e1181fa894a9987288a Mon Sep 17 00:00:00 2001 From: lyzno1 <92089059+lyzno1@users.noreply.github.com> Date: Thu, 31 Jul 2025 16:04:49 +0800 Subject: [PATCH 17/32] fix: resolve sidebar animation glitches and layout shifts in app detail page (#23216) (#23221) --- web/app/components/app-sidebar/app-info.tsx | 21 +- web/app/components/app-sidebar/index.tsx | 5 +- .../components/app-sidebar/navLink.spec.tsx | 189 +++++++++++ web/app/components/app-sidebar/navLink.tsx | 17 +- .../sidebar-animation-issues.spec.tsx | 297 ++++++++++++++++++ .../text-squeeze-fix-verification.spec.tsx | 235 ++++++++++++++ 6 files changed, 746 insertions(+), 18 deletions(-) create mode 100644 web/app/components/app-sidebar/navLink.spec.tsx create mode 100644 web/app/components/app-sidebar/sidebar-animation-issues.spec.tsx create mode 100644 web/app/components/app-sidebar/text-squeeze-fix-verification.spec.tsx diff --git a/web/app/components/app-sidebar/app-info.tsx b/web/app/components/app-sidebar/app-info.tsx index 58c9f7e5ca..c04d79d2f2 100644 --- a/web/app/components/app-sidebar/app-info.tsx +++ b/web/app/components/app-sidebar/app-info.tsx @@ -271,16 +271,17 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx
- { - expand && ( -
-
-
{appDetail.name}
-
-
{appDetail.mode === 'advanced-chat' ? t('app.types.advanced') : appDetail.mode === 'agent-chat' ? t('app.types.agent') : appDetail.mode === 'chat' ? t('app.types.chatbot') : appDetail.mode === 'completion' ? t('app.types.completion') : t('app.types.workflow')}
-
- ) - } +
+
+
{appDetail.name}
+
+
{appDetail.mode === 'advanced-chat' ? t('app.types.advanced') : appDetail.mode === 'agent-chat' ? t('app.types.agent') : appDetail.mode === 'chat' ? t('app.types.chatbot') : appDetail.mode === 'completion' ? t('app.types.completion') : t('app.types.workflow')}
+
)} diff --git a/web/app/components/app-sidebar/index.tsx b/web/app/components/app-sidebar/index.tsx index b6bfc0e9ac..cf32339b8a 100644 --- a/web/app/components/app-sidebar/index.tsx +++ b/web/app/components/app-sidebar/index.tsx @@ -124,10 +124,7 @@ const AppDetailNav = ({ title, desc, isExternal, icon, icon_background, navigati { !isMobile && (
({ + useSelectedLayoutSegment: () => 'overview', +})) + +// Mock Next.js Link component +jest.mock('next/link', () => { + return function MockLink({ children, href, className, title }: any) { + return ( + + {children} + + ) + } +}) + +// Mock RemixIcon components +const MockIcon = ({ className }: { className?: string }) => ( + +) + +describe('NavLink Text Animation Issues', () => { + const mockProps: NavLinkProps = { + name: 'Orchestrate', + href: '/app/123/workflow', + iconMap: { + selected: MockIcon, + normal: MockIcon, + }, + } + + beforeEach(() => { + // Mock getComputedStyle for transition testing + Object.defineProperty(window, 'getComputedStyle', { + value: jest.fn((element) => { + const isExpanded = element.getAttribute('data-mode') === 'expand' + return { + transition: 'all 0.3s ease', + opacity: isExpanded ? '1' : '0', + width: isExpanded ? 'auto' : '0px', + overflow: 'hidden', + paddingLeft: isExpanded ? '12px' : '10px', // px-3 vs px-2.5 + paddingRight: isExpanded ? '12px' : '10px', + } + }), + writable: true, + }) + }) + + describe('Text Squeeze Animation Issue', () => { + it('should show text squeeze effect when switching from collapse to expand', async () => { + const { rerender } = render() + + // In collapse mode, text should be in DOM but hidden via CSS + const textElement = screen.getByText('Orchestrate') + expect(textElement).toBeInTheDocument() + expect(textElement).toHaveClass('opacity-0') + expect(textElement).toHaveClass('w-0') + expect(textElement).toHaveClass('overflow-hidden') + + // Icon should still be present + expect(screen.getByTestId('nav-icon')).toBeInTheDocument() + + // Check padding in collapse mode + const linkElement = screen.getByTestId('nav-link') + expect(linkElement).toHaveClass('px-2.5') + + // Switch to expand mode - this is where the squeeze effect occurs + rerender() + + // Text should now appear + expect(screen.getByText('Orchestrate')).toBeInTheDocument() + + // Check padding change - this contributes to the squeeze effect + expect(linkElement).toHaveClass('px-3') + + // The bug: text appears abruptly without smooth transition + // This test documents the current behavior that causes the squeeze effect + const expandedTextElement = screen.getByText('Orchestrate') + expect(expandedTextElement).toBeInTheDocument() + + // In a properly animated version, we would expect: + // - Opacity transition from 0 to 1 + // - Width transition from 0 to auto + // - No layout shift from padding changes + }) + + it('should maintain icon position consistency during text appearance', () => { + const { rerender } = render() + + const iconElement = screen.getByTestId('nav-icon') + const initialIconClasses = iconElement.className + + // Icon should have mr-0 in collapse mode + expect(iconElement).toHaveClass('mr-0') + + rerender() + + const expandedIconClasses = iconElement.className + + // Icon should have mr-2 in expand mode - this shift contributes to the squeeze effect + expect(iconElement).toHaveClass('mr-2') + + console.log('Collapsed icon classes:', initialIconClasses) + console.log('Expanded icon classes:', expandedIconClasses) + + // This margin change causes the icon to shift when text appears + }) + + it('should document the abrupt text rendering issue', () => { + const { rerender } = render() + + // Text is present in DOM but hidden via CSS classes + const collapsedText = screen.getByText('Orchestrate') + expect(collapsedText).toBeInTheDocument() + expect(collapsedText).toHaveClass('opacity-0') + expect(collapsedText).toHaveClass('pointer-events-none') + + rerender() + + // Text suddenly appears in DOM - no transition + expect(screen.getByText('Orchestrate')).toBeInTheDocument() + + // The issue: {mode === 'expand' && name} causes abrupt show/hide + // instead of smooth opacity/width transition + }) + }) + + describe('Layout Shift Issues', () => { + it('should detect padding differences causing layout shifts', () => { + const { rerender } = render() + + const linkElement = screen.getByTestId('nav-link') + + // Collapsed state padding + expect(linkElement).toHaveClass('px-2.5') + + rerender() + + // Expanded state padding - different value causes layout shift + expect(linkElement).toHaveClass('px-3') + + // This 2px difference (10px vs 12px) contributes to the squeeze effect + }) + + it('should detect icon margin changes causing shifts', () => { + const { rerender } = render() + + const iconElement = screen.getByTestId('nav-icon') + + // Collapsed: no right margin + expect(iconElement).toHaveClass('mr-0') + + rerender() + + // Expanded: 8px right margin (mr-2) + expect(iconElement).toHaveClass('mr-2') + + // This sudden margin appearance causes the squeeze effect + }) + }) + + describe('Active State Handling', () => { + it('should handle active state correctly in both modes', () => { + // Test non-active state + const { rerender } = render() + + let linkElement = screen.getByTestId('nav-link') + expect(linkElement).not.toHaveClass('bg-state-accent-active') + + // Test with active state (when href matches current segment) + const activeProps = { + ...mockProps, + href: '/app/123/overview', // matches mocked segment + } + + rerender() + + linkElement = screen.getByTestId('nav-link') + expect(linkElement).toHaveClass('bg-state-accent-active') + }) + }) +}) diff --git a/web/app/components/app-sidebar/navLink.tsx b/web/app/components/app-sidebar/navLink.tsx index 295b553b04..4607f7b693 100644 --- a/web/app/components/app-sidebar/navLink.tsx +++ b/web/app/components/app-sidebar/navLink.tsx @@ -44,20 +44,29 @@ export default function NavLink({ key={name} href={href} className={classNames( - isActive ? 'bg-state-accent-active text-text-accent font-semibold' : 'text-components-menu-item-text hover:bg-state-base-hover hover:text-components-menu-item-text-hover', - 'group flex items-center h-9 rounded-md py-2 text-sm font-normal', + isActive ? 'bg-state-accent-active font-semibold text-text-accent' : 'text-components-menu-item-text hover:bg-state-base-hover hover:text-components-menu-item-text-hover', + 'group flex h-9 items-center rounded-md py-2 text-sm font-normal', mode === 'expand' ? 'px-3' : 'px-2.5', )} title={mode === 'collapse' ? name : ''} >
+ Update the value of a specific conversation variable. This endpoint allows you to modify the value of a variable that was captured during the conversation while preserving its name, type, and description. + + ### Path Parameters + + + + The ID of the conversation containing the variable to update. + + + The ID of the variable to update. + + + + ### Request Body + + + + The new value for the variable. Must match the variable's expected type (string, number, object, etc.). + + + The user identifier, defined by the developer, must ensure uniqueness within the application. + + + + ### Response + + Returns the updated variable object with: + - `id` (string) Variable ID + - `name` (string) Variable name + - `value_type` (string) Variable type (string, number, object, etc.) + - `value` (any) Updated variable value + - `description` (string) Variable description + - `created_at` (int) Creation timestamp + - `updated_at` (int) Last update timestamp + + ### Errors + - 400, `Type mismatch: variable expects {expected_type}, but got {actual_type} type`, Value type doesn't match variable's expected type + - 404, `conversation_not_exists`, Conversation not found + - 404, `conversation_variable_not_exists`, Variable not found + + + + + + + ```bash {{ title: 'cURL' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": "Updated Value", + "user": "abc-123" + }' + ``` + + + + + ```bash {{ title: 'String Value' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": "New string value", + "user": "abc-123" + }' + ``` + + ```bash {{ title: 'Number Value' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": 42, + "user": "abc-123" + }' + ``` + + ```bash {{ title: 'Object Value' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": {"product": "Widget", "quantity": 10, "price": 29.99}, + "user": "abc-123" + }' + ``` + + + + ```json {{ title: 'Response' }} + { + "id": "variable-uuid-1", + "name": "customer_name", + "value_type": "string", + "value": "Updated Value", + "description": "Customer name extracted from the conversation", + "created_at": 1650000000000, + "updated_at": 1650000001000 + } + ``` + + + + +--- + + + + 特定の会話変数の値を更新します。このエンドポイントは、名前、型、説明を保持しながら、会話中にキャプチャされた変数の値を変更することを可能にします。 + + ### パスパラメータ + + + + 更新する変数を含む会話のID。 + + + 更新する変数のID。 + + + + ### リクエストボディ + + + + 変数の新しい値。変数の期待される型(文字列、数値、オブジェクトなど)と一致する必要があります。 + + + ユーザー識別子。開発者によって定義されたルールに従い、アプリケーション内で一意である必要があります。 + + + + ### レスポンス + + 以下を含む更新された変数オブジェクトを返します: + - `id` (string) 変数ID + - `name` (string) 変数名 + - `value_type` (string) 変数型(文字列、数値、オブジェクトなど) + - `value` (any) 更新された変数値 + - `description` (string) 変数の説明 + - `created_at` (int) 作成タイムスタンプ + - `updated_at` (int) 最終更新タイムスタンプ + + ### エラー + - 400, `Type mismatch: variable expects {expected_type}, but got {actual_type} type`, 値の型が変数の期待される型と一致しません + - 404, `conversation_not_exists`, 会話が見つかりません + - 404, `conversation_variable_not_exists`, 変数が見つかりません + + + + + + + ```bash {{ title: 'cURL' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": "Updated Value", + "user": "abc-123" + }' + ``` + + + + + ```bash {{ title: '文字列値' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": "新しい文字列値", + "user": "abc-123" + }' + ``` + + ```bash {{ title: '数値' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": 42, + "user": "abc-123" + }' + ``` + + ```bash {{ title: 'オブジェクト値' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": {"product": "Widget", "quantity": 10, "price": 29.99}, + "user": "abc-123" + }' + ``` + + + + ```json {{ title: 'Response' }} + { + "id": "variable-uuid-1", + "name": "customer_name", + "value_type": "string", + "value": "Updated Value", + "description": "会話から抽出された顧客名", + "created_at": 1650000000000, + "updated_at": 1650000001000 + } + ``` + + + + +--- + + + + 更新特定对话变量的值。此端点允许您修改在对话过程中捕获的变量值,同时保留其名称、类型和描述。 + + ### 路径参数 + + + + 包含要更新变量的对话ID。 + + + 要更新的变量ID。 + + + + ### 请求体 + + + + 变量的新值。必须匹配变量的预期类型(字符串、数字、对象等)。 + + + 用户标识符,由开发人员定义的规则,在应用程序内必须唯一。 + + + + ### 响应 + + 返回包含以下内容的更新变量对象: + - `id` (string) 变量ID + - `name` (string) 变量名称 + - `value_type` (string) 变量类型(字符串、数字、对象等) + - `value` (any) 更新后的变量值 + - `description` (string) 变量描述 + - `created_at` (int) 创建时间戳 + - `updated_at` (int) 最后更新时间戳 + + ### 错误 + - 400, `Type mismatch: variable expects {expected_type}, but got {actual_type} type`, 值类型与变量的预期类型不匹配 + - 404, `conversation_not_exists`, 对话不存在 + - 404, `conversation_variable_not_exists`, 变量不存在 + + + + + + + ```bash {{ title: 'cURL' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": "Updated Value", + "user": "abc-123" + }' + ``` + + + + + ```bash {{ title: '字符串值' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": "新的字符串值", + "user": "abc-123" + }' + ``` + + ```bash {{ title: '数字值' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": 42, + "user": "abc-123" + }' + ``` + + ```bash {{ title: '对象值' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": {"product": "Widget", "quantity": 10, "price": 29.99}, + "user": "abc-123" + }' + ``` + + + + ```json {{ title: 'Response' }} + { + "id": "variable-uuid-1", + "name": "customer_name", + "value_type": "string", + "value": "Updated Value", + "description": "客户名称(从对话中提取)", + "created_at": 1650000000000, + "updated_at": 1650000001000 + } + ``` + + + + +--- + + + + Update the value of a specific conversation variable. This endpoint allows you to modify the value of a variable that was captured during the conversation while preserving its name, type, and description. + + ### Path Parameters + + + + The ID of the conversation containing the variable to update. + + + The ID of the variable to update. + + + + ### Request Body + + + + The new value for the variable. Must match the variable's expected type (string, number, object, etc.). + + + The user identifier, defined by the developer, must ensure uniqueness within the application. + + + + ### Response + + Returns the updated variable object with: + - `id` (string) Variable ID + - `name` (string) Variable name + - `value_type` (string) Variable type (string, number, object, etc.) + - `value` (any) Updated variable value + - `description` (string) Variable description + - `created_at` (int) Creation timestamp + - `updated_at` (int) Last update timestamp + + ### Errors + - 400, `Type mismatch: variable expects {expected_type}, but got {actual_type} type`, Value type doesn't match variable's expected type + - 404, `conversation_not_exists`, Conversation not found + - 404, `conversation_variable_not_exists`, Variable not found + + + + + + + ```bash {{ title: 'cURL' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": "Updated Value", + "user": "abc-123" + }' + ``` + + + + + ```bash {{ title: 'String Value' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": "New string value", + "user": "abc-123" + }' + ``` + + ```bash {{ title: 'Number Value' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": 42, + "user": "abc-123" + }' + ``` + + ```bash {{ title: 'Object Value' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": {"product": "Widget", "quantity": 10, "price": 29.99}, + "user": "abc-123" + }' + ``` + + + + ```json {{ title: 'Response' }} + { + "id": "variable-uuid-1", + "name": "customer_name", + "value_type": "string", + "value": "Updated Value", + "description": "Customer name extracted from the conversation", + "created_at": 1650000000000, + "updated_at": 1650000001000 + } + ``` + + + + +--- + + + + 特定の会話変数の値を更新します。このエンドポイントは、名前、型、説明を保持しながら、会話中にキャプチャされた変数の値を変更することを可能にします。 + + ### パスパラメータ + + + + 更新する変数を含む会話のID。 + + + 更新する変数のID。 + + + + ### リクエストボディ + + + + 変数の新しい値。変数の期待される型(文字列、数値、オブジェクトなど)と一致する必要があります。 + + + ユーザー識別子。開発者によって定義されたルールに従い、アプリケーション内で一意である必要があります。 + + + + ### レスポンス + + 以下を含む更新された変数オブジェクトを返します: + - `id` (string) 変数ID + - `name` (string) 変数名 + - `value_type` (string) 変数型(文字列、数値、オブジェクトなど) + - `value` (any) 更新された変数値 + - `description` (string) 変数の説明 + - `created_at` (int) 作成タイムスタンプ + - `updated_at` (int) 最終更新タイムスタンプ + + ### エラー + - 400, `Type mismatch: variable expects {expected_type}, but got {actual_type} type`, 値の型が変数の期待される型と一致しません + - 404, `conversation_not_exists`, 会話が見つかりません + - 404, `conversation_variable_not_exists`, 変数が見つかりません + + + + + + + ```bash {{ title: 'cURL' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": "Updated Value", + "user": "abc-123" + }' + ``` + + + + + ```bash {{ title: '文字列値' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": "新しい文字列値", + "user": "abc-123" + }' + ``` + + ```bash {{ title: '数値' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": 42, + "user": "abc-123" + }' + ``` + + ```bash {{ title: 'オブジェクト値' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": {"product": "Widget", "quantity": 10, "price": 29.99}, + "user": "abc-123" + }' + ``` + + + + ```json {{ title: 'Response' }} + { + "id": "variable-uuid-1", + "name": "customer_name", + "value_type": "string", + "value": "Updated Value", + "description": "会話から抽出された顧客名", + "created_at": 1650000000000, + "updated_at": 1650000001000 + } + ``` + + + + +--- + + + + 更新特定对话变量的值。此端点允许您修改在对话过程中捕获的变量值,同时保留其名称、类型和描述。 + + ### 路径参数 + + + + 包含要更新变量的对话ID。 + + + 要更新的变量ID。 + + + + ### 请求体 + + + + 变量的新值。必须匹配变量的预期类型(字符串、数字、对象等)。 + + + 用户标识符,由开发人员定义的规则,在应用程序内必须唯一。 + + + + ### 响应 + + 返回包含以下内容的更新变量对象: + - `id` (string) 变量ID + - `name` (string) 变量名称 + - `value_type` (string) 变量类型(字符串、数字、对象等) + - `value` (any) 更新后的变量值 + - `description` (string) 变量描述 + - `created_at` (int) 创建时间戳 + - `updated_at` (int) 最后更新时间戳 + + ### 错误 + - 400, `Type mismatch: variable expects {expected_type}, but got {actual_type} type`, 值类型与变量的预期类型不匹配 + - 404, `conversation_not_exists`, 对话不存在 + - 404, `conversation_variable_not_exists`, 变量不存在 + + + + + + + ```bash {{ title: 'cURL' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": "Updated Value", + "user": "abc-123" + }' + ``` + + + + + ```bash {{ title: '字符串值' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": "新的字符串值", + "user": "abc-123" + }' + ``` + + ```bash {{ title: '数字值' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": 42, + "user": "abc-123" + }' + ``` + + ```bash {{ title: '对象值' }} + curl -X PUT '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables/{variable_id}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer {api_key}' \ + --data-raw '{ + "value": {"product": "Widget", "quantity": 10, "price": 29.99}, + "user": "abc-123" + }' + ``` + + + + ```json {{ title: 'Response' }} + { + "id": "variable-uuid-1", + "name": "customer_name", + "value_type": "string", + "value": "Updated Value", + "description": "客户名称(从对话中提取)", + "created_at": 1650000000000, + "updated_at": 1650000001000 + } + ``` + + + + +--- + Date: Thu, 31 Jul 2025 22:26:50 -0400 Subject: [PATCH 25/32] fix: resolve multipart/form-data boundary issue in HTTP Request compo nent #22880 (#23008) Co-authored-by: crazywoola <427733928@qq.com> --- .../workflow/nodes/http_request/executor.py | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/api/core/workflow/nodes/http_request/executor.py b/api/core/workflow/nodes/http_request/executor.py index fe103c7117..2106369bd6 100644 --- a/api/core/workflow/nodes/http_request/executor.py +++ b/api/core/workflow/nodes/http_request/executor.py @@ -277,6 +277,22 @@ class Executor: elif self.auth.config.type == "custom": headers[authorization.config.header] = authorization.config.api_key or "" + # Handle Content-Type for multipart/form-data requests + # Fix for issue #22880: Missing boundary when using multipart/form-data + body = self.node_data.body + if body and body.type == "form-data": + # For multipart/form-data with files, let httpx handle the boundary automatically + # by not setting Content-Type header when files are present + if not self.files or all(f[0] == "__multipart_placeholder__" for f in self.files): + # Only set Content-Type when there are no actual files + # This ensures httpx generates the correct boundary + if "content-type" not in (k.lower() for k in headers): + headers["Content-Type"] = "multipart/form-data" + elif body and body.type in BODY_TYPE_TO_CONTENT_TYPE: + # Set Content-Type for other body types + if "content-type" not in (k.lower() for k in headers): + headers["Content-Type"] = BODY_TYPE_TO_CONTENT_TYPE[body.type] + return headers def _validate_and_parse_response(self, response: httpx.Response) -> Response: @@ -384,15 +400,24 @@ class Executor: # '__multipart_placeholder__' is inserted to force multipart encoding but is not a real file. # This prevents logging meaningless placeholder entries. if self.files and not all(f[0] == "__multipart_placeholder__" for f in self.files): - for key, (filename, content, mime_type) in self.files: + for file_entry in self.files: + # file_entry should be (key, (filename, content, mime_type)), but handle edge cases + if len(file_entry) != 2 or not isinstance(file_entry[1], tuple) or len(file_entry[1]) < 2: + continue # skip malformed entries + key = file_entry[0] + content = file_entry[1][1] body_string += f"--{boundary}\r\n" body_string += f'Content-Disposition: form-data; name="{key}"\r\n\r\n' - # decode content - try: - body_string += content.decode("utf-8") - except UnicodeDecodeError: - # fix: decode binary content - pass + # decode content safely + if isinstance(content, bytes): + try: + body_string += content.decode("utf-8") + except UnicodeDecodeError: + body_string += content.decode("utf-8", errors="replace") + elif isinstance(content, str): + body_string += content + else: + body_string += f"[Unsupported content type: {type(content).__name__}]" body_string += "\r\n" body_string += f"--{boundary}--\r\n" elif self.node_data.body: From c33741a5e9599a0e7b944dc96baf57aeaf70ad12 Mon Sep 17 00:00:00 2001 From: Alan Bustamante Date: Fri, 1 Aug 2025 04:34:46 +0200 Subject: [PATCH 26/32] fix: improve boolean field handling in plugin configuration forms (#23160) Co-authored-by: crazywoola <427733928@qq.com> --- .../plugin-detail-panel/endpoint-modal.tsx | 17 ++++++++++++++++- .../components/tools/utils/to-form-schema.ts | 10 ++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx b/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx index 130773e0c2..a715237a43 100644 --- a/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx @@ -47,7 +47,22 @@ const EndpointModal: FC = ({ return } } - onSaved(tempCredential) + + // Fix: Process boolean fields to ensure they are sent as proper boolean values + const processedCredential = { ...tempCredential } + formSchemas.forEach((field) => { + if (field.type === 'boolean' && processedCredential[field.name] !== undefined) { + const value = processedCredential[field.name] + if (typeof value === 'string') + processedCredential[field.name] = value === 'true' || value === '1' || value === 'True' + else if (typeof value === 'number') + processedCredential[field.name] = value === 1 + else if (typeof value === 'boolean') + processedCredential[field.name] = value + } + }) + + onSaved(processedCredential) } return ( diff --git a/web/app/components/tools/utils/to-form-schema.ts b/web/app/components/tools/utils/to-form-schema.ts index ee7f3379ad..ae43e6f157 100644 --- a/web/app/components/tools/utils/to-form-schema.ts +++ b/web/app/components/tools/utils/to-form-schema.ts @@ -63,6 +63,16 @@ export const addDefaultValue = (value: Record, formSchemas: { varia const itemValue = value[formSchema.variable] if ((formSchema.default !== undefined) && (value === undefined || itemValue === null || itemValue === '' || itemValue === undefined)) newValues[formSchema.variable] = formSchema.default + + // Fix: Convert boolean field values to proper boolean type + if (formSchema.type === 'boolean' && itemValue !== undefined && itemValue !== null && itemValue !== '') { + if (typeof itemValue === 'string') + newValues[formSchema.variable] = itemValue === 'true' || itemValue === '1' || itemValue === 'True' + else if (typeof itemValue === 'number') + newValues[formSchema.variable] = itemValue === 1 + else if (typeof itemValue === 'boolean') + newValues[formSchema.variable] = itemValue + } }) return newValues } From da5c003f9788790277ae9dd41414a56ad644aab2 Mon Sep 17 00:00:00 2001 From: wanttobeamaster <45583625+wanttobeamaster@users.noreply.github.com> Date: Fri, 1 Aug 2025 14:14:11 +0800 Subject: [PATCH 27/32] chore: tablestore full text search support score normalization (#23255) Co-authored-by: xiaozhiqing.xzq --- api/.env.example | 1 + .../middleware/vdb/tablestore_config.py | 5 +++ .../vdb/tablestore/tablestore_vector.py | 40 ++++++++++++++++--- .../vdb/tablestore/test_tablestore.py | 22 +++++++++- docker/.env.example | 1 + docker/docker-compose.yaml | 1 + 6 files changed, 63 insertions(+), 7 deletions(-) diff --git a/api/.env.example b/api/.env.example index 18f2dbf647..4beabfecea 100644 --- a/api/.env.example +++ b/api/.env.example @@ -232,6 +232,7 @@ TABLESTORE_ENDPOINT=https://instance-name.cn-hangzhou.ots.aliyuncs.com TABLESTORE_INSTANCE_NAME=instance-name TABLESTORE_ACCESS_KEY_ID=xxx TABLESTORE_ACCESS_KEY_SECRET=xxx +TABLESTORE_NORMALIZE_FULLTEXT_BM25_SCORE=false # Tidb Vector configuration TIDB_VECTOR_HOST=xxx.eu-central-1.xxx.aws.tidbcloud.com diff --git a/api/configs/middleware/vdb/tablestore_config.py b/api/configs/middleware/vdb/tablestore_config.py index c4dcc0d465..1aab01c6e1 100644 --- a/api/configs/middleware/vdb/tablestore_config.py +++ b/api/configs/middleware/vdb/tablestore_config.py @@ -28,3 +28,8 @@ class TableStoreConfig(BaseSettings): description="AccessKey secret for the instance name", default=None, ) + + TABLESTORE_NORMALIZE_FULLTEXT_BM25_SCORE: bool = Field( + description="Whether to normalize full-text search scores to [0, 1]", + default=False, + ) diff --git a/api/core/rag/datasource/vdb/tablestore/tablestore_vector.py b/api/core/rag/datasource/vdb/tablestore/tablestore_vector.py index 784e27fc7f..91d667ff2c 100644 --- a/api/core/rag/datasource/vdb/tablestore/tablestore_vector.py +++ b/api/core/rag/datasource/vdb/tablestore/tablestore_vector.py @@ -1,5 +1,6 @@ import json import logging +import math from typing import Any, Optional import tablestore # type: ignore @@ -22,6 +23,7 @@ class TableStoreConfig(BaseModel): access_key_secret: Optional[str] = None instance_name: Optional[str] = None endpoint: Optional[str] = None + normalize_full_text_bm25_score: Optional[bool] = False @model_validator(mode="before") @classmethod @@ -47,6 +49,7 @@ class TableStoreVector(BaseVector): config.access_key_secret, config.instance_name, ) + self._normalize_full_text_bm25_score = config.normalize_full_text_bm25_score self._table_name = f"{collection_name}" self._index_name = f"{collection_name}_idx" self._tags_field = f"{Field.METADATA_KEY.value}_tags" @@ -131,8 +134,8 @@ class TableStoreVector(BaseVector): filtered_list = None if document_ids_filter: filtered_list = ["document_id=" + item for item in document_ids_filter] - - return self._search_by_full_text(query, filtered_list, top_k) + score_threshold = float(kwargs.get("score_threshold") or 0.0) + return self._search_by_full_text(query, filtered_list, top_k, score_threshold) def delete(self) -> None: self._delete_table_if_exist() @@ -318,7 +321,19 @@ class TableStoreVector(BaseVector): documents = sorted(documents, key=lambda x: x.metadata["score"] if x.metadata else 0, reverse=True) return documents - def _search_by_full_text(self, query: str, document_ids_filter: list[str] | None, top_k: int) -> list[Document]: + @staticmethod + def _normalize_score_exp_decay(score: float, k: float = 0.15) -> float: + """ + Args: + score: BM25 search score. + k: decay factor, the larger the k, the steeper the low score end + """ + normalized_score = 1 - math.exp(-k * score) + return max(0.0, min(1.0, normalized_score)) + + def _search_by_full_text( + self, query: str, document_ids_filter: list[str] | None, top_k: int, score_threshold: float + ) -> list[Document]: bool_query = tablestore.BoolQuery(must_queries=[], filter_queries=[], should_queries=[], must_not_queries=[]) bool_query.must_queries.append(tablestore.MatchQuery(text=query, field_name=Field.CONTENT_KEY.value)) @@ -339,15 +354,27 @@ class TableStoreVector(BaseVector): documents = [] for search_hit in search_response.search_hits: + score = None + if self._normalize_full_text_bm25_score: + score = self._normalize_score_exp_decay(search_hit.score) + + # skip when score is below threshold and use normalize score + if score and score <= score_threshold: + continue + ots_column_map = {} for col in search_hit.row[1]: ots_column_map[col[0]] = col[1] - vector_str = ots_column_map.get(Field.VECTOR.value) metadata_str = ots_column_map.get(Field.METADATA_KEY.value) - vector = json.loads(vector_str) if vector_str else None metadata = json.loads(metadata_str) if metadata_str else {} + vector_str = ots_column_map.get(Field.VECTOR.value) + vector = json.loads(vector_str) if vector_str else None + + if score: + metadata["score"] = score + documents.append( Document( page_content=ots_column_map.get(Field.CONTENT_KEY.value) or "", @@ -355,6 +382,8 @@ class TableStoreVector(BaseVector): metadata=metadata, ) ) + if self._normalize_full_text_bm25_score: + documents = sorted(documents, key=lambda x: x.metadata["score"] if x.metadata else 0, reverse=True) return documents @@ -375,5 +404,6 @@ class TableStoreVectorFactory(AbstractVectorFactory): instance_name=dify_config.TABLESTORE_INSTANCE_NAME, access_key_id=dify_config.TABLESTORE_ACCESS_KEY_ID, access_key_secret=dify_config.TABLESTORE_ACCESS_KEY_SECRET, + normalize_full_text_bm25_score=dify_config.TABLESTORE_NORMALIZE_FULLTEXT_BM25_SCORE, ), ) diff --git a/api/tests/integration_tests/vdb/tablestore/test_tablestore.py b/api/tests/integration_tests/vdb/tablestore/test_tablestore.py index da549af1b6..aebf3fbda1 100644 --- a/api/tests/integration_tests/vdb/tablestore/test_tablestore.py +++ b/api/tests/integration_tests/vdb/tablestore/test_tablestore.py @@ -2,6 +2,7 @@ import os import uuid import tablestore +from _pytest.python_api import approx from core.rag.datasource.vdb.tablestore.tablestore_vector import ( TableStoreConfig, @@ -16,7 +17,7 @@ from tests.integration_tests.vdb.test_vector_store import ( class TableStoreVectorTest(AbstractVectorTest): - def __init__(self): + def __init__(self, normalize_full_text_score: bool = False): super().__init__() self.vector = TableStoreVector( collection_name=self.collection_name, @@ -25,6 +26,7 @@ class TableStoreVectorTest(AbstractVectorTest): instance_name=os.getenv("TABLESTORE_INSTANCE_NAME"), access_key_id=os.getenv("TABLESTORE_ACCESS_KEY_ID"), access_key_secret=os.getenv("TABLESTORE_ACCESS_KEY_SECRET"), + normalize_full_text_bm25_score=normalize_full_text_score, ), ) @@ -64,7 +66,21 @@ class TableStoreVectorTest(AbstractVectorTest): docs = self.vector.search_by_full_text(get_example_text(), document_ids_filter=[self.example_doc_id]) assert len(docs) == 1 assert docs[0].metadata["doc_id"] == self.example_doc_id - assert not hasattr(docs[0], "score") + if self.vector._config.normalize_full_text_bm25_score: + assert docs[0].metadata["score"] == approx(0.1214, abs=1e-3) + else: + assert docs[0].metadata.get("score") is None + + # return none if normalize_full_text_score=true and score_threshold > 0 + docs = self.vector.search_by_full_text( + get_example_text(), document_ids_filter=[self.example_doc_id], score_threshold=0.5 + ) + if self.vector._config.normalize_full_text_bm25_score: + assert len(docs) == 0 + else: + assert len(docs) == 1 + assert docs[0].metadata["doc_id"] == self.example_doc_id + assert docs[0].metadata.get("score") is None docs = self.vector.search_by_full_text(get_example_text(), document_ids_filter=[str(uuid.uuid4())]) assert len(docs) == 0 @@ -80,3 +96,5 @@ class TableStoreVectorTest(AbstractVectorTest): def test_tablestore_vector(setup_mock_redis): TableStoreVectorTest().run_all_tests() + TableStoreVectorTest(normalize_full_text_score=True).run_all_tests() + TableStoreVectorTest(normalize_full_text_score=False).run_all_tests() diff --git a/docker/.env.example b/docker/.env.example index 7ecdf899fe..13cac189aa 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -653,6 +653,7 @@ TABLESTORE_ENDPOINT=https://instance-name.cn-hangzhou.ots.aliyuncs.com TABLESTORE_INSTANCE_NAME=instance-name TABLESTORE_ACCESS_KEY_ID=xxx TABLESTORE_ACCESS_KEY_SECRET=xxx +TABLESTORE_NORMALIZE_FULLTEXT_BM25_SCORE=false # ------------------------------ # Knowledge Configuration diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index ae83aa758d..690dccb1a8 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -312,6 +312,7 @@ x-shared-env: &shared-api-worker-env TABLESTORE_INSTANCE_NAME: ${TABLESTORE_INSTANCE_NAME:-instance-name} TABLESTORE_ACCESS_KEY_ID: ${TABLESTORE_ACCESS_KEY_ID:-xxx} TABLESTORE_ACCESS_KEY_SECRET: ${TABLESTORE_ACCESS_KEY_SECRET:-xxx} + TABLESTORE_NORMALIZE_FULLTEXT_BM25_SCORE: ${TABLESTORE_NORMALIZE_FULLTEXT_BM25_SCORE:-false} UPLOAD_FILE_SIZE_LIMIT: ${UPLOAD_FILE_SIZE_LIMIT:-15} UPLOAD_FILE_BATCH_LIMIT: ${UPLOAD_FILE_BATCH_LIMIT:-5} ETL_TYPE: ${ETL_TYPE:-dify} From f7016fd92218e30d4fccd3957a9d2332c89913e5 Mon Sep 17 00:00:00 2001 From: HyaCinth <88471803+HyaCiovo@users.noreply.github.com> Date: Fri, 1 Aug 2025 14:18:38 +0800 Subject: [PATCH 28/32] chore: Optimize component styles and interactions (#23250) (#23251) --- .../plugins/marketplace/empty/index.tsx | 2 +- .../block-selector/tool/action-item.tsx | 1 + .../nodes/agent/components/tool-icon.tsx | 56 ++++++++++--------- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/web/app/components/plugins/marketplace/empty/index.tsx b/web/app/components/plugins/marketplace/empty/index.tsx index 0306d5003d..a9cf125a15 100644 --- a/web/app/components/plugins/marketplace/empty/index.tsx +++ b/web/app/components/plugins/marketplace/empty/index.tsx @@ -28,7 +28,7 @@ const Empty = ({
11 && 'mb-0', lightCard && 'bg-background-default-lighter opacity-75', diff --git a/web/app/components/workflow/block-selector/tool/action-item.tsx b/web/app/components/workflow/block-selector/tool/action-item.tsx index e5e33614b0..4a2ed4a87b 100644 --- a/web/app/components/workflow/block-selector/tool/action-item.tsx +++ b/web/app/components/workflow/block-selector/tool/action-item.tsx @@ -34,6 +34,7 @@ const ToolItem: FC = ({ diff --git a/web/app/components/workflow/nodes/agent/components/tool-icon.tsx b/web/app/components/workflow/nodes/agent/components/tool-icon.tsx index 4ff0cd780d..8e6993a78d 100644 --- a/web/app/components/workflow/nodes/agent/components/tool-icon.tsx +++ b/web/app/components/workflow/nodes/agent/components/tool-icon.tsx @@ -61,37 +61,39 @@ export const ToolIcon = memo(({ providerName }: ToolIconProps) => { >
- {(() => { - if (iconFetchError || !icon) +
+ {(() => { + if (iconFetchError || !icon) + return + if (typeof icon === 'string') { + return tool icon setIconFetchError(true)} + /> + } + if (typeof icon === 'object') { + return + } return - if (typeof icon === 'string') { - return tool icon setIconFetchError(true)} - /> - } - if (typeof icon === 'object') { - return - } - return - })()} - {indicator && } + })()} +
+ {indicator && }
}) From 05b002a8b713d762a4ae3b9549cf6eb8fa494b44 Mon Sep 17 00:00:00 2001 From: Leo Zhang Date: Fri, 1 Aug 2025 14:22:59 +0800 Subject: [PATCH 29/32] Add a practical AKS one-click deployment Helm (#23253) --- README.md | 4 ++++ README_AR.md | 4 ++++ README_BN.md | 4 ++++ README_CN.md | 3 +++ README_DE.md | 4 ++++ README_ES.md | 4 ++++ README_FR.md | 4 ++++ README_JA.md | 4 ++++ README_KL.md | 4 ++++ README_KR.md | 4 ++++ README_PT.md | 4 ++++ README_SI.md | 4 ++++ README_TR.md | 4 ++++ README_TW.md | 4 ++++ README_VI.md | 4 ++++ 15 files changed, 59 insertions(+) diff --git a/README.md b/README.md index 16a1268cb1..775f6f351f 100644 --- a/README.md +++ b/README.md @@ -235,6 +235,10 @@ Quickly deploy Dify to Alibaba cloud with [Alibaba Cloud Computing Nest](https:/ One-Click deploy Dify to Alibaba Cloud with [Alibaba Cloud Data Management](https://www.alibabacloud.com/help/en/dms/dify-in-invitational-preview/) +#### Deploy to AKS with Azure Devops Pipeline + +One-Click deploy Dify to AKS with [Azure Devops Pipeline Helm Chart by @LeoZhang](https://github.com/Ruiruiz30/Dify-helm-chart-AKS) + ## Contributing diff --git a/README_AR.md b/README_AR.md index d2cb0098a3..e7a4dbdb27 100644 --- a/README_AR.md +++ b/README_AR.md @@ -217,6 +217,10 @@ docker compose up -d انشر ​​Dify على علي بابا كلاود بنقرة واحدة باستخدام [Alibaba Cloud Data Management](https://www.alibabacloud.com/help/en/dms/dify-in-invitational-preview/) +#### استخدام Azure Devops Pipeline للنشر على AKS + +انشر Dify على AKS بنقرة واحدة باستخدام [Azure Devops Pipeline Helm Chart by @LeoZhang](https://github.com/Ruiruiz30/Dify-helm-chart-AKS) + ## المساهمة diff --git a/README_BN.md b/README_BN.md index f57413ec8b..e4da437eff 100644 --- a/README_BN.md +++ b/README_BN.md @@ -235,6 +235,10 @@ GitHub-এ ডিফাইকে স্টার দিয়ে রাখুন [Alibaba Cloud Data Management](https://www.alibabacloud.com/help/en/dms/dify-in-invitational-preview/) + #### AKS-এ ডিপ্লয় করার জন্য Azure Devops Pipeline ব্যবহার + +[Azure Devops Pipeline Helm Chart by @LeoZhang](https://github.com/Ruiruiz30/Dify-helm-chart-AKS) ব্যবহার করে Dify কে AKS-এ এক ক্লিকে ডিপ্লয় করুন + ## Contributing diff --git a/README_CN.md b/README_CN.md index e9c73eb48b..82149519d3 100644 --- a/README_CN.md +++ b/README_CN.md @@ -233,6 +233,9 @@ docker compose up -d 使用 [阿里云数据管理DMS](https://help.aliyun.com/zh/dms/dify-in-invitational-preview) 将 Dify 一键部署到 阿里云 +#### 使用 Azure Devops Pipeline 部署到AKS + +使用[Azure Devops Pipeline Helm Chart by @LeoZhang](https://github.com/Ruiruiz30/Dify-helm-chart-AKS) 将 Dify 一键部署到 AKS ## Star History diff --git a/README_DE.md b/README_DE.md index d31a56542d..2420ac0392 100644 --- a/README_DE.md +++ b/README_DE.md @@ -230,6 +230,10 @@ Bereitstellung von Dify auf AWS mit [CDK](https://aws.amazon.com/cdk/) Ein-Klick-Bereitstellung von Dify in der Alibaba Cloud mit [Alibaba Cloud Data Management](https://www.alibabacloud.com/help/en/dms/dify-in-invitational-preview/) +#### Verwendung von Azure Devops Pipeline für AKS-Bereitstellung + +Stellen Sie Dify mit einem Klick in AKS bereit, indem Sie [Azure Devops Pipeline Helm Chart by @LeoZhang](https://github.com/Ruiruiz30/Dify-helm-chart-AKS) verwenden + ## Contributing diff --git a/README_ES.md b/README_ES.md index 918bfe2286..4fa59dc18f 100644 --- a/README_ES.md +++ b/README_ES.md @@ -230,6 +230,10 @@ Despliegue Dify en AWS usando [CDK](https://aws.amazon.com/cdk/) Despliega Dify en Alibaba Cloud con un solo clic con [Alibaba Cloud Data Management](https://www.alibabacloud.com/help/en/dms/dify-in-invitational-preview/) +#### Uso de Azure Devops Pipeline para implementar en AKS + +Implementa Dify en AKS con un clic usando [Azure Devops Pipeline Helm Chart by @LeoZhang](https://github.com/Ruiruiz30/Dify-helm-chart-AKS) + ## Contribuir diff --git a/README_FR.md b/README_FR.md index 56ca878aae..dcbc869620 100644 --- a/README_FR.md +++ b/README_FR.md @@ -228,6 +228,10 @@ Déployez Dify sur AWS en utilisant [CDK](https://aws.amazon.com/cdk/) Déployez Dify en un clic sur Alibaba Cloud avec [Alibaba Cloud Data Management](https://www.alibabacloud.com/help/en/dms/dify-in-invitational-preview/) +#### Utilisation d'Azure Devops Pipeline pour déployer sur AKS + +Déployez Dify sur AKS en un clic en utilisant [Azure Devops Pipeline Helm Chart by @LeoZhang](https://github.com/Ruiruiz30/Dify-helm-chart-AKS) + ## Contribuer diff --git a/README_JA.md b/README_JA.md index 6d277a36ed..d840fd6419 100644 --- a/README_JA.md +++ b/README_JA.md @@ -227,6 +227,10 @@ docker compose up -d #### Alibaba Cloud Data Management [Alibaba Cloud Data Management](https://www.alibabacloud.com/help/en/dms/dify-in-invitational-preview/) を利用して、DifyをAlibaba Cloudへワンクリックでデプロイできます +#### AKSへのデプロイにAzure Devops Pipelineを使用 + +[Azure Devops Pipeline Helm Chart by @LeoZhang](https://github.com/Ruiruiz30/Dify-helm-chart-AKS)を使用してDifyをAKSにワンクリックでデプロイ + ## 貢献 diff --git a/README_KL.md b/README_KL.md index dac67eeb29..41c7969e1c 100644 --- a/README_KL.md +++ b/README_KL.md @@ -228,6 +228,10 @@ wa'logh nIqHom neH ghun deployment toy'wI' [CDK](https://aws.amazon.com/cdk/) lo [Alibaba Cloud Data Management](https://www.alibabacloud.com/help/en/dms/dify-in-invitational-preview/) +#### AKS 'e' Deploy je Azure Devops Pipeline lo'laH + +[Azure Devops Pipeline Helm Chart by @LeoZhang](https://github.com/Ruiruiz30/Dify-helm-chart-AKS) lo'laH Dify AKS 'e' wa'DIch click 'e' Deploy + ## Contributing diff --git a/README_KR.md b/README_KR.md index 072481da02..d4b31a8928 100644 --- a/README_KR.md +++ b/README_KR.md @@ -222,6 +222,10 @@ Dify를 Kubernetes에 배포하고 프리미엄 스케일링 설정을 구성했 [Alibaba Cloud Data Management](https://www.alibabacloud.com/help/en/dms/dify-in-invitational-preview/)를 통해 원클릭으로 Dify를 Alibaba Cloud에 배포할 수 있습니다 +#### AKS에 배포하기 위해 Azure Devops Pipeline 사용 + +[Azure Devops Pipeline Helm Chart by @LeoZhang](https://github.com/Ruiruiz30/Dify-helm-chart-AKS)을 사용하여 Dify를 AKS에 원클릭으로 배포 + ## 기여 diff --git a/README_PT.md b/README_PT.md index 1260f8e6fd..94452cb233 100644 --- a/README_PT.md +++ b/README_PT.md @@ -227,6 +227,10 @@ Implante o Dify na AWS usando [CDK](https://aws.amazon.com/cdk/) Implante o Dify na Alibaba Cloud com um clique usando o [Alibaba Cloud Data Management](https://www.alibabacloud.com/help/en/dms/dify-in-invitational-preview/) +#### Usando Azure Devops Pipeline para Implantar no AKS + +Implante o Dify no AKS com um clique usando [Azure Devops Pipeline Helm Chart by @LeoZhang](https://github.com/Ruiruiz30/Dify-helm-chart-AKS) + ## Contribuindo diff --git a/README_SI.md b/README_SI.md index 7ded001d86..d840e9155f 100644 --- a/README_SI.md +++ b/README_SI.md @@ -228,6 +228,10 @@ Uvedite Dify v AWS z uporabo [CDK](https://aws.amazon.com/cdk/) Z enim klikom namestite Dify na Alibaba Cloud z [Alibaba Cloud Data Management](https://www.alibabacloud.com/help/en/dms/dify-in-invitational-preview/) +#### Uporaba Azure Devops Pipeline za uvajanje v AKS + +Z enim klikom namestite Dify v AKS z uporabo [Azure Devops Pipeline Helm Chart by @LeoZhang](https://github.com/Ruiruiz30/Dify-helm-chart-AKS) + ## Prispevam diff --git a/README_TR.md b/README_TR.md index 37953f0de1..470a7570e0 100644 --- a/README_TR.md +++ b/README_TR.md @@ -221,6 +221,10 @@ Dify'ı bulut platformuna tek tıklamayla dağıtın [terraform](https://www.ter [Alibaba Cloud Data Management](https://www.alibabacloud.com/help/en/dms/dify-in-invitational-preview/) kullanarak Dify'ı tek tıkla Alibaba Cloud'a dağıtın +#### AKS'ye Dağıtım için Azure Devops Pipeline Kullanımı + +[Azure Devops Pipeline Helm Chart by @LeoZhang](https://github.com/Ruiruiz30/Dify-helm-chart-AKS) kullanarak Dify'ı tek tıkla AKS'ye dağıtın + ## Katkıda Bulunma diff --git a/README_TW.md b/README_TW.md index f70d6a25f6..18f1d2754a 100644 --- a/README_TW.md +++ b/README_TW.md @@ -233,6 +233,10 @@ Dify 的所有功能都提供相應的 API,因此您可以輕鬆地將 Dify 透過 [阿里雲數據管理DMS](https://www.alibabacloud.com/help/en/dms/dify-in-invitational-preview/),一鍵將 Dify 部署至阿里雲 +#### 使用 Azure Devops Pipeline 部署到AKS + +使用[Azure Devops Pipeline Helm Chart by @LeoZhang](https://github.com/Ruiruiz30/Dify-helm-chart-AKS) 將 Dify 一鍵部署到 AKS + ## 貢獻 diff --git a/README_VI.md b/README_VI.md index ddd9aa95f6..2ab6da80fc 100644 --- a/README_VI.md +++ b/README_VI.md @@ -224,6 +224,10 @@ Triển khai Dify trên AWS bằng [CDK](https://aws.amazon.com/cdk/) Triển khai Dify lên Alibaba Cloud chỉ với một cú nhấp chuột bằng [Alibaba Cloud Data Management](https://www.alibabacloud.com/help/en/dms/dify-in-invitational-preview/) +#### Sử dụng Azure Devops Pipeline để Triển khai lên AKS + +Triển khai Dify lên AKS chỉ với một cú nhấp chuột bằng [Azure Devops Pipeline Helm Chart bởi @LeoZhang](https://github.com/Ruiruiz30/Dify-helm-chart-AKS) + ## Đóng góp From 759ded3e3a61669f1d88c16b5a2b83c9c6e379f0 Mon Sep 17 00:00:00 2001 From: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com> Date: Fri, 1 Aug 2025 14:51:16 +0800 Subject: [PATCH 30/32] minor fix: fix default for status of TidbAuthBinding in compatible with various versions (#22288) --- ...32b3f888abf_manual_dataset_field_update.py | 25 +++++++++++++++++++ api/models/dataset.py | 2 +- .../configuration/base/warning-mask/index.tsx | 2 +- .../detail/completed/child-segment-detail.tsx | 2 -- web/app/components/workflow/index.tsx | 2 -- .../components/agent-strategy-selector.tsx | 1 - .../workflow/nodes/agent/use-config.ts | 1 - .../workflow/nodes/llm/use-config.ts | 2 -- web/app/components/workflow/types.ts | 2 +- 9 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 api/migrations/versions/2025_07_24_1450-532b3f888abf_manual_dataset_field_update.py diff --git a/api/migrations/versions/2025_07_24_1450-532b3f888abf_manual_dataset_field_update.py b/api/migrations/versions/2025_07_24_1450-532b3f888abf_manual_dataset_field_update.py new file mode 100644 index 0000000000..1664fb99c4 --- /dev/null +++ b/api/migrations/versions/2025_07_24_1450-532b3f888abf_manual_dataset_field_update.py @@ -0,0 +1,25 @@ +"""manual dataset field update + +Revision ID: 532b3f888abf +Revises: 8bcc02c9bd07 +Create Date: 2025-07-24 14:50:48.779833 + +""" +from alembic import op +import models as models +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '532b3f888abf' +down_revision = '8bcc02c9bd07' +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute("ALTER TABLE tidb_auth_bindings ALTER COLUMN status SET DEFAULT 'CREATING'::character varying") + + +def downgrade(): + op.execute("ALTER TABLE tidb_auth_bindings ALTER COLUMN status SET DEFAULT 'CREATING'") diff --git a/api/models/dataset.py b/api/models/dataset.py index 4d41d0c8b3..e62101ae73 100644 --- a/api/models/dataset.py +++ b/api/models/dataset.py @@ -970,7 +970,7 @@ class TidbAuthBinding(Base): cluster_id: Mapped[str] = mapped_column(String(255), nullable=False) cluster_name: Mapped[str] = mapped_column(String(255), nullable=False) active: Mapped[bool] = mapped_column(db.Boolean, nullable=False, server_default=db.text("false")) - status = mapped_column(String(255), nullable=False, server_default=db.text("CREATING")) + status = mapped_column(String(255), nullable=False, server_default=db.text("'CREATING'::character varying")) account: Mapped[str] = mapped_column(String(255), nullable=False) password: Mapped[str] = mapped_column(String(255), nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, server_default=func.current_timestamp()) diff --git a/web/app/components/app/configuration/base/warning-mask/index.tsx b/web/app/components/app/configuration/base/warning-mask/index.tsx index 8bd7ea12aa..78d01703f8 100644 --- a/web/app/components/app/configuration/base/warning-mask/index.tsx +++ b/web/app/components/app/configuration/base/warning-mask/index.tsx @@ -22,7 +22,7 @@ const WarningMask: FC = ({ footer, }) => { return ( -
{warningIcon}
diff --git a/web/app/components/datasets/documents/detail/completed/child-segment-detail.tsx b/web/app/components/datasets/documents/detail/completed/child-segment-detail.tsx index 4fb1e90657..e686226e5f 100644 --- a/web/app/components/datasets/documents/detail/completed/child-segment-detail.tsx +++ b/web/app/components/datasets/documents/detail/completed/child-segment-detail.tsx @@ -60,7 +60,6 @@ const ChildSegmentDetail: FC = ({ const wordCountText = useMemo(() => { const count = content.length return `${formatNumber(count)} ${t('datasetDocuments.segment.characters', { count })}` - // eslint-disable-next-line react-hooks/exhaustive-deps }, [content.length]) const EditTimeText = useMemo(() => { @@ -69,7 +68,6 @@ const ChildSegmentDetail: FC = ({ dateFormat: `${t('datasetDocuments.segment.dateTimeFormat')}`, }) return `${t('datasetDocuments.segment.editedAt')} ${timeText}` - // eslint-disable-next-line react-hooks/exhaustive-deps }, [childChunkInfo?.updated_at]) return ( diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index 3356188618..a5894451c1 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -190,7 +190,6 @@ export const Workflow: FC = memo(({ return () => { handleSyncWorkflowDraft(true, true) } - // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft() @@ -282,7 +281,6 @@ export const Workflow: FC = memo(({ const { fetchInspectVars } = useSetWorkflowVarsWithValue() useEffect(() => { fetchInspectVars() - // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const store = useStoreApi() diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx index c872952900..9e67debd58 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx @@ -143,7 +143,6 @@ export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) => category: PluginType.agent, }) } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [query]) const pluginRef = useRef(null) diff --git a/web/app/components/workflow/nodes/agent/use-config.ts b/web/app/components/workflow/nodes/agent/use-config.ts index dd9236f24f..01abf7f761 100644 --- a/web/app/components/workflow/nodes/agent/use-config.ts +++ b/web/app/components/workflow/nodes/agent/use-config.ts @@ -151,7 +151,6 @@ const useConfig = (id: string, payload: AgentNodeType) => { return const newData = formattingLegacyData() setInputs(newData) - // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentStrategy]) // vars diff --git a/web/app/components/workflow/nodes/llm/use-config.ts b/web/app/components/workflow/nodes/llm/use-config.ts index b8516caed8..8c22068671 100644 --- a/web/app/components/workflow/nodes/llm/use-config.ts +++ b/web/app/components/workflow/nodes/llm/use-config.ts @@ -101,7 +101,6 @@ const useConfig = (id: string, payload: LLMNodeType) => { }) setInputs(newInputs) } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [defaultConfig, isChatModel]) const [modelChanged, setModelChanged] = useState(false) @@ -161,7 +160,6 @@ const useConfig = (id: string, payload: LLMNodeType) => { return setModelChanged(false) handleVisionConfigAfterModelChanged() - // eslint-disable-next-line react-hooks/exhaustive-deps }, [isVisionModel, modelChanged]) // variables diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index d8153cf08f..11a424c5dd 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -447,6 +447,6 @@ export enum VersionHistoryContextMenuOptions { delete = 'delete', } -export interface ChildNodeTypeCount { +export type ChildNodeTypeCount = { [key: string]: number; } From fd086b06a6461210086680e5f0a85fa596267cb8 Mon Sep 17 00:00:00 2001 From: Yongtao Huang Date: Fri, 1 Aug 2025 15:21:31 +0800 Subject: [PATCH 31/32] CI: restrict autofix.ci to run only in official repo (#23267) --- .github/workflows/autofix.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 5e290c5d02..152ff3b648 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -9,6 +9,7 @@ permissions: jobs: autofix: + if: github.repository == 'langgenius/dify' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From f78b903a4946e0a142d0e8ffe78d78d590b4d9f7 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Fri, 1 Aug 2025 15:43:36 +0800 Subject: [PATCH 32/32] Chore/variable label (#23270) --- .../workflow-variable-block/component.tsx | 77 ++---------- .../readonly-input-with-select-var.tsx | 45 ++----- .../nodes/_base/components/variable-tag.tsx | 68 +++-------- .../variable/var-reference-picker.tsx | 18 ++- .../variable/var-reference-vars.tsx | 21 ++-- .../variable-label/base/variable-icon.tsx | 28 +++++ .../variable-label/base/variable-label.tsx | 83 +++++++++++++ .../variable-label/base/variable-name.tsx | 30 +++++ .../base/variable-node-label.tsx | 37 ++++++ .../variable/variable-label/hooks.ts | 89 ++++++++++++++ .../variable/variable-label/index.tsx | 5 + .../variable/variable-label/types.ts | 19 +++ .../variable-icon-with-color.tsx | 30 +++++ .../variable-label-in-editor.tsx | 40 +++++++ .../variable-label/variable-label-in-node.tsx | 17 +++ .../variable-label-in-select.tsx | 13 ++ .../variable-label/variable-label-in-text.tsx | 17 +++ .../workflow/nodes/assigner/node.tsx | 42 +++---- .../nodes/document-extractor/node.tsx | 19 ++- .../components/workflow/nodes/end/node.tsx | 47 ++------ .../components/workflow/nodes/http/node.tsx | 2 +- .../components/condition-files-list-value.tsx | 30 ++--- .../if-else/components/condition-value.tsx | 32 ++--- .../workflow/nodes/list-operator/node.tsx | 19 ++- .../components/condition-files-list-value.tsx | 30 ++--- .../nodes/loop/components/condition-value.tsx | 29 ++--- .../components/node-group-item.tsx | 46 ++++---- .../components/node-variable-item.tsx | 111 ------------------ .../workflow/variable-inspect/group.tsx | 14 +-- .../workflow/variable-inspect/right.tsx | 20 ++-- 30 files changed, 595 insertions(+), 483 deletions(-) create mode 100644 web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-icon.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-label.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-name.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-node-label.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/variable/variable-label/hooks.ts create mode 100644 web/app/components/workflow/nodes/_base/components/variable/variable-label/index.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/variable/variable-label/types.ts create mode 100644 web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-icon-with-color.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-editor.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-node.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-select.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-text.tsx delete mode 100644 web/app/components/workflow/nodes/variable-assigner/components/node-variable-item.tsx diff --git a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx index da5ad84cb1..06b583543a 100644 --- a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx +++ b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx @@ -10,10 +10,6 @@ import { } from 'lexical' import { mergeRegister } from '@lexical/utils' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' -import { - RiErrorWarningFill, - RiMoreLine, -} from '@remixicon/react' import { useReactFlow, useStoreApi } from 'reactflow' import { useSelectOrDelete } from '../../hooks' import type { WorkflowNodesMap } from './node' @@ -22,17 +18,15 @@ import { DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND, UPDATE_WORKFLOW_NODES_MAP, } from './index' -import cn from '@/utils/classnames' -import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' -import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others' -import { VarBlockIcon } from '@/app/components/workflow/block-icon' -import { Line3 } from '@/app/components/base/icons/src/public/common' import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' import Tooltip from '@/app/components/base/tooltip' import { isExceptionVariable } from '@/app/components/workflow/utils' import VarFullPathPanel from '@/app/components/workflow/nodes/_base/components/variable/var-full-path-panel' import { Type } from '@/app/components/workflow/nodes/llm/types' import type { ValueSelector, Var } from '@/app/components/workflow/types' +import { + VariableLabelInEditor, +} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' type WorkflowVariableBlockComponentProps = { nodeKey: string @@ -126,69 +120,22 @@ const WorkflowVariableBlockComponent = ({ }, [node, reactflow, store]) const Item = ( -
{ e.stopPropagation() handleVariableJump() }} + isExceptionVariable={isException} + errorMsg={!variableValid ? t('workflow.errorMsg.invalidVariable') : undefined} + isSelected={isSelected} ref={ref} - > - {!isEnv && !isChatVar && ( -
- { - node?.type && ( -
- -
- ) - } -
{node?.title}
- -
- )} - {isShowAPart && ( -
- - -
- )} - -
- {!isEnv && !isChatVar && } - {isEnv && } - {isChatVar && } -
{varName}
- { - !variableValid && ( - - ) - } -
-
+ notShowFullPath={isShowAPart} + /> ) - if (!variableValid) { - return ( - - {Item} - - ) - } - if (!node) return Item diff --git a/web/app/components/workflow/nodes/_base/components/readonly-input-with-select-var.tsx b/web/app/components/workflow/nodes/_base/components/readonly-input-with-select-var.tsx index 4a4ca454d3..c1927011dc 100644 --- a/web/app/components/workflow/nodes/_base/components/readonly-input-with-select-var.tsx +++ b/web/app/components/workflow/nodes/_base/components/readonly-input-with-select-var.tsx @@ -4,12 +4,10 @@ import React from 'react' import cn from 'classnames' import { useWorkflow } from '../../../hooks' import { BlockEnum } from '../../../types' -import { VarBlockIcon } from '../../../block-icon' -import { getNodeInfoById, isConversationVar, isENV, isSystemVar } from './variable/utils' -import { Line3 } from '@/app/components/base/icons/src/public/common' -import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' -import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others' -import { RiMoreLine } from '@remixicon/react' +import { getNodeInfoById, isSystemVar } from './variable/utils' +import { + VariableLabelInText, +} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' type Props = { nodeId: string value: string @@ -42,40 +40,17 @@ const ReadonlyInputWithSelectVar: FC = ({ const value = vars[index].split('.') const isSystem = isSystemVar(value) - const isEnv = isENV(value) - const isChatVar = isConversationVar(value) const node = (isSystem ? startNode : getNodeInfoById(availableNodes, value[0]))?.data - const varName = `${isSystem ? 'sys.' : ''}${value[value.length - 1]}` const isShowAPart = value.length > 2 return ( {str} -
- {!isEnv && !isChatVar && ( -
-
- -
-
{node?.title}
- -
- )} - {isShowAPart && ( -
- - -
- )} -
- {!isEnv && !isChatVar && } - {isEnv && } - {isChatVar && } -
{varName}
-
-
+
) }) return html diff --git a/web/app/components/workflow/nodes/_base/components/variable-tag.tsx b/web/app/components/workflow/nodes/_base/components/variable-tag.tsx index d73a3d4924..163cbba49e 100644 --- a/web/app/components/workflow/nodes/_base/components/variable-tag.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable-tag.tsx @@ -1,9 +1,6 @@ import { useCallback, useMemo } from 'react' import { useNodes, useReactFlow, useStoreApi } from 'reactflow' -import { capitalize } from 'lodash-es' import { useTranslation } from 'react-i18next' -import { RiErrorWarningFill } from '@remixicon/react' -import { VarBlockIcon } from '@/app/components/workflow/block-icon' import type { CommonNodeType, Node, @@ -11,13 +8,11 @@ import type { VarType, } from '@/app/components/workflow/types' import { BlockEnum } from '@/app/components/workflow/types' -import { Line3 } from '@/app/components/base/icons/src/public/common' -import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' -import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others' import { getNodeInfoById, isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' -import Tooltip from '@/app/components/base/tooltip' -import cn from '@/utils/classnames' import { isExceptionVariable } from '@/app/components/workflow/utils' +import { + VariableLabelInSelect, +} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' type VariableTagProps = { valueSelector: ValueSelector @@ -73,51 +68,20 @@ const VariableTag = ({ const { t } = useTranslation() return ( - -
{ - if (e.metaKey || e.ctrlKey) { - e.stopPropagation() - handleVariableJump() - } - }} - > - {(!isEnv && !isChatVar && <> - {node && ( - <> - -
- {node?.data.title} -
- - )} - - - )} - {isEnv && } - {isChatVar && } -
- {variableName} -
- { - !isShort && varType && ( -
{capitalize(varType)}
- ) + { + if (e.metaKey || e.ctrlKey) { + e.stopPropagation() + handleVariableJump() } - {!isValid && } -
-
+ }} + errorMsg={!isValid ? t('workflow.errorMsg.invalidVariable') : undefined} + isExceptionVariable={isException} + /> ) } diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx index e6f3ce1fa1..0e57db0d8f 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx @@ -23,7 +23,6 @@ import { type CredentialFormSchema, type FormOption, FormTypeEnum } from '@/app/ import { BlockEnum } from '@/app/components/workflow/types' import { VarBlockIcon } from '@/app/components/workflow/block-icon' import { Line3 } from '@/app/components/base/icons/src/public/common' -import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import { PortalToFollowElem, @@ -44,6 +43,7 @@ import VarFullPathPanel from './var-full-path-panel' import { noop } from 'lodash-es' import { useFetchDynamicOptions } from '@/service/use-plugins' import type { Tool } from '@/app/components/tools/types' +import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label' const TRIGGER_DEFAULT_WIDTH = 227 @@ -138,7 +138,6 @@ const VarReferencePicker: FC = ({ useEffect(() => { if (triggerRef.current) setTriggerWidth(triggerRef.current.clientWidth) - // eslint-disable-next-line react-hooks/exhaustive-deps }, [triggerRef.current]) const [varKindType, setVarKindType] = useState(defaultVarKindType) @@ -149,7 +148,6 @@ const VarReferencePicker: FC = ({ const [open, setOpen] = useState(false) useEffect(() => { onOpen() - // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]) const hasValue = !isConstant && value.length > 0 @@ -362,6 +360,13 @@ const VarReferencePicker: FC = ({ return schema }, [dynamicOptions]) + const variableCategory = useMemo(() => { + if (isEnv) return 'environment' + if (isChatVar) return 'conversation' + if (isLoopVar) return 'loop' + return 'system' + }, [isEnv, isChatVar, isLoopVar]) + return (
= ({
)}
- {!hasValue && } {isLoading && } - {isEnv && } - {isChatVar && } +
{varName}
diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx index 303840d8e7..2c600ba66c 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx @@ -5,7 +5,6 @@ import { useHover } from 'ahooks' import { useTranslation } from 'react-i18next' import cn from '@/utils/classnames' import { type NodeOutPutVar, type ValueSelector, type Var, VarType } from '@/app/components/workflow/types' -import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows' import { PortalToFollowElem, @@ -13,7 +12,6 @@ import { PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' import Input from '@/app/components/base/input' -import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others' import { checkKeys } from '@/utils/var' import type { StructuredOutput } from '../../../llm/types' import { Type } from '../../../llm/types' @@ -21,8 +19,8 @@ import PickerStructurePanel from '@/app/components/workflow/nodes/_base/componen import { varTypeToStructType } from './utils' import type { Field } from '@/app/components/workflow/nodes/llm/types' import { FILE_STRUCT } from '@/app/components/workflow/constants' -import { Loop } from '@/app/components/base/icons/src/vender/workflow' import { noop } from 'lodash-es' +import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label' type ObjectChildrenProps = { nodeId: string @@ -118,7 +116,6 @@ const Item: FC = ({ const open = (isObj || isStructureOutput) && isHovering useEffect(() => { onHovering && onHovering(isHovering) - // eslint-disable-next-line react-hooks/exhaustive-deps }, [isHovering]) const handleChosen = (e: React.MouseEvent) => { e.stopPropagation() @@ -132,6 +129,12 @@ const Item: FC = ({ onChange([nodeId, ...objPath, itemData.variable], itemData) } } + const variableCategory = useMemo(() => { + if (isEnv) return 'environment' + if (isChatVar) return 'conversation' + if (isLoopVar) return 'loop' + return 'system' + }, [isEnv, isChatVar, isSys, isLoopVar]) return ( = ({ onMouseDown={e => e.preventDefault()} >
- {!isEnv && !isChatVar && !isLoopVar && } - {isEnv && } - {isChatVar && } - {isLoopVar && } + {!isEnv && !isChatVar && (
{itemData.variable}
)} @@ -219,11 +222,9 @@ const ObjectChildren: FC = ({ const isHovering = isItemHovering || isChildrenHovering useEffect(() => { onHovering && onHovering(isHovering) - // eslint-disable-next-line react-hooks/exhaustive-deps }, [isHovering]) useEffect(() => { onHovering && onHovering(isItemHovering) - // eslint-disable-next-line react-hooks/exhaustive-deps }, [isItemHovering]) // absolute top-[-2px] return ( diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-icon.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-icon.tsx new file mode 100644 index 0000000000..93f47f794a --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-icon.tsx @@ -0,0 +1,28 @@ +import { memo } from 'react' +import cn from '@/utils/classnames' +import { useVarIcon } from '../hooks' +import type { VarInInspectType } from '@/types/workflow' + +export type VariableIconProps = { + className?: string + variables?: string[] + variableCategory?: VarInInspectType | string +} +const VariableIcon = ({ + className, + variables = [], + variableCategory, +}: VariableIconProps) => { + const VarIcon = useVarIcon(variables, variableCategory) + + return VarIcon && ( + + ) +} + +export default memo(VariableIcon) diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-label.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-label.tsx new file mode 100644 index 0000000000..99f080f545 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-label.tsx @@ -0,0 +1,83 @@ +import { memo } from 'react' +import { capitalize } from 'lodash-es' +import { + RiErrorWarningFill, + RiMoreLine, +} from '@remixicon/react' +import type { VariablePayload } from '../types' +import { useVarColor } from '../hooks' +import VariableNodeLabel from './variable-node-label' +import VariableIcon from './variable-icon' +import VariableName from './variable-name' +import cn from '@/utils/classnames' +import Tooltip from '@/app/components/base/tooltip' + +const VariableLabel = ({ + nodeType, + nodeTitle, + variables, + variableType, + className, + errorMsg, + onClick, + isExceptionVariable, + ref, + notShowFullPath, + rightSlot, +}: VariablePayload) => { + const varColorClassName = useVarColor(variables, isExceptionVariable) + return ( +
+ + { + notShowFullPath && ( + <> + +
/
+ + ) + } + + + { + variableType && ( +
+ {capitalize(variableType)} +
+ ) + } + { + !!errorMsg && ( + + + + ) + } + { + rightSlot + } +
+ ) +} + +export default memo(VariableLabel) diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-name.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-name.tsx new file mode 100644 index 0000000000..f656b780a5 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-name.tsx @@ -0,0 +1,30 @@ +import { memo } from 'react' +import { useVarName } from '../hooks' +import cn from '@/utils/classnames' + +type VariableNameProps = { + variables: string[] + className?: string + notShowFullPath?: boolean +} +const VariableName = ({ + variables, + className, + notShowFullPath, +}: VariableNameProps) => { + const varName = useVarName(variables, notShowFullPath) + + return ( +
+ {varName} +
+ ) +} + +export default memo(VariableName) diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-node-label.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-node-label.tsx new file mode 100644 index 0000000000..e4b0e52866 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-node-label.tsx @@ -0,0 +1,37 @@ +import { memo } from 'react' +import { VarBlockIcon } from '@/app/components/workflow/block-icon' +import type { BlockEnum } from '@/app/components/workflow/types' + +type VariableNodeLabelProps = { + nodeType?: BlockEnum + nodeTitle?: string +} +const VariableNodeLabel = ({ + nodeType, + nodeTitle, +}: VariableNodeLabelProps) => { + if (!nodeType) + return null + + return ( + <> + + { + nodeTitle && ( +
+ {nodeTitle} +
+ ) + } +
/
+ + ) +} + +export default memo(VariableNodeLabel) diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/hooks.ts b/web/app/components/workflow/nodes/_base/components/variable/variable-label/hooks.ts new file mode 100644 index 0000000000..14ca87903b --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/hooks.ts @@ -0,0 +1,89 @@ +import { useMemo } from 'react' +import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' +import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others' +import { Loop } from '@/app/components/base/icons/src/vender/workflow' +import { + isConversationVar, + isENV, + isSystemVar, +} from '../utils' +import { VarInInspectType } from '@/types/workflow' + +export const useVarIcon = (variables: string[], variableCategory?: VarInInspectType | string) => { + if (variableCategory === 'loop') + return Loop + + if (isENV(variables) || variableCategory === VarInInspectType.environment || variableCategory === 'environment') + return Env + + if (isConversationVar(variables) || variableCategory === VarInInspectType.conversation || variableCategory === 'conversation') + return BubbleX + + return Variable02 +} + +export const useVarColor = (variables: string[], isExceptionVariable?: boolean, variableCategory?: VarInInspectType | string) => { + return useMemo(() => { + if (isExceptionVariable) + return 'text-text-warning' + + if (variableCategory === 'loop') + return 'text-util-colors-cyan-cyan-500' + + if (isENV(variables) || variableCategory === VarInInspectType.environment || variableCategory === 'environment') + return 'text-util-colors-violet-violet-600' + + if (isConversationVar(variables) || variableCategory === VarInInspectType.conversation || variableCategory === 'conversation') + return 'text-util-colors-teal-teal-700' + + return 'text-text-accent' + }, [variables, isExceptionVariable]) +} + +export const useVarName = (variables: string[], notShowFullPath?: boolean) => { + const variableFullPathName = variables.slice(1).join('.') + const variablesLength = variables.length + const varName = useMemo(() => { + const isSystem = isSystemVar(variables) + const varName = notShowFullPath ? variables[variablesLength - 1] : variableFullPathName + return `${isSystem ? 'sys.' : ''}${varName}` + }, [variables, notShowFullPath]) + + return varName +} + +export const useVarBgColorInEditor = (variables: string[], hasError?: boolean) => { + if (hasError) { + return { + hoverBorderColor: 'hover:border-state-destructive-active', + hoverBgColor: 'hover:bg-state-destructive-hover', + selectedBorderColor: '!border-state-destructive-solid', + selectedBgColor: '!bg-state-destructive-hover', + } + } + + if (isENV(variables)) { + return { + hoverBorderColor: 'hover:border-util-colors-violet-violet-100', + hoverBgColor: 'hover:bg-util-colors-violet-violet-50', + selectedBorderColor: 'border-util-colors-violet-violet-600', + selectedBgColor: 'bg-util-colors-violet-violet-50', + } + } + + if (isConversationVar(variables)) { + return { + hoverBorderColor: 'hover:border-util-colors-teal-teal-100', + hoverBgColor: 'hover:bg-util-colors-teal-teal-50', + selectedBorderColor: 'border-util-colors-teal-teal-600', + selectedBgColor: 'bg-util-colors-teal-teal-50', + } + } + + return { + hoverBorderColor: 'hover:border-state-accent-alt', + hoverBgColor: 'hover:bg-state-accent-hover', + selectedBorderColor: 'border-state-accent-solid', + selectedBgColor: 'bg-state-accent-hover', + } +} diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/index.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/index.tsx new file mode 100644 index 0000000000..012522e0aa --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/index.tsx @@ -0,0 +1,5 @@ +export { default as VariableLabelInSelect } from './variable-label-in-select' +export { default as VariableLabelInEditor } from './variable-label-in-editor' +export { default as VariableLabelInNode } from './variable-label-in-node' +export { default as VariableLabelInText } from './variable-label-in-text' +export { default as VariableIconWithColor } from './variable-icon-with-color' diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/types.ts b/web/app/components/workflow/nodes/_base/components/variable/variable-label/types.ts new file mode 100644 index 0000000000..6f3b06f6ee --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/types.ts @@ -0,0 +1,19 @@ +import type { ReactNode } from 'react' +import type { + BlockEnum, + VarType, +} from '@/app/components/workflow/types' + +export type VariablePayload = { + className?: string + nodeType?: BlockEnum + nodeTitle?: string + variables: string[] + variableType?: VarType + onClick?: (e: React.MouseEvent) => void + errorMsg?: string + isExceptionVariable?: boolean + ref?: React.Ref + notShowFullPath?: boolean + rightSlot?: ReactNode +} diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-icon-with-color.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-icon-with-color.tsx new file mode 100644 index 0000000000..56d6c3738e --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-icon-with-color.tsx @@ -0,0 +1,30 @@ +import { memo } from 'react' +import VariableIcon from './base/variable-icon' +import type { VariableIconProps } from './base/variable-icon' +import { useVarColor } from './hooks' +import cn from '@/utils/classnames' + +type VariableIconWithColorProps = { + isExceptionVariable?: boolean +} & VariableIconProps + +const VariableIconWithColor = ({ + isExceptionVariable, + variableCategory, + variables = [], + className, +}: VariableIconWithColorProps) => { + const varColorClassName = useVarColor(variables, isExceptionVariable, variableCategory) + return ( + + ) +} + +export default memo(VariableIconWithColor) diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-editor.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-editor.tsx new file mode 100644 index 0000000000..fa5ae57f91 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-editor.tsx @@ -0,0 +1,40 @@ +import { memo } from 'react' +import type { VariablePayload } from './types' +import VariableLabel from './base/variable-label' +import { useVarBgColorInEditor } from './hooks' +import cn from '@/utils/classnames' + +type VariableLabelInEditorProps = { + isSelected?: boolean +} & VariablePayload +const VariableLabelInEditor = ({ + isSelected, + variables, + errorMsg, + ...rest +}: VariableLabelInEditorProps) => { + const { + hoverBorderColor, + hoverBgColor, + selectedBorderColor, + selectedBgColor, + } = useVarBgColorInEditor(variables, !!errorMsg) + + return ( + + ) +} + +export default memo(VariableLabelInEditor) diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-node.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-node.tsx new file mode 100644 index 0000000000..cebe140e26 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-node.tsx @@ -0,0 +1,17 @@ +import { memo } from 'react' +import type { VariablePayload } from './types' +import VariableLabel from './base/variable-label' +import cn from '@/utils/classnames' + +const VariableLabelInNode = (variablePayload: VariablePayload) => { + return ( + + ) +} + +export default memo(VariableLabelInNode) diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-select.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-select.tsx new file mode 100644 index 0000000000..34e7b5f461 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-select.tsx @@ -0,0 +1,13 @@ +import { memo } from 'react' +import type { VariablePayload } from './types' +import VariableLabel from './base/variable-label' + +const VariableLabelInSelect = (variablePayload: VariablePayload) => { + return ( + + ) +} + +export default memo(VariableLabelInSelect) diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-text.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-text.tsx new file mode 100644 index 0000000000..dd0d6fcf8b --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-text.tsx @@ -0,0 +1,17 @@ +import { memo } from 'react' +import type { VariablePayload } from './types' +import VariableLabel from './base/variable-label' +import cn from '@/utils/classnames' + +const VariableLabelInText = (variablePayload: VariablePayload) => { + return ( + + ) +} + +export default memo(VariableLabelInText) diff --git a/web/app/components/workflow/nodes/assigner/node.tsx b/web/app/components/workflow/nodes/assigner/node.tsx index 2dd1ead4f8..5e5950d715 100644 --- a/web/app/components/workflow/nodes/assigner/node.tsx +++ b/web/app/components/workflow/nodes/assigner/node.tsx @@ -2,10 +2,13 @@ import type { FC } from 'react' import React from 'react' import { useNodes } from 'reactflow' import { useTranslation } from 'react-i18next' -import NodeVariableItem from '../variable-assigner/components/node-variable-item' import type { AssignerNodeType } from './types' -import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' import { BlockEnum, type Node, type NodeProps } from '@/app/components/workflow/types' +import { + VariableLabelInNode, +} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' +import Badge from '@/app/components/base/badge' const i18nPrefix = 'workflow.nodes.assigner' @@ -38,19 +41,16 @@ const NodeComponent: FC> = ({ if (!variable || variable.length === 0) return null const isSystem = isSystemVar(variable) - const isEnv = isENV(variable) - const isChatVar = isConversationVar(variable) const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0]) - const varName = isSystem ? `sys.${variable[variable.length - 1]}` : variable.slice(1).join('.') return ( - + } /> ) })} @@ -63,21 +63,17 @@ const NodeComponent: FC> = ({ if (!variable || variable.length === 0) return null const isSystem = isSystemVar(variable) - const isEnv = isENV(variable) - const isChatVar = isConversationVar(variable) - const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0]) - const varName = isSystem ? `sys.${variable[variable.length - 1]}` : variable.slice(1).join('.') return (
- + } />
) diff --git a/web/app/components/workflow/nodes/document-extractor/node.tsx b/web/app/components/workflow/nodes/document-extractor/node.tsx index 6b1d4343be..ab7fe9a9a6 100644 --- a/web/app/components/workflow/nodes/document-extractor/node.tsx +++ b/web/app/components/workflow/nodes/document-extractor/node.tsx @@ -2,10 +2,12 @@ import type { FC } from 'react' import React from 'react' import { useNodes } from 'reactflow' import { useTranslation } from 'react-i18next' -import NodeVariableItem from '../variable-assigner/components/node-variable-item' import type { DocExtractorNodeType } from './types' -import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' import { BlockEnum, type Node, type NodeProps } from '@/app/components/workflow/types' +import { + VariableLabelInNode, +} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' const i18nPrefix = 'workflow.nodes.docExtractor' @@ -21,19 +23,14 @@ const NodeComponent: FC> = ({ return null const isSystem = isSystemVar(variable) - const isEnv = isENV(variable) - const isChatVar = isConversationVar(variable) const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0]) - const varName = isSystem ? `sys.${variable[variable.length - 1]}` : variable.slice(1).join('.') return (
{t(`${i18nPrefix}.inputVar`)}
-
) diff --git a/web/app/components/workflow/nodes/end/node.tsx b/web/app/components/workflow/nodes/end/node.tsx index 6906e0f77c..2583e61b68 100644 --- a/web/app/components/workflow/nodes/end/node.tsx +++ b/web/app/components/workflow/nodes/end/node.tsx @@ -1,19 +1,16 @@ import type { FC } from 'react' import React from 'react' -import cn from 'classnames' import type { EndNodeType } from './types' import type { NodeProps, Variable } from '@/app/components/workflow/types' -import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' import { useIsChatMode, useWorkflow, useWorkflowVariables, } from '@/app/components/workflow/hooks' -import { VarBlockIcon } from '@/app/components/workflow/block-icon' -import { Line3 } from '@/app/components/base/icons/src/public/common' -import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' -import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others' import { BlockEnum } from '@/app/components/workflow/types' +import { + VariableLabelInNode, +} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' const Node: FC> = ({ id, @@ -42,42 +39,20 @@ const Node: FC> = ({
{filteredOutputs.map(({ value_selector }, index) => { const node = getNode(value_selector[0]) - const isSystem = isSystemVar(value_selector) - const isEnv = isENV(value_selector) - const isChatVar = isConversationVar(value_selector) - const varName = isSystem ? `sys.${value_selector[value_selector.length - 1]}` : value_selector[value_selector.length - 1] const varType = getCurrentVariableType({ valueSelector: value_selector, availableNodes, isChatMode, }) - return ( -
-
- {!isEnv && !isChatVar && ( - <> -
- -
-
{node?.data.title}
- - - )} -
- {!isEnv && !isChatVar && } - {isEnv && } - {isChatVar && } -
{varName}
-
-
-
-
{varType}
-
-
+ return ( + ) })} diff --git a/web/app/components/workflow/nodes/http/node.tsx b/web/app/components/workflow/nodes/http/node.tsx index aa1912bd59..6002bf737d 100644 --- a/web/app/components/workflow/nodes/http/node.tsx +++ b/web/app/components/workflow/nodes/http/node.tsx @@ -15,7 +15,7 @@ const Node: FC> = ({
{method}
-
+
{ const notHasValue = comparisonOperatorNotRequireValue(c.comparison_operator) if (notHasValue) @@ -76,19 +72,11 @@ const ConditionValue = ({ return (
- {!isEnvVar && !isChatVar && } - {isEnvVar && } - {isChatVar && } - -
- {variableName} -
+
| undefined = nodes.find(n => n.id === variableSelector[0]) as Node const isException = isExceptionVariable(variableName, node?.data.type) const formatValue = useMemo(() => { @@ -76,20 +74,14 @@ const ConditionValue = ({ return (
- {!isEnvVar && !isChatVar && } - {isEnvVar && } - {isChatVar && } - -
- {variableName} -
+
> = ({ return null const isSystem = isSystemVar(variable) - const isEnv = isENV(variable) - const isChatVar = isConversationVar(variable) const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0]) - const varName = isSystem ? `sys.${variable[variable.length - 1]}` : variable.slice(1).join('.') return (
{t(`${i18nPrefix}.inputVar`)}
-
) diff --git a/web/app/components/workflow/nodes/loop/components/condition-files-list-value.tsx b/web/app/components/workflow/nodes/loop/components/condition-files-list-value.tsx index 772b960953..00eec93de3 100644 --- a/web/app/components/workflow/nodes/loop/components/condition-files-list-value.tsx +++ b/web/app/components/workflow/nodes/loop/components/condition-files-list-value.tsx @@ -11,10 +11,10 @@ import { } from '../utils' import type { ValueSelector } from '../../../types' import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from './../default' -import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' -import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others' -import cn from '@/utils/classnames' -import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import { + VariableLabelInNode, +} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' const i18nPrefix = 'workflow.nodes.ifElse' type ConditionValueProps = { @@ -32,11 +32,7 @@ const ConditionValue = ({ const variableSelector = variable_selector as ValueSelector - const variableName = (isSystemVar(variableSelector) ? variableSelector.slice(0).join('.') : variableSelector.slice(1).join('.')) const operatorName = isComparisonOperatorNeedTranslate(operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${operator}`) : operator - const notHasValue = comparisonOperatorNotRequireValue(operator) - const isEnvVar = isENV(variableSelector) - const isChatVar = isConversationVar(variableSelector) const formatValue = useCallback((c: Condition) => { const notHasValue = comparisonOperatorNotRequireValue(c.comparison_operator) if (notHasValue) @@ -76,19 +72,11 @@ const ConditionValue = ({ return (
- {!isEnvVar && !isChatVar && } - {isEnvVar && } - {isChatVar && } - -
- {variableName} -
+
{ const { t } = useTranslation() - const variableName = labelName || (isSystemVar(variableSelector) ? variableSelector.slice(0).join('.') : variableSelector.slice(1).join('.')) const operatorName = isComparisonOperatorNeedTranslate(operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${operator}`) : operator const notHasValue = comparisonOperatorNotRequireValue(operator) - const isEnvVar = isENV(variableSelector) - const isChatVar = isConversationVar(variableSelector) const formatValue = useMemo(() => { if (notHasValue) return '' @@ -67,19 +64,11 @@ const ConditionValue = ({ return (
- {!isEnvVar && !isChatVar && } - {isEnvVar && } - {isChatVar && } - -
- {variableName} -
+
{ - const isSystem = isSystemVar(variable) - const isEnv = isENV(variable) - const isChatVar = isConversationVar(variable) + !!item.variables.length && ( +
+ { + item.variables.map((variable = [], index) => { + const isSystem = isSystemVar(variable) - const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0]) - const varName = isSystem ? `sys.${variable[variable.length - 1]}` : variable.slice(1).join('.') - const isException = isExceptionVariable(varName, node?.data.type) + const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0]) + const varName = isSystem ? `sys.${variable[variable.length - 1]}` : variable.slice(1).join('.') + const isException = isExceptionVariable(varName, node?.data.type) - return ( - - ) - }) + return ( + + ) + }) + } +
+ ) }
) diff --git a/web/app/components/workflow/nodes/variable-assigner/components/node-variable-item.tsx b/web/app/components/workflow/nodes/variable-assigner/components/node-variable-item.tsx deleted file mode 100644 index f5d05aae26..0000000000 --- a/web/app/components/workflow/nodes/variable-assigner/components/node-variable-item.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { - memo, - useMemo, -} from 'react' -import { useTranslation } from 'react-i18next' -import cn from '@/utils/classnames' -import { VarBlockIcon } from '@/app/components/workflow/block-icon' -import { Line3 } from '@/app/components/base/icons/src/public/common' -import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' -import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others' -import Badge from '@/app/components/base/badge' -import type { Node } from '@/app/components/workflow/types' - -type NodeVariableItemProps = { - isEnv: boolean - isChatVar: boolean - node: Node - varName: string - writeMode?: string - showBorder?: boolean - className?: string - isException?: boolean -} - -const i18nPrefix = 'workflow.nodes.assigner' - -const NodeVariableItem = ({ - isEnv, - isChatVar, - node, - varName, - writeMode, - showBorder, - className, - isException, -}: NodeVariableItemProps) => { - const { t } = useTranslation() - - const VariableIcon = useMemo(() => { - if (isEnv) { - return ( - - ) - } - - if (isChatVar) { - return ( - - ) - } - - return ( - - ) - }, [isEnv, isChatVar, isException]) - - const VariableName = useMemo(() => { - return ( -
- {varName} -
- ) - }, [isEnv, isChatVar, varName, isException]) - return ( -
-
- { - node && ( - <> -
- -
-
- {node?.data.title} -
- - - ) - } - {VariableIcon} - {VariableName} -
- {writeMode && } -
- ) -} - -export default memo(NodeVariableItem) diff --git a/web/app/components/workflow/variable-inspect/group.tsx b/web/app/components/workflow/variable-inspect/group.tsx index 1b032c8992..29b6c3ca44 100644 --- a/web/app/components/workflow/variable-inspect/group.tsx +++ b/web/app/components/workflow/variable-inspect/group.tsx @@ -11,16 +11,12 @@ import { import ActionButton from '@/app/components/base/action-button' import Tooltip from '@/app/components/base/tooltip' import BlockIcon from '@/app/components/workflow/block-icon' -import { - BubbleX, - Env, -} from '@/app/components/base/icons/src/vender/line/others' -import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import type { currentVarType } from './panel' import { VarInInspectType } from '@/types/workflow' import type { NodeWithVar, VarInInspect } from '@/types/workflow' import cn from '@/utils/classnames' import { useToolIcon } from '../hooks' +import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label' type Props = { nodeData?: NodeWithVar @@ -158,9 +154,11 @@ const Group = ({ )} onClick={() => handleSelectVar(varItem, varType)} > - {isEnv && } - {isChatVar && } - {(isSystem || nodeData) && } +
{varItem.name}
{varItem.value_type}
diff --git a/web/app/components/workflow/variable-inspect/right.tsx b/web/app/components/workflow/variable-inspect/right.tsx index 6ddd0d47d3..aa318cfe79 100644 --- a/web/app/components/workflow/variable-inspect/right.tsx +++ b/web/app/components/workflow/variable-inspect/right.tsx @@ -14,12 +14,11 @@ import Badge from '@/app/components/base/badge' import CopyFeedback from '@/app/components/base/copy-feedback' import Tooltip from '@/app/components/base/tooltip' import BlockIcon from '@/app/components/workflow/block-icon' -import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others' -import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import Loading from '@/app/components/base/loading' import type { currentVarType } from './panel' import { VarInInspectType } from '@/types/workflow' import cn from '@/utils/classnames' +import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label' type Props = { currentNodeVar?: currentVarType @@ -86,15 +85,14 @@ const Right = ({
{currentNodeVar && ( <> - {currentNodeVar.nodeType === VarInInspectType.environment && ( - - )} - {currentNodeVar.nodeType === VarInInspectType.conversation && ( - - )} - {currentNodeVar.nodeType === VarInInspectType.system && ( - - )} + { + [VarInInspectType.environment, VarInInspectType.conversation, VarInInspectType.system].includes(currentNodeVar.nodeType as VarInInspectType) && ( + + ) + } {currentNodeVar.nodeType !== VarInInspectType.environment && currentNodeVar.nodeType !== VarInInspectType.conversation && currentNodeVar.nodeType !== VarInInspectType.system && ( <>