Dify 云服务 ·
@@ -35,17 +35,19 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
#
@@ -111,7 +113,7 @@ Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI
### 快速启动
-启动 Dify 服务器的最简单方法是运行我们的 [docker-compose.yml](docker/docker-compose.yaml) 文件。在运行安装命令之前,请确保您的机器上安装了 [Docker](https://docs.docker.com/get-docker/) 和 [Docker Compose](https://docs.docker.com/compose/install/):
+启动 Dify 服务器的最简单方法是运行我们的 [docker-compose.yml](../../docker/docker-compose.yaml) 文件。在运行安装命令之前,请确保您的机器上安装了 [Docker](https://docs.docker.com/get-docker/) 和 [Docker Compose](https://docs.docker.com/compose/install/):
```bash
cd docker
@@ -123,7 +125,7 @@ docker compose up -d
### 自定义配置
-如果您需要自定义配置,请参考 [.env.example](docker/.env.example) 文件中的注释,并更新 `.env` 文件中对应的值。此外,您可能需要根据您的具体部署环境和需求对 `docker-compose.yaml` 文件本身进行调整,例如更改镜像版本、端口映射或卷挂载。完成任何更改后,请重新运行 `docker-compose up -d`。您可以在[此处](https://docs.dify.ai/getting-started/install-self-hosted/environments)找到可用环境变量的完整列表。
+如果您需要自定义配置,请参考 [.env.example](../../docker/.env.example) 文件中的注释,并更新 `.env` 文件中对应的值。此外,您可能需要根据您的具体部署环境和需求对 `docker-compose.yaml` 文件本身进行调整,例如更改镜像版本、端口映射或卷挂载。完成任何更改后,请重新运行 `docker-compose up -d`。您可以在[此处](https://docs.dify.ai/getting-started/install-self-hosted/environments)找到可用环境变量的完整列表。
#### 使用 Helm Chart 或 Kubernetes 资源清单(YAML)部署
@@ -180,7 +182,7 @@ docker compose up -d
## Contributing
-对于那些想要贡献代码的人,请参阅我们的[贡献指南](https://github.com/langgenius/dify/blob/main/CONTRIBUTING/CONTRIBUTING_CN.md)。
+对于那些想要贡献代码的人,请参阅我们的[贡献指南](./CONTRIBUTING.md)。
同时,请考虑通过社交媒体、活动和会议来支持 Dify 的分享。
> 我们正在寻找贡献者来帮助将 Dify 翻译成除了中文和英文之外的其他语言。如果您有兴趣帮助,请参阅我们的[i18n README](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md)获取更多信息,并在我们的[Discord 社区服务器](https://discord.gg/8Tpq4AcN9c)的`global-users`频道中留言。
@@ -196,7 +198,7 @@ docker compose up -d
我们欢迎您为 Dify 做出贡献,以帮助改善 Dify。包括:提交代码、问题、新想法,或分享您基于 Dify 创建的有趣且有用的 AI 应用程序。同时,我们也欢迎您在不同的活动、会议和社交媒体上分享 Dify。
- [GitHub Discussion](https://github.com/langgenius/dify/discussions). 👉:分享您的应用程序并与社区交流。
-- [GitHub Issues](https://github.com/langgenius/dify/issues)。👉:使用 Dify.AI 时遇到的错误和问题,请参阅[贡献指南](../CONTRIBUTING.md)。
+- [GitHub Issues](https://github.com/langgenius/dify/issues)。👉:使用 Dify.AI 时遇到的错误和问题,请参阅[贡献指南](./CONTRIBUTING.md)。
- [电子邮件支持](mailto:hello@dify.ai?subject=%5BGitHub%5DQuestions%20About%20Dify)。👉:关于使用 Dify.AI 的问题。
- [Discord](https://discord.gg/FngNHpbcY7)。👉:分享您的应用程序并与社区交流。
- [X(Twitter)](https://twitter.com/dify_ai)。👉:分享您的应用程序并与社区交流。
@@ -208,4 +210,4 @@ docker compose up -d
## License
-本仓库遵循 [Dify Open Source License](../LICENSE) 开源协议,该许可证本质上是 Apache 2.0,但有一些额外的限制。
+本仓库遵循 [Dify Open Source License](../../LICENSE) 开源协议,该许可证本质上是 Apache 2.0,但有一些额外的限制。
diff --git a/CONTRIBUTING/CONTRIBUTING_TW.md b/docs/zh-TW/CONTRIBUTING.md
similarity index 96%
rename from CONTRIBUTING/CONTRIBUTING_TW.md
rename to docs/zh-TW/CONTRIBUTING.md
index 7fba220a22..1d5f02efa1 100644
--- a/CONTRIBUTING/CONTRIBUTING_TW.md
+++ b/docs/zh-TW/CONTRIBUTING.md
@@ -6,7 +6,7 @@
這份指南與 Dify 一樣,都在持續完善中。如果指南內容有落後於實際專案的情況,還請見諒,也歡迎提供改進建議。
-關於授權部分,請花點時間閱讀我們簡短的[授權和貢獻者協議](../LICENSE)。社群也需遵守[行為準則](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)。
+關於授權部分,請花點時間閱讀我們簡短的[授權和貢獻者協議](../../LICENSE)。社群也需遵守[行為準則](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)。
## 開始之前
diff --git a/README/README_TW.md b/docs/zh-TW/README.md
similarity index 80%
rename from README/README_TW.md
rename to docs/zh-TW/README.md
index b9c0b81246..526e8d9c8c 100644
--- a/README/README_TW.md
+++ b/docs/zh-TW/README.md
@@ -1,4 +1,4 @@
-
+
📌 介紹 Dify 工作流程檔案上傳功能:重現 Google NotebookLM Podcast
@@ -39,18 +39,18 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
Dify 是一個開源的 LLM 應用程式開發平台。其直觀的界面結合了智能代理工作流程、RAG 管道、代理功能、模型管理、可觀察性功能等,讓您能夠快速從原型進展到生產環境。
@@ -64,7 +64,7 @@ Dify 是一個開源的 LLM 應用程式開發平台。其直觀的界面結合
-啟動 Dify 伺服器最簡單的方式是透過 [docker compose](docker/docker-compose.yaml)。在使用以下命令運行 Dify 之前,請確保您的機器已安裝 [Docker](https://docs.docker.com/get-docker/) 和 [Docker Compose](https://docs.docker.com/compose/install/):
+啟動 Dify 伺服器最簡單的方式是透過 [docker compose](../../docker/docker-compose.yaml)。在使用以下命令運行 Dify 之前,請確保您的機器已安裝 [Docker](https://docs.docker.com/get-docker/) 和 [Docker Compose](https://docs.docker.com/compose/install/):
```bash
cd dify
@@ -128,7 +128,7 @@ Dify 的所有功能都提供相應的 API,因此您可以輕鬆地將 Dify
## 進階設定
-如果您需要自定義配置,請參考我們的 [.env.example](docker/.env.example) 文件中的註釋,並在您的 `.env` 文件中更新相應的值。此外,根據您特定的部署環境和需求,您可能需要調整 `docker-compose.yaml` 文件本身,例如更改映像版本、端口映射或卷掛載。進行任何更改後,請重新運行 `docker-compose up -d`。您可以在[這裡](https://docs.dify.ai/getting-started/install-self-hosted/environments)找到可用環境變數的完整列表。
+如果您需要自定義配置,請參考我們的 [.env.example](../../docker/.env.example) 文件中的註釋,並在您的 `.env` 文件中更新相應的值。此外,根據您特定的部署環境和需求,您可能需要調整 `docker-compose.yaml` 文件本身,例如更改映像版本、端口映射或卷掛載。進行任何更改後,請重新運行 `docker-compose up -d`。您可以在[這裡](https://docs.dify.ai/getting-started/install-self-hosted/environments)找到可用環境變數的完整列表。
如果您想配置高可用性設置,社區貢獻的 [Helm Charts](https://helm.sh/) 和 Kubernetes 資源清單(YAML)允許在 Kubernetes 上部署 Dify。
@@ -173,7 +173,7 @@ Dify 的所有功能都提供相應的 API,因此您可以輕鬆地將 Dify
## 貢獻
-對於想要貢獻程式碼的開發者,請參閱我們的[貢獻指南](https://github.com/langgenius/dify/blob/main/CONTRIBUTING/CONTRIBUTING_TW.md)。
+對於想要貢獻程式碼的開發者,請參閱我們的[貢獻指南](./CONTRIBUTING.md)。
同時,也請考慮透過在社群媒體和各種活動與會議上分享 Dify 來支持我們。
> 我們正在尋找貢獻者協助將 Dify 翻譯成中文和英文以外的語言。如果您有興趣幫忙,請查看 [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) 獲取更多資訊,並在我們的 [Discord 社群伺服器](https://discord.gg/8Tpq4AcN9c) 的 `global-users` 頻道留言給我們。
@@ -181,7 +181,7 @@ Dify 的所有功能都提供相應的 API,因此您可以輕鬆地將 Dify
## 社群與聯絡方式
- [GitHub Discussion](https://github.com/langgenius/dify/discussions):最適合分享反饋和提問。
-- [GitHub Issues](https://github.com/langgenius/dify/issues):最適合報告使用 Dify.AI 時遇到的問題和提出功能建議。請參閱我們的[貢獻指南](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)。
+- [GitHub Issues](https://github.com/langgenius/dify/issues):最適合報告使用 Dify.AI 時遇到的問題和提出功能建議。請參閱我們的[貢獻指南](./CONTRIBUTING.md)。
- [Discord](https://discord.gg/FngNHpbcY7):最適合分享您的應用程式並與社群互動。
- [X(Twitter)](https://twitter.com/dify_ai):最適合分享您的應用程式並與社群互動。
@@ -201,4 +201,4 @@ Dify 的所有功能都提供相應的 API,因此您可以輕鬆地將 Dify
## 授權條款
-本代碼庫採用 [Dify 開源授權](../LICENSE),這基本上是 Apache 2.0 授權加上一些額外限制條款。
+本代碼庫採用 [Dify 開源授權](../../LICENSE),這基本上是 Apache 2.0 授權加上一些額外限制條款。
diff --git a/scripts/stress-test/setup/import_workflow_app.py b/scripts/stress-test/setup/import_workflow_app.py
index 86d0239e35..41a76bd29b 100755
--- a/scripts/stress-test/setup/import_workflow_app.py
+++ b/scripts/stress-test/setup/import_workflow_app.py
@@ -8,7 +8,7 @@ sys.path.append(str(Path(__file__).parent.parent))
import json
import httpx
-from common import Logger, config_helper
+from common import Logger, config_helper # type: ignore[import]
def import_workflow_app() -> None:
diff --git a/sdks/python-client/MANIFEST.in b/sdks/python-client/MANIFEST.in
index 12f44237a2..34b7e8711c 100644
--- a/sdks/python-client/MANIFEST.in
+++ b/sdks/python-client/MANIFEST.in
@@ -1 +1,3 @@
recursive-include dify_client *.py
+include README.md
+include LICENSE
diff --git a/sdks/python-client/README.md b/sdks/python-client/README.md
index 34b14b3a94..ebfb5f5397 100644
--- a/sdks/python-client/README.md
+++ b/sdks/python-client/README.md
@@ -10,6 +10,8 @@ First, install `dify-client` python sdk package:
pip install dify-client
```
+### Synchronous Usage
+
Write your code with sdk:
- completion generate with `blocking` response_mode
@@ -221,3 +223,187 @@ answer = result.get("data").get("outputs")
print(answer["answer"])
```
+
+- Dataset Management
+
+```python
+from dify_client import KnowledgeBaseClient
+
+api_key = "your_api_key"
+dataset_id = "your_dataset_id"
+
+# Use context manager to ensure proper resource cleanup
+with KnowledgeBaseClient(api_key, dataset_id) as kb_client:
+ # Get dataset information
+ dataset_info = kb_client.get_dataset()
+ dataset_info.raise_for_status()
+ print(dataset_info.json())
+
+ # Update dataset configuration
+ update_response = kb_client.update_dataset(
+ name="Updated Dataset Name",
+ description="Updated description",
+ indexing_technique="high_quality"
+ )
+ update_response.raise_for_status()
+ print(update_response.json())
+
+ # Batch update document status
+ batch_response = kb_client.batch_update_document_status(
+ action="enable",
+ document_ids=["doc_id_1", "doc_id_2", "doc_id_3"]
+ )
+ batch_response.raise_for_status()
+ print(batch_response.json())
+```
+
+- Conversation Variables Management
+
+```python
+from dify_client import ChatClient
+
+api_key = "your_api_key"
+
+# Use context manager to ensure proper resource cleanup
+with ChatClient(api_key) as chat_client:
+ # Get all conversation variables
+ variables = chat_client.get_conversation_variables(
+ conversation_id="conversation_id",
+ user="user_id"
+ )
+ variables.raise_for_status()
+ print(variables.json())
+
+ # Update a specific conversation variable
+ update_var = chat_client.update_conversation_variable(
+ conversation_id="conversation_id",
+ variable_id="variable_id",
+ value="new_value",
+ user="user_id"
+ )
+ update_var.raise_for_status()
+ print(update_var.json())
+```
+
+### Asynchronous Usage
+
+The SDK provides full async/await support for all API operations using `httpx.AsyncClient`. All async clients mirror their synchronous counterparts but require `await` for method calls.
+
+- async chat with `blocking` response_mode
+
+```python
+import asyncio
+from dify_client import AsyncChatClient
+
+api_key = "your_api_key"
+
+async def main():
+ # Use async context manager for proper resource cleanup
+ async with AsyncChatClient(api_key) as client:
+ response = await client.create_chat_message(
+ inputs={},
+ query="Hello, how are you?",
+ user="user_id",
+ response_mode="blocking"
+ )
+ response.raise_for_status()
+ result = response.json()
+ print(result.get('answer'))
+
+# Run the async function
+asyncio.run(main())
+```
+
+- async completion with `streaming` response_mode
+
+```python
+import asyncio
+import json
+from dify_client import AsyncCompletionClient
+
+api_key = "your_api_key"
+
+async def main():
+ async with AsyncCompletionClient(api_key) as client:
+ response = await client.create_completion_message(
+ inputs={"query": "What's the weather?"},
+ response_mode="streaming",
+ user="user_id"
+ )
+ response.raise_for_status()
+
+ # Stream the response
+ async for line in response.aiter_lines():
+ if line.startswith('data:'):
+ data = line[5:].strip()
+ if data:
+ chunk = json.loads(data)
+ print(chunk.get('answer', ''), end='', flush=True)
+
+asyncio.run(main())
+```
+
+- async workflow execution
+
+```python
+import asyncio
+from dify_client import AsyncWorkflowClient
+
+api_key = "your_api_key"
+
+async def main():
+ async with AsyncWorkflowClient(api_key) as client:
+ response = await client.run(
+ inputs={"query": "What is machine learning?"},
+ response_mode="blocking",
+ user="user_id"
+ )
+ response.raise_for_status()
+ result = response.json()
+ print(result.get("data").get("outputs"))
+
+asyncio.run(main())
+```
+
+- async dataset management
+
+```python
+import asyncio
+from dify_client import AsyncKnowledgeBaseClient
+
+api_key = "your_api_key"
+dataset_id = "your_dataset_id"
+
+async def main():
+ async with AsyncKnowledgeBaseClient(api_key, dataset_id) as kb_client:
+ # Get dataset information
+ dataset_info = await kb_client.get_dataset()
+ dataset_info.raise_for_status()
+ print(dataset_info.json())
+
+ # List documents
+ docs = await kb_client.list_documents(page=1, page_size=10)
+ docs.raise_for_status()
+ print(docs.json())
+
+asyncio.run(main())
+```
+
+**Benefits of Async Usage:**
+
+- **Better Performance**: Handle multiple concurrent API requests efficiently
+- **Non-blocking I/O**: Don't block the event loop during network operations
+- **Scalability**: Ideal for applications handling many simultaneous requests
+- **Modern Python**: Leverages Python's native async/await syntax
+
+**Available Async Clients:**
+
+- `AsyncDifyClient` - Base async client
+- `AsyncChatClient` - Async chat operations
+- `AsyncCompletionClient` - Async completion operations
+- `AsyncWorkflowClient` - Async workflow operations
+- `AsyncKnowledgeBaseClient` - Async dataset/knowledge base operations
+- `AsyncWorkspaceClient` - Async workspace operations
+
+```
+```
diff --git a/sdks/python-client/dify_client/__init__.py b/sdks/python-client/dify_client/__init__.py
index e252bc0472..ced093b20a 100644
--- a/sdks/python-client/dify_client/__init__.py
+++ b/sdks/python-client/dify_client/__init__.py
@@ -7,11 +7,28 @@ from dify_client.client import (
WorkspaceClient,
)
+from dify_client.async_client import (
+ AsyncChatClient,
+ AsyncCompletionClient,
+ AsyncDifyClient,
+ AsyncKnowledgeBaseClient,
+ AsyncWorkflowClient,
+ AsyncWorkspaceClient,
+)
+
__all__ = [
+ # Synchronous clients
"ChatClient",
"CompletionClient",
"DifyClient",
"KnowledgeBaseClient",
"WorkflowClient",
"WorkspaceClient",
+ # Asynchronous clients
+ "AsyncChatClient",
+ "AsyncCompletionClient",
+ "AsyncDifyClient",
+ "AsyncKnowledgeBaseClient",
+ "AsyncWorkflowClient",
+ "AsyncWorkspaceClient",
]
diff --git a/sdks/python-client/dify_client/async_client.py b/sdks/python-client/dify_client/async_client.py
new file mode 100644
index 0000000000..984f668d0c
--- /dev/null
+++ b/sdks/python-client/dify_client/async_client.py
@@ -0,0 +1,808 @@
+"""Asynchronous Dify API client.
+
+This module provides async/await support for all Dify API operations using httpx.AsyncClient.
+All client classes mirror their synchronous counterparts but require `await` for method calls.
+
+Example:
+ import asyncio
+ from dify_client import AsyncChatClient
+
+ async def main():
+ async with AsyncChatClient(api_key="your-key") as client:
+ response = await client.create_chat_message(
+ inputs={},
+ query="Hello",
+ user="user-123"
+ )
+ print(response.json())
+
+ asyncio.run(main())
+"""
+
+import json
+import os
+from typing import Literal, Dict, List, Any, IO
+
+import aiofiles
+import httpx
+
+
+class AsyncDifyClient:
+ """Asynchronous Dify API client.
+
+ This client uses httpx.AsyncClient for efficient async connection pooling.
+ It's recommended to use this client as a context manager:
+
+ Example:
+ async with AsyncDifyClient(api_key="your-key") as client:
+ response = await client.get_app_info()
+ """
+
+ def __init__(
+ self,
+ api_key: str,
+ base_url: str = "https://api.dify.ai/v1",
+ timeout: float = 60.0,
+ ):
+ """Initialize the async Dify client.
+
+ Args:
+ api_key: Your Dify API key
+ base_url: Base URL for the Dify API
+ timeout: Request timeout in seconds (default: 60.0)
+ """
+ self.api_key = api_key
+ self.base_url = base_url
+ self._client = httpx.AsyncClient(
+ base_url=base_url,
+ timeout=httpx.Timeout(timeout, connect=5.0),
+ )
+
+ async def __aenter__(self):
+ """Support async context manager protocol."""
+ return self
+
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
+ """Clean up resources when exiting async context."""
+ await self.aclose()
+
+ async def aclose(self):
+ """Close the async HTTP client and release resources."""
+ if hasattr(self, "_client"):
+ await self._client.aclose()
+
+ async def _send_request(
+ self,
+ method: str,
+ endpoint: str,
+ json: dict | None = None,
+ params: dict | None = None,
+ stream: bool = False,
+ **kwargs,
+ ):
+ """Send an async HTTP request to the Dify API.
+
+ Args:
+ method: HTTP method (GET, POST, PUT, PATCH, DELETE)
+ endpoint: API endpoint path
+ json: JSON request body
+ params: Query parameters
+ stream: Whether to stream the response
+ **kwargs: Additional arguments to pass to httpx.request
+
+ Returns:
+ httpx.Response object
+ """
+ headers = {
+ "Authorization": f"Bearer {self.api_key}",
+ "Content-Type": "application/json",
+ }
+
+ response = await self._client.request(
+ method,
+ endpoint,
+ json=json,
+ params=params,
+ headers=headers,
+ **kwargs,
+ )
+
+ return response
+
+ async def _send_request_with_files(self, method: str, endpoint: str, data: dict, files: dict):
+ """Send an async HTTP request with file uploads.
+
+ Args:
+ method: HTTP method (POST, PUT, etc.)
+ endpoint: API endpoint path
+ data: Form data
+ files: Files to upload
+
+ Returns:
+ httpx.Response object
+ """
+ headers = {"Authorization": f"Bearer {self.api_key}"}
+
+ response = await self._client.request(
+ method,
+ endpoint,
+ data=data,
+ headers=headers,
+ files=files,
+ )
+
+ return response
+
+ async def message_feedback(self, message_id: str, rating: Literal["like", "dislike"], user: str):
+ """Send feedback for a message."""
+ data = {"rating": rating, "user": user}
+ return await self._send_request("POST", f"/messages/{message_id}/feedbacks", data)
+
+ async def get_application_parameters(self, user: str):
+ """Get application parameters."""
+ params = {"user": user}
+ return await self._send_request("GET", "/parameters", params=params)
+
+ async def file_upload(self, user: str, files: dict):
+ """Upload a file."""
+ data = {"user": user}
+ return await self._send_request_with_files("POST", "/files/upload", data=data, files=files)
+
+ async def text_to_audio(self, text: str, user: str, streaming: bool = False):
+ """Convert text to audio."""
+ data = {"text": text, "user": user, "streaming": streaming}
+ return await self._send_request("POST", "/text-to-audio", json=data)
+
+ async def get_meta(self, user: str):
+ """Get metadata."""
+ params = {"user": user}
+ return await self._send_request("GET", "/meta", params=params)
+
+ async def get_app_info(self):
+ """Get basic application information including name, description, tags, and mode."""
+ return await self._send_request("GET", "/info")
+
+ async def get_app_site_info(self):
+ """Get application site information."""
+ return await self._send_request("GET", "/site")
+
+ async def get_file_preview(self, file_id: str):
+ """Get file preview by file ID."""
+ return await self._send_request("GET", f"/files/{file_id}/preview")
+
+
+class AsyncCompletionClient(AsyncDifyClient):
+ """Async client for Completion API operations."""
+
+ async def create_completion_message(
+ self,
+ inputs: dict,
+ response_mode: Literal["blocking", "streaming"],
+ user: str,
+ files: dict | None = None,
+ ):
+ """Create a completion message.
+
+ Args:
+ inputs: Input variables for the completion
+ response_mode: Response mode ('blocking' or 'streaming')
+ user: User identifier
+ files: Optional files to include
+
+ Returns:
+ httpx.Response object
+ """
+ data = {
+ "inputs": inputs,
+ "response_mode": response_mode,
+ "user": user,
+ "files": files,
+ }
+ return await self._send_request(
+ "POST",
+ "/completion-messages",
+ data,
+ stream=(response_mode == "streaming"),
+ )
+
+
+class AsyncChatClient(AsyncDifyClient):
+ """Async client for Chat API operations."""
+
+ async def create_chat_message(
+ self,
+ inputs: dict,
+ query: str,
+ user: str,
+ response_mode: Literal["blocking", "streaming"] = "blocking",
+ conversation_id: str | None = None,
+ files: dict | None = None,
+ ):
+ """Create a chat message.
+
+ Args:
+ inputs: Input variables for the chat
+ query: User query/message
+ user: User identifier
+ response_mode: Response mode ('blocking' or 'streaming')
+ conversation_id: Optional conversation ID for context
+ files: Optional files to include
+
+ Returns:
+ httpx.Response object
+ """
+ data = {
+ "inputs": inputs,
+ "query": query,
+ "user": user,
+ "response_mode": response_mode,
+ "files": files,
+ }
+ if conversation_id:
+ data["conversation_id"] = conversation_id
+
+ return await self._send_request(
+ "POST",
+ "/chat-messages",
+ data,
+ stream=(response_mode == "streaming"),
+ )
+
+ async def get_suggested(self, message_id: str, user: str):
+ """Get suggested questions for a message."""
+ params = {"user": user}
+ return await self._send_request("GET", f"/messages/{message_id}/suggested", params=params)
+
+ async def stop_message(self, task_id: str, user: str):
+ """Stop a running message generation."""
+ data = {"user": user}
+ return await self._send_request("POST", f"/chat-messages/{task_id}/stop", data)
+
+ async def get_conversations(
+ self,
+ user: str,
+ last_id: str | None = None,
+ limit: int | None = None,
+ pinned: bool | None = None,
+ ):
+ """Get list of conversations."""
+ params = {"user": user, "last_id": last_id, "limit": limit, "pinned": pinned}
+ return await self._send_request("GET", "/conversations", params=params)
+
+ async def get_conversation_messages(
+ self,
+ user: str,
+ conversation_id: str | None = None,
+ first_id: str | None = None,
+ limit: int | None = None,
+ ):
+ """Get messages from a conversation."""
+ params = {
+ "user": user,
+ "conversation_id": conversation_id,
+ "first_id": first_id,
+ "limit": limit,
+ }
+ return await self._send_request("GET", "/messages", params=params)
+
+ async def rename_conversation(self, conversation_id: str, name: str, auto_generate: bool, user: str):
+ """Rename a conversation."""
+ data = {"name": name, "auto_generate": auto_generate, "user": user}
+ return await self._send_request("POST", f"/conversations/{conversation_id}/name", data)
+
+ async def delete_conversation(self, conversation_id: str, user: str):
+ """Delete a conversation."""
+ data = {"user": user}
+ return await self._send_request("DELETE", f"/conversations/{conversation_id}", data)
+
+ async def audio_to_text(self, audio_file: IO[bytes] | tuple, user: str):
+ """Convert audio to text."""
+ data = {"user": user}
+ files = {"file": audio_file}
+ return await self._send_request_with_files("POST", "/audio-to-text", data, files)
+
+ # Annotation APIs
+ async def annotation_reply_action(
+ self,
+ action: Literal["enable", "disable"],
+ score_threshold: float,
+ embedding_provider_name: str,
+ embedding_model_name: str,
+ ):
+ """Enable or disable annotation reply feature."""
+ data = {
+ "score_threshold": score_threshold,
+ "embedding_provider_name": embedding_provider_name,
+ "embedding_model_name": embedding_model_name,
+ }
+ return await self._send_request("POST", f"/apps/annotation-reply/{action}", json=data)
+
+ async def get_annotation_reply_status(self, action: Literal["enable", "disable"], job_id: str):
+ """Get the status of an annotation reply action job."""
+ return await self._send_request("GET", f"/apps/annotation-reply/{action}/status/{job_id}")
+
+ async def list_annotations(self, page: int = 1, limit: int = 20, keyword: str | None = None):
+ """List annotations for the application."""
+ params = {"page": page, "limit": limit, "keyword": keyword}
+ return await self._send_request("GET", "/apps/annotations", params=params)
+
+ async def create_annotation(self, question: str, answer: str):
+ """Create a new annotation."""
+ data = {"question": question, "answer": answer}
+ return await self._send_request("POST", "/apps/annotations", json=data)
+
+ async def update_annotation(self, annotation_id: str, question: str, answer: str):
+ """Update an existing annotation."""
+ data = {"question": question, "answer": answer}
+ return await self._send_request("PUT", f"/apps/annotations/{annotation_id}", json=data)
+
+ async def delete_annotation(self, annotation_id: str):
+ """Delete an annotation."""
+ return await self._send_request("DELETE", f"/apps/annotations/{annotation_id}")
+
+ # Conversation Variables APIs
+ async def get_conversation_variables(self, conversation_id: str, user: str):
+ """Get all variables for a specific conversation.
+
+ Args:
+ conversation_id: The conversation ID to query variables for
+ user: User identifier
+
+ Returns:
+ Response from the API containing:
+ - variables: List of conversation variables with their values
+ - conversation_id: The conversation ID
+ """
+ params = {"user": user}
+ url = f"/conversations/{conversation_id}/variables"
+ return await self._send_request("GET", url, params=params)
+
+ async def update_conversation_variable(self, conversation_id: str, variable_id: str, value: Any, user: str):
+ """Update a specific conversation variable.
+
+ Args:
+ conversation_id: The conversation ID
+ variable_id: The variable ID to update
+ value: New value for the variable
+ user: User identifier
+
+ Returns:
+ Response from the API with updated variable information
+ """
+ data = {"value": value, "user": user}
+ url = f"/conversations/{conversation_id}/variables/{variable_id}"
+ return await self._send_request("PATCH", url, json=data)
+
+
+class AsyncWorkflowClient(AsyncDifyClient):
+ """Async client for Workflow API operations."""
+
+ async def run(
+ self,
+ inputs: dict,
+ response_mode: Literal["blocking", "streaming"] = "streaming",
+ user: str = "abc-123",
+ ):
+ """Run a workflow."""
+ data = {"inputs": inputs, "response_mode": response_mode, "user": user}
+ return await self._send_request("POST", "/workflows/run", data)
+
+ async def stop(self, task_id: str, user: str):
+ """Stop a running workflow task."""
+ data = {"user": user}
+ return await self._send_request("POST", f"/workflows/tasks/{task_id}/stop", data)
+
+ async def get_result(self, workflow_run_id: str):
+ """Get workflow run result."""
+ return await self._send_request("GET", f"/workflows/run/{workflow_run_id}")
+
+ async def get_workflow_logs(
+ self,
+ keyword: str = None,
+ status: Literal["succeeded", "failed", "stopped"] | None = None,
+ page: int = 1,
+ limit: int = 20,
+ created_at__before: str = None,
+ created_at__after: str = None,
+ created_by_end_user_session_id: str = None,
+ created_by_account: str = None,
+ ):
+ """Get workflow execution logs with optional filtering."""
+ params = {
+ "page": page,
+ "limit": limit,
+ "keyword": keyword,
+ "status": status,
+ "created_at__before": created_at__before,
+ "created_at__after": created_at__after,
+ "created_by_end_user_session_id": created_by_end_user_session_id,
+ "created_by_account": created_by_account,
+ }
+ return await self._send_request("GET", "/workflows/logs", params=params)
+
+ async def run_specific_workflow(
+ self,
+ workflow_id: str,
+ inputs: dict,
+ response_mode: Literal["blocking", "streaming"] = "streaming",
+ user: str = "abc-123",
+ ):
+ """Run a specific workflow by workflow ID."""
+ data = {"inputs": inputs, "response_mode": response_mode, "user": user}
+ return await self._send_request(
+ "POST",
+ f"/workflows/{workflow_id}/run",
+ data,
+ stream=(response_mode == "streaming"),
+ )
+
+
+class AsyncWorkspaceClient(AsyncDifyClient):
+ """Async client for workspace-related operations."""
+
+ async def get_available_models(self, model_type: str):
+ """Get available models by model type."""
+ url = f"/workspaces/current/models/model-types/{model_type}"
+ return await self._send_request("GET", url)
+
+
+class AsyncKnowledgeBaseClient(AsyncDifyClient):
+ """Async client for Knowledge Base API operations."""
+
+ def __init__(
+ self,
+ api_key: str,
+ base_url: str = "https://api.dify.ai/v1",
+ dataset_id: str | None = None,
+ timeout: float = 60.0,
+ ):
+ """Construct an AsyncKnowledgeBaseClient object.
+
+ Args:
+ api_key: API key of Dify
+ base_url: Base URL of Dify API
+ dataset_id: ID of the dataset
+ timeout: Request timeout in seconds
+ """
+ super().__init__(api_key=api_key, base_url=base_url, timeout=timeout)
+ self.dataset_id = dataset_id
+
+ def _get_dataset_id(self):
+ """Get the dataset ID, raise error if not set."""
+ if self.dataset_id is None:
+ raise ValueError("dataset_id is not set")
+ return self.dataset_id
+
+ async def create_dataset(self, name: str, **kwargs):
+ """Create a new dataset."""
+ return await self._send_request("POST", "/datasets", {"name": name}, **kwargs)
+
+ async def list_datasets(self, page: int = 1, page_size: int = 20, **kwargs):
+ """List all datasets."""
+ return await self._send_request("GET", "/datasets", params={"page": page, "limit": page_size}, **kwargs)
+
+ async def create_document_by_text(self, name: str, text: str, extra_params: dict | None = None, **kwargs):
+ """Create a document by text.
+
+ Args:
+ name: Name of the document
+ text: Text content of the document
+ extra_params: Extra parameters for the API
+
+ Returns:
+ Response from the API
+ """
+ data = {
+ "indexing_technique": "high_quality",
+ "process_rule": {"mode": "automatic"},
+ "name": name,
+ "text": text,
+ }
+ if extra_params is not None and isinstance(extra_params, dict):
+ data.update(extra_params)
+ url = f"/datasets/{self._get_dataset_id()}/document/create_by_text"
+ return await self._send_request("POST", url, json=data, **kwargs)
+
+ async def update_document_by_text(
+ self,
+ document_id: str,
+ name: str,
+ text: str,
+ extra_params: dict | None = None,
+ **kwargs,
+ ):
+ """Update a document by text."""
+ data = {"name": name, "text": text}
+ if extra_params is not None and isinstance(extra_params, dict):
+ data.update(extra_params)
+ url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/update_by_text"
+ return await self._send_request("POST", url, json=data, **kwargs)
+
+ async def create_document_by_file(
+ self,
+ file_path: str,
+ original_document_id: str | None = None,
+ extra_params: dict | None = None,
+ ):
+ """Create a document by file."""
+ async with aiofiles.open(file_path, "rb") as f:
+ files = {"file": (os.path.basename(file_path), f)}
+ data = {
+ "process_rule": {"mode": "automatic"},
+ "indexing_technique": "high_quality",
+ }
+ if extra_params is not None and isinstance(extra_params, dict):
+ data.update(extra_params)
+ if original_document_id is not None:
+ data["original_document_id"] = original_document_id
+ url = f"/datasets/{self._get_dataset_id()}/document/create_by_file"
+ return await self._send_request_with_files("POST", url, {"data": json.dumps(data)}, files)
+
+ async def update_document_by_file(self, document_id: str, file_path: str, extra_params: dict | None = None):
+ """Update a document by file."""
+ async with aiofiles.open(file_path, "rb") as f:
+ files = {"file": (os.path.basename(file_path), f)}
+ data = {}
+ if extra_params is not None and isinstance(extra_params, dict):
+ data.update(extra_params)
+ url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/update_by_file"
+ return await self._send_request_with_files("POST", url, {"data": json.dumps(data)}, files)
+
+ async def batch_indexing_status(self, batch_id: str, **kwargs):
+ """Get the status of the batch indexing."""
+ url = f"/datasets/{self._get_dataset_id()}/documents/{batch_id}/indexing-status"
+ return await self._send_request("GET", url, **kwargs)
+
+ async def delete_dataset(self):
+ """Delete this dataset."""
+ url = f"/datasets/{self._get_dataset_id()}"
+ return await self._send_request("DELETE", url)
+
+ async def delete_document(self, document_id: str):
+ """Delete a document."""
+ url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}"
+ return await self._send_request("DELETE", url)
+
+ async def list_documents(
+ self,
+ page: int | None = None,
+ page_size: int | None = None,
+ keyword: str | None = None,
+ **kwargs,
+ ):
+ """Get a list of documents in this dataset."""
+ params = {
+ "page": page,
+ "limit": page_size,
+ "keyword": keyword,
+ }
+ url = f"/datasets/{self._get_dataset_id()}/documents"
+ return await self._send_request("GET", url, params=params, **kwargs)
+
+ async def add_segments(self, document_id: str, segments: list[dict], **kwargs):
+ """Add segments to a document."""
+ data = {"segments": segments}
+ url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/segments"
+ return await self._send_request("POST", url, json=data, **kwargs)
+
+ async def query_segments(
+ self,
+ document_id: str,
+ keyword: str | None = None,
+ status: str | None = None,
+ **kwargs,
+ ):
+ """Query segments in this document.
+
+ Args:
+ document_id: ID of the document
+ keyword: Query keyword (optional)
+ status: Status of the segment (optional, e.g., 'completed')
+ **kwargs: Additional parameters to pass to the API.
+ Can include a 'params' dict for extra query parameters.
+
+ Returns:
+ Response from the API
+ """
+ url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/segments"
+ params = {
+ "keyword": keyword,
+ "status": status,
+ }
+ if "params" in kwargs:
+ params.update(kwargs.pop("params"))
+ return await self._send_request("GET", url, params=params, **kwargs)
+
+ async def delete_document_segment(self, document_id: str, segment_id: str):
+ """Delete a segment from a document."""
+ url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/segments/{segment_id}"
+ return await self._send_request("DELETE", url)
+
+ async def update_document_segment(self, document_id: str, segment_id: str, segment_data: dict, **kwargs):
+ """Update a segment in a document."""
+ data = {"segment": segment_data}
+ url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/segments/{segment_id}"
+ return await self._send_request("POST", url, json=data, **kwargs)
+
+ # Advanced Knowledge Base APIs
+ async def hit_testing(
+ self,
+ query: str,
+ retrieval_model: Dict[str, Any] = None,
+ external_retrieval_model: Dict[str, Any] = None,
+ ):
+ """Perform hit testing on the dataset."""
+ data = {"query": query}
+ if retrieval_model:
+ data["retrieval_model"] = retrieval_model
+ if external_retrieval_model:
+ data["external_retrieval_model"] = external_retrieval_model
+ url = f"/datasets/{self._get_dataset_id()}/hit-testing"
+ return await self._send_request("POST", url, json=data)
+
+ async def get_dataset_metadata(self):
+ """Get dataset metadata."""
+ url = f"/datasets/{self._get_dataset_id()}/metadata"
+ return await self._send_request("GET", url)
+
+ async def create_dataset_metadata(self, metadata_data: Dict[str, Any]):
+ """Create dataset metadata."""
+ url = f"/datasets/{self._get_dataset_id()}/metadata"
+ return await self._send_request("POST", url, json=metadata_data)
+
+ async def update_dataset_metadata(self, metadata_id: str, metadata_data: Dict[str, Any]):
+ """Update dataset metadata."""
+ url = f"/datasets/{self._get_dataset_id()}/metadata/{metadata_id}"
+ return await self._send_request("PATCH", url, json=metadata_data)
+
+ async def get_built_in_metadata(self):
+ """Get built-in metadata."""
+ url = f"/datasets/{self._get_dataset_id()}/metadata/built-in"
+ return await self._send_request("GET", url)
+
+ async def manage_built_in_metadata(self, action: str, metadata_data: Dict[str, Any] = None):
+ """Manage built-in metadata with specified action."""
+ data = metadata_data or {}
+ url = f"/datasets/{self._get_dataset_id()}/metadata/built-in/{action}"
+ return await self._send_request("POST", url, json=data)
+
+ async def update_documents_metadata(self, operation_data: List[Dict[str, Any]]):
+ """Update metadata for multiple documents."""
+ url = f"/datasets/{self._get_dataset_id()}/documents/metadata"
+ data = {"operation_data": operation_data}
+ return await self._send_request("POST", url, json=data)
+
+ # Dataset Tags APIs
+ async def list_dataset_tags(self):
+ """List all dataset tags."""
+ return await self._send_request("GET", "/datasets/tags")
+
+ async def bind_dataset_tags(self, tag_ids: List[str]):
+ """Bind tags to dataset."""
+ data = {"tag_ids": tag_ids, "target_id": self._get_dataset_id()}
+ return await self._send_request("POST", "/datasets/tags/binding", json=data)
+
+ async def unbind_dataset_tag(self, tag_id: str):
+ """Unbind a single tag from dataset."""
+ data = {"tag_id": tag_id, "target_id": self._get_dataset_id()}
+ return await self._send_request("POST", "/datasets/tags/unbinding", json=data)
+
+ async def get_dataset_tags(self):
+ """Get tags for current dataset."""
+ url = f"/datasets/{self._get_dataset_id()}/tags"
+ return await self._send_request("GET", url)
+
+ # RAG Pipeline APIs
+ async def get_datasource_plugins(self, is_published: bool = True):
+ """Get datasource plugins for RAG pipeline."""
+ params = {"is_published": is_published}
+ url = f"/datasets/{self._get_dataset_id()}/pipeline/datasource-plugins"
+ return await self._send_request("GET", url, params=params)
+
+ async def run_datasource_node(
+ self,
+ node_id: str,
+ inputs: Dict[str, Any],
+ datasource_type: str,
+ is_published: bool = True,
+ credential_id: str = None,
+ ):
+ """Run a datasource node in RAG pipeline."""
+ data = {
+ "inputs": inputs,
+ "datasource_type": datasource_type,
+ "is_published": is_published,
+ }
+ if credential_id:
+ data["credential_id"] = credential_id
+ url = f"/datasets/{self._get_dataset_id()}/pipeline/datasource/nodes/{node_id}/run"
+ return await self._send_request("POST", url, json=data, stream=True)
+
+ async def run_rag_pipeline(
+ self,
+ inputs: Dict[str, Any],
+ datasource_type: str,
+ datasource_info_list: List[Dict[str, Any]],
+ start_node_id: str,
+ is_published: bool = True,
+ response_mode: Literal["streaming", "blocking"] = "blocking",
+ ):
+ """Run RAG pipeline."""
+ data = {
+ "inputs": inputs,
+ "datasource_type": datasource_type,
+ "datasource_info_list": datasource_info_list,
+ "start_node_id": start_node_id,
+ "is_published": is_published,
+ "response_mode": response_mode,
+ }
+ url = f"/datasets/{self._get_dataset_id()}/pipeline/run"
+ return await self._send_request("POST", url, json=data, stream=response_mode == "streaming")
+
+ async def upload_pipeline_file(self, file_path: str):
+ """Upload file for RAG pipeline."""
+ async with aiofiles.open(file_path, "rb") as f:
+ files = {"file": (os.path.basename(file_path), f)}
+ return await self._send_request_with_files("POST", "/datasets/pipeline/file-upload", {}, files)
+
+ # Dataset Management APIs
+ async def get_dataset(self, dataset_id: str | None = None):
+ """Get detailed information about a specific dataset."""
+ ds_id = dataset_id or self._get_dataset_id()
+ url = f"/datasets/{ds_id}"
+ return await self._send_request("GET", url)
+
+ async def update_dataset(
+ self,
+ dataset_id: str | None = None,
+ name: str | None = None,
+ description: str | None = None,
+ indexing_technique: str | None = None,
+ embedding_model: str | None = None,
+ embedding_model_provider: str | None = None,
+ retrieval_model: Dict[str, Any] | None = None,
+ **kwargs,
+ ):
+ """Update dataset configuration.
+
+ Args:
+ dataset_id: Dataset ID (optional, uses current dataset_id if not provided)
+ name: New dataset name
+ description: New dataset description
+ indexing_technique: Indexing technique ('high_quality' or 'economy')
+ embedding_model: Embedding model name
+ embedding_model_provider: Embedding model provider
+ retrieval_model: Retrieval model configuration dict
+ **kwargs: Additional parameters to pass to the API
+
+ Returns:
+ Response from the API with updated dataset information
+ """
+ ds_id = dataset_id or self._get_dataset_id()
+ url = f"/datasets/{ds_id}"
+
+ payload = {
+ "name": name,
+ "description": description,
+ "indexing_technique": indexing_technique,
+ "embedding_model": embedding_model,
+ "embedding_model_provider": embedding_model_provider,
+ "retrieval_model": retrieval_model,
+ }
+
+ data = {k: v for k, v in payload.items() if v is not None}
+ data.update(kwargs)
+
+ return await self._send_request("PATCH", url, json=data)
+
+ async def batch_update_document_status(
+ self,
+ action: Literal["enable", "disable", "archive", "un_archive"],
+ document_ids: List[str],
+ dataset_id: str | None = None,
+ ):
+ """Batch update document status."""
+ ds_id = dataset_id or self._get_dataset_id()
+ url = f"/datasets/{ds_id}/documents/status/{action}"
+ data = {"document_ids": document_ids}
+ return await self._send_request("PATCH", url, json=data)
diff --git a/sdks/python-client/dify_client/client.py b/sdks/python-client/dify_client/client.py
index fb42e3773d..41c5abe16d 100644
--- a/sdks/python-client/dify_client/client.py
+++ b/sdks/python-client/dify_client/client.py
@@ -1,32 +1,114 @@
import json
-from typing import Literal, Union, Dict, List, Any, Optional, IO
+import os
+from typing import Literal, Dict, List, Any, IO
-import requests
+import httpx
class DifyClient:
- def __init__(self, api_key, base_url: str = "https://api.dify.ai/v1"):
+ """Synchronous Dify API client.
+
+ This client uses httpx.Client for efficient connection pooling and resource management.
+ It's recommended to use this client as a context manager:
+
+ Example:
+ with DifyClient(api_key="your-key") as client:
+ response = client.get_app_info()
+ """
+
+ def __init__(
+ self,
+ api_key: str,
+ base_url: str = "https://api.dify.ai/v1",
+ timeout: float = 60.0,
+ ):
+ """Initialize the Dify client.
+
+ Args:
+ api_key: Your Dify API key
+ base_url: Base URL for the Dify API
+ timeout: Request timeout in seconds (default: 60.0)
+ """
self.api_key = api_key
self.base_url = base_url
+ self._client = httpx.Client(
+ base_url=base_url,
+ timeout=httpx.Timeout(timeout, connect=5.0),
+ )
+
+ def __enter__(self):
+ """Support context manager protocol."""
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ """Clean up resources when exiting context."""
+ self.close()
+
+ def close(self):
+ """Close the HTTP client and release resources."""
+ if hasattr(self, "_client"):
+ self._client.close()
def _send_request(
- self, method: str, endpoint: str, json: dict | None = None, params: dict | None = None, stream: bool = False
+ self,
+ method: str,
+ endpoint: str,
+ json: dict | None = None,
+ params: dict | None = None,
+ stream: bool = False,
+ **kwargs,
):
+ """Send an HTTP request to the Dify API.
+
+ Args:
+ method: HTTP method (GET, POST, PUT, PATCH, DELETE)
+ endpoint: API endpoint path
+ json: JSON request body
+ params: Query parameters
+ stream: Whether to stream the response
+ **kwargs: Additional arguments to pass to httpx.request
+
+ Returns:
+ httpx.Response object
+ """
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
}
- url = f"{self.base_url}{endpoint}"
- response = requests.request(method, url, json=json, params=params, headers=headers, stream=stream)
+ # httpx.Client automatically prepends base_url
+ response = self._client.request(
+ method,
+ endpoint,
+ json=json,
+ params=params,
+ headers=headers,
+ **kwargs,
+ )
return response
- def _send_request_with_files(self, method, endpoint, data, files):
+ def _send_request_with_files(self, method: str, endpoint: str, data: dict, files: dict):
+ """Send an HTTP request with file uploads.
+
+ Args:
+ method: HTTP method (POST, PUT, etc.)
+ endpoint: API endpoint path
+ data: Form data
+ files: Files to upload
+
+ Returns:
+ httpx.Response object
+ """
headers = {"Authorization": f"Bearer {self.api_key}"}
- url = f"{self.base_url}{endpoint}"
- response = requests.request(method, url, data=data, headers=headers, files=files)
+ response = self._client.request(
+ method,
+ endpoint,
+ data=data,
+ headers=headers,
+ files=files,
+ )
return response
@@ -65,7 +147,11 @@ class DifyClient:
class CompletionClient(DifyClient):
def create_completion_message(
- self, inputs: dict, response_mode: Literal["blocking", "streaming"], user: str, files: dict | None = None
+ self,
+ inputs: dict,
+ response_mode: Literal["blocking", "streaming"],
+ user: str,
+ files: dict | None = None,
):
data = {
"inputs": inputs,
@@ -77,7 +163,7 @@ class CompletionClient(DifyClient):
"POST",
"/completion-messages",
data,
- stream=True if response_mode == "streaming" else False,
+ stream=(response_mode == "streaming"),
)
@@ -105,7 +191,7 @@ class ChatClient(DifyClient):
"POST",
"/chat-messages",
data,
- stream=True if response_mode == "streaming" else False,
+ stream=(response_mode == "streaming"),
)
def get_suggested(self, message_id: str, user: str):
@@ -166,10 +252,6 @@ class ChatClient(DifyClient):
embedding_model_name: str,
):
"""Enable or disable annotation reply feature."""
- # Backend API requires these fields to be non-None values
- if score_threshold is None or embedding_provider_name is None or embedding_model_name is None:
- raise ValueError("score_threshold, embedding_provider_name, and embedding_model_name cannot be None")
-
data = {
"score_threshold": score_threshold,
"embedding_provider_name": embedding_provider_name,
@@ -181,11 +263,9 @@ class ChatClient(DifyClient):
"""Get the status of an annotation reply action job."""
return self._send_request("GET", f"/apps/annotation-reply/{action}/status/{job_id}")
- def list_annotations(self, page: int = 1, limit: int = 20, keyword: str = ""):
+ def list_annotations(self, page: int = 1, limit: int = 20, keyword: str | None = None):
"""List annotations for the application."""
- params = {"page": page, "limit": limit}
- if keyword:
- params["keyword"] = keyword
+ params = {"page": page, "limit": limit, "keyword": keyword}
return self._send_request("GET", "/apps/annotations", params=params)
def create_annotation(self, question: str, answer: str):
@@ -202,9 +282,47 @@ class ChatClient(DifyClient):
"""Delete an annotation."""
return self._send_request("DELETE", f"/apps/annotations/{annotation_id}")
+ # Conversation Variables APIs
+ def get_conversation_variables(self, conversation_id: str, user: str):
+ """Get all variables for a specific conversation.
+
+ Args:
+ conversation_id: The conversation ID to query variables for
+ user: User identifier
+
+ Returns:
+ Response from the API containing:
+ - variables: List of conversation variables with their values
+ - conversation_id: The conversation ID
+ """
+ params = {"user": user}
+ url = f"/conversations/{conversation_id}/variables"
+ return self._send_request("GET", url, params=params)
+
+ def update_conversation_variable(self, conversation_id: str, variable_id: str, value: Any, user: str):
+ """Update a specific conversation variable.
+
+ Args:
+ conversation_id: The conversation ID
+ variable_id: The variable ID to update
+ value: New value for the variable
+ user: User identifier
+
+ Returns:
+ Response from the API with updated variable information
+ """
+ data = {"value": value, "user": user}
+ url = f"/conversations/{conversation_id}/variables/{variable_id}"
+ return self._send_request("PATCH", url, json=data)
+
class WorkflowClient(DifyClient):
- def run(self, inputs: dict, response_mode: Literal["blocking", "streaming"] = "streaming", user: str = "abc-123"):
+ def run(
+ self,
+ inputs: dict,
+ response_mode: Literal["blocking", "streaming"] = "streaming",
+ user: str = "abc-123",
+ ):
data = {"inputs": inputs, "response_mode": response_mode, "user": user}
return self._send_request("POST", "/workflows/run", data)
@@ -252,7 +370,10 @@ class WorkflowClient(DifyClient):
"""Run a specific workflow by workflow ID."""
data = {"inputs": inputs, "response_mode": response_mode, "user": user}
return self._send_request(
- "POST", f"/workflows/{workflow_id}/run", data, stream=True if response_mode == "streaming" else False
+ "POST",
+ f"/workflows/{workflow_id}/run",
+ data,
+ stream=(response_mode == "streaming"),
)
@@ -293,7 +414,7 @@ class KnowledgeBaseClient(DifyClient):
return self._send_request("POST", "/datasets", {"name": name}, **kwargs)
def list_datasets(self, page: int = 1, page_size: int = 20, **kwargs):
- return self._send_request("GET", f"/datasets?page={page}&limit={page_size}", **kwargs)
+ return self._send_request("GET", "/datasets", params={"page": page, "limit": page_size}, **kwargs)
def create_document_by_text(self, name, text, extra_params: dict | None = None, **kwargs):
"""
@@ -333,7 +454,12 @@ class KnowledgeBaseClient(DifyClient):
return self._send_request("POST", url, json=data, **kwargs)
def update_document_by_text(
- self, document_id: str, name: str, text: str, extra_params: dict | None = None, **kwargs
+ self,
+ document_id: str,
+ name: str,
+ text: str,
+ extra_params: dict | None = None,
+ **kwargs,
):
"""
Update a document by text.
@@ -368,7 +494,10 @@ class KnowledgeBaseClient(DifyClient):
return self._send_request("POST", url, json=data, **kwargs)
def create_document_by_file(
- self, file_path: str, original_document_id: str | None = None, extra_params: dict | None = None
+ self,
+ file_path: str,
+ original_document_id: str | None = None,
+ extra_params: dict | None = None,
):
"""
Create a document by file.
@@ -395,17 +524,18 @@ class KnowledgeBaseClient(DifyClient):
}
:return: Response from the API
"""
- files = {"file": open(file_path, "rb")}
- data = {
- "process_rule": {"mode": "automatic"},
- "indexing_technique": "high_quality",
- }
- if extra_params is not None and isinstance(extra_params, dict):
- data.update(extra_params)
- if original_document_id is not None:
- data["original_document_id"] = original_document_id
- url = f"/datasets/{self._get_dataset_id()}/document/create_by_file"
- return self._send_request_with_files("POST", url, {"data": json.dumps(data)}, files)
+ with open(file_path, "rb") as f:
+ files = {"file": (os.path.basename(file_path), f)}
+ data = {
+ "process_rule": {"mode": "automatic"},
+ "indexing_technique": "high_quality",
+ }
+ if extra_params is not None and isinstance(extra_params, dict):
+ data.update(extra_params)
+ if original_document_id is not None:
+ data["original_document_id"] = original_document_id
+ url = f"/datasets/{self._get_dataset_id()}/document/create_by_file"
+ return self._send_request_with_files("POST", url, {"data": json.dumps(data)}, files)
def update_document_by_file(self, document_id: str, file_path: str, extra_params: dict | None = None):
"""
@@ -433,12 +563,13 @@ class KnowledgeBaseClient(DifyClient):
}
:return:
"""
- files = {"file": open(file_path, "rb")}
- data = {}
- if extra_params is not None and isinstance(extra_params, dict):
- data.update(extra_params)
- url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/update_by_file"
- return self._send_request_with_files("POST", url, {"data": json.dumps(data)}, files)
+ with open(file_path, "rb") as f:
+ files = {"file": (os.path.basename(file_path), f)}
+ data = {}
+ if extra_params is not None and isinstance(extra_params, dict):
+ data.update(extra_params)
+ url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/update_by_file"
+ return self._send_request_with_files("POST", url, {"data": json.dumps(data)}, files)
def batch_indexing_status(self, batch_id: str, **kwargs):
"""
@@ -516,6 +647,8 @@ class KnowledgeBaseClient(DifyClient):
:param document_id: ID of the document
:param keyword: query keyword, optional
:param status: status of the segment, optional, e.g. completed
+ :param kwargs: Additional parameters to pass to the API.
+ Can include a 'params' dict for extra query parameters.
"""
url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/segments"
params = {}
@@ -524,7 +657,7 @@ class KnowledgeBaseClient(DifyClient):
if status is not None:
params["status"] = status
if "params" in kwargs:
- params.update(kwargs["params"])
+ params.update(kwargs.pop("params"))
return self._send_request("GET", url, params=params, **kwargs)
def delete_document_segment(self, document_id: str, segment_id: str):
@@ -553,7 +686,10 @@ class KnowledgeBaseClient(DifyClient):
# Advanced Knowledge Base APIs
def hit_testing(
- self, query: str, retrieval_model: Dict[str, Any] = None, external_retrieval_model: Dict[str, Any] = None
+ self,
+ query: str,
+ retrieval_model: Dict[str, Any] = None,
+ external_retrieval_model: Dict[str, Any] = None,
):
"""Perform hit testing on the dataset."""
data = {"query": query}
@@ -632,7 +768,11 @@ class KnowledgeBaseClient(DifyClient):
credential_id: str = None,
):
"""Run a datasource node in RAG pipeline."""
- data = {"inputs": inputs, "datasource_type": datasource_type, "is_published": is_published}
+ data = {
+ "inputs": inputs,
+ "datasource_type": datasource_type,
+ "is_published": is_published,
+ }
if credential_id:
data["credential_id"] = credential_id
url = f"/datasets/{self._get_dataset_id()}/pipeline/datasource/nodes/{node_id}/run"
@@ -662,5 +802,94 @@ class KnowledgeBaseClient(DifyClient):
def upload_pipeline_file(self, file_path: str):
"""Upload file for RAG pipeline."""
with open(file_path, "rb") as f:
- files = {"file": f}
+ files = {"file": (os.path.basename(file_path), f)}
return self._send_request_with_files("POST", "/datasets/pipeline/file-upload", {}, files)
+
+ # Dataset Management APIs
+ def get_dataset(self, dataset_id: str | None = None):
+ """Get detailed information about a specific dataset.
+
+ Args:
+ dataset_id: Dataset ID (optional, uses current dataset_id if not provided)
+
+ Returns:
+ Response from the API containing dataset details including:
+ - name, description, permission
+ - indexing_technique, embedding_model, embedding_model_provider
+ - retrieval_model configuration
+ - document_count, word_count, app_count
+ - created_at, updated_at
+ """
+ ds_id = dataset_id or self._get_dataset_id()
+ url = f"/datasets/{ds_id}"
+ return self._send_request("GET", url)
+
+ def update_dataset(
+ self,
+ dataset_id: str | None = None,
+ name: str | None = None,
+ description: str | None = None,
+ indexing_technique: str | None = None,
+ embedding_model: str | None = None,
+ embedding_model_provider: str | None = None,
+ retrieval_model: Dict[str, Any] | None = None,
+ **kwargs,
+ ):
+ """Update dataset configuration.
+
+ Args:
+ dataset_id: Dataset ID (optional, uses current dataset_id if not provided)
+ name: New dataset name
+ description: New dataset description
+ indexing_technique: Indexing technique ('high_quality' or 'economy')
+ embedding_model: Embedding model name
+ embedding_model_provider: Embedding model provider
+ retrieval_model: Retrieval model configuration dict
+ **kwargs: Additional parameters to pass to the API
+
+ Returns:
+ Response from the API with updated dataset information
+ """
+ ds_id = dataset_id or self._get_dataset_id()
+ url = f"/datasets/{ds_id}"
+
+ # Build data dictionary with all possible parameters
+ payload = {
+ "name": name,
+ "description": description,
+ "indexing_technique": indexing_technique,
+ "embedding_model": embedding_model,
+ "embedding_model_provider": embedding_model_provider,
+ "retrieval_model": retrieval_model,
+ }
+
+ # Filter out None values and merge with additional kwargs
+ data = {k: v for k, v in payload.items() if v is not None}
+ data.update(kwargs)
+
+ return self._send_request("PATCH", url, json=data)
+
+ def batch_update_document_status(
+ self,
+ action: Literal["enable", "disable", "archive", "un_archive"],
+ document_ids: List[str],
+ dataset_id: str | None = None,
+ ):
+ """Batch update document status (enable/disable/archive/unarchive).
+
+ Args:
+ action: Action to perform on documents
+ - 'enable': Enable documents for retrieval
+ - 'disable': Disable documents from retrieval
+ - 'archive': Archive documents
+ - 'un_archive': Unarchive documents
+ document_ids: List of document IDs to update
+ dataset_id: Dataset ID (optional, uses current dataset_id if not provided)
+
+ Returns:
+ Response from the API with operation result
+ """
+ ds_id = dataset_id or self._get_dataset_id()
+ url = f"/datasets/{ds_id}/documents/status/{action}"
+ data = {"document_ids": document_ids}
+ return self._send_request("PATCH", url, json=data)
diff --git a/sdks/python-client/pyproject.toml b/sdks/python-client/pyproject.toml
new file mode 100644
index 0000000000..db02cbd6e3
--- /dev/null
+++ b/sdks/python-client/pyproject.toml
@@ -0,0 +1,43 @@
+[project]
+name = "dify-client"
+version = "0.1.12"
+description = "A package for interacting with the Dify Service-API"
+readme = "README.md"
+requires-python = ">=3.10"
+dependencies = [
+ "httpx>=0.27.0",
+ "aiofiles>=23.0.0",
+]
+authors = [
+ {name = "Dify", email = "hello@dify.ai"}
+]
+license = {text = "MIT"}
+keywords = ["dify", "nlp", "ai", "language-processing"]
+classifiers = [
+ "Programming Language :: Python :: 3",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+]
+
+[project.urls]
+Homepage = "https://github.com/langgenius/dify"
+
+[project.optional-dependencies]
+dev = [
+ "pytest>=7.0.0",
+ "pytest-asyncio>=0.21.0",
+]
+
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[tool.hatch.build.targets.wheel]
+packages = ["dify_client"]
+
+[tool.pytest.ini_options]
+testpaths = ["tests"]
+python_files = ["test_*.py"]
+python_classes = ["Test*"]
+python_functions = ["test_*"]
+asyncio_mode = "auto"
diff --git a/sdks/python-client/setup.py b/sdks/python-client/setup.py
deleted file mode 100644
index a05f6410fb..0000000000
--- a/sdks/python-client/setup.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from setuptools import setup
-
-with open("README.md", encoding="utf-8") as fh:
- long_description = fh.read()
-
-setup(
- name="dify-client",
- version="0.1.12",
- author="Dify",
- author_email="hello@dify.ai",
- description="A package for interacting with the Dify Service-API",
- long_description=long_description,
- long_description_content_type="text/markdown",
- url="https://github.com/langgenius/dify",
- license="MIT",
- packages=["dify_client"],
- classifiers=[
- "Programming Language :: Python :: 3",
- "License :: OSI Approved :: MIT License",
- "Operating System :: OS Independent",
- ],
- python_requires=">=3.6",
- install_requires=["requests"],
- keywords="dify nlp ai language-processing",
- include_package_data=True,
-)
diff --git a/sdks/python-client/tests/test_async_client.py b/sdks/python-client/tests/test_async_client.py
new file mode 100644
index 0000000000..4f5001866f
--- /dev/null
+++ b/sdks/python-client/tests/test_async_client.py
@@ -0,0 +1,250 @@
+#!/usr/bin/env python3
+"""
+Test suite for async client implementation in the Python SDK.
+
+This test validates the async/await functionality using httpx.AsyncClient
+and ensures API parity with sync clients.
+"""
+
+import unittest
+from unittest.mock import Mock, patch, AsyncMock
+
+from dify_client.async_client import (
+ AsyncDifyClient,
+ AsyncChatClient,
+ AsyncCompletionClient,
+ AsyncWorkflowClient,
+ AsyncWorkspaceClient,
+ AsyncKnowledgeBaseClient,
+)
+
+
+class TestAsyncAPIParity(unittest.TestCase):
+ """Test that async clients have API parity with sync clients."""
+
+ def test_dify_client_api_parity(self):
+ """Test AsyncDifyClient has same methods as DifyClient."""
+ from dify_client import DifyClient
+
+ sync_methods = {name for name in dir(DifyClient) if not name.startswith("_")}
+ async_methods = {name for name in dir(AsyncDifyClient) if not name.startswith("_")}
+
+ # aclose is async-specific, close is sync-specific
+ sync_methods.discard("close")
+ async_methods.discard("aclose")
+
+ # Verify parity
+ self.assertEqual(sync_methods, async_methods, "API parity mismatch for DifyClient")
+
+ def test_chat_client_api_parity(self):
+ """Test AsyncChatClient has same methods as ChatClient."""
+ from dify_client import ChatClient
+
+ sync_methods = {name for name in dir(ChatClient) if not name.startswith("_")}
+ async_methods = {name for name in dir(AsyncChatClient) if not name.startswith("_")}
+
+ sync_methods.discard("close")
+ async_methods.discard("aclose")
+
+ self.assertEqual(sync_methods, async_methods, "API parity mismatch for ChatClient")
+
+ def test_completion_client_api_parity(self):
+ """Test AsyncCompletionClient has same methods as CompletionClient."""
+ from dify_client import CompletionClient
+
+ sync_methods = {name for name in dir(CompletionClient) if not name.startswith("_")}
+ async_methods = {name for name in dir(AsyncCompletionClient) if not name.startswith("_")}
+
+ sync_methods.discard("close")
+ async_methods.discard("aclose")
+
+ self.assertEqual(sync_methods, async_methods, "API parity mismatch for CompletionClient")
+
+ def test_workflow_client_api_parity(self):
+ """Test AsyncWorkflowClient has same methods as WorkflowClient."""
+ from dify_client import WorkflowClient
+
+ sync_methods = {name for name in dir(WorkflowClient) if not name.startswith("_")}
+ async_methods = {name for name in dir(AsyncWorkflowClient) if not name.startswith("_")}
+
+ sync_methods.discard("close")
+ async_methods.discard("aclose")
+
+ self.assertEqual(sync_methods, async_methods, "API parity mismatch for WorkflowClient")
+
+ def test_workspace_client_api_parity(self):
+ """Test AsyncWorkspaceClient has same methods as WorkspaceClient."""
+ from dify_client import WorkspaceClient
+
+ sync_methods = {name for name in dir(WorkspaceClient) if not name.startswith("_")}
+ async_methods = {name for name in dir(AsyncWorkspaceClient) if not name.startswith("_")}
+
+ sync_methods.discard("close")
+ async_methods.discard("aclose")
+
+ self.assertEqual(sync_methods, async_methods, "API parity mismatch for WorkspaceClient")
+
+ def test_knowledge_base_client_api_parity(self):
+ """Test AsyncKnowledgeBaseClient has same methods as KnowledgeBaseClient."""
+ from dify_client import KnowledgeBaseClient
+
+ sync_methods = {name for name in dir(KnowledgeBaseClient) if not name.startswith("_")}
+ async_methods = {name for name in dir(AsyncKnowledgeBaseClient) if not name.startswith("_")}
+
+ sync_methods.discard("close")
+ async_methods.discard("aclose")
+
+ self.assertEqual(sync_methods, async_methods, "API parity mismatch for KnowledgeBaseClient")
+
+
+class TestAsyncClientMocked(unittest.IsolatedAsyncioTestCase):
+ """Test async client with mocked httpx.AsyncClient."""
+
+ @patch("dify_client.async_client.httpx.AsyncClient")
+ async def test_async_client_initialization(self, mock_httpx_async_client):
+ """Test async client initializes with httpx.AsyncClient."""
+ mock_client_instance = AsyncMock()
+ mock_httpx_async_client.return_value = mock_client_instance
+
+ client = AsyncDifyClient("test-key", "https://api.dify.ai/v1")
+
+ # Verify httpx.AsyncClient was called
+ mock_httpx_async_client.assert_called_once()
+ self.assertEqual(client.api_key, "test-key")
+
+ await client.aclose()
+
+ @patch("dify_client.async_client.httpx.AsyncClient")
+ async def test_async_context_manager(self, mock_httpx_async_client):
+ """Test async context manager works."""
+ mock_client_instance = AsyncMock()
+ mock_httpx_async_client.return_value = mock_client_instance
+
+ async with AsyncDifyClient("test-key") as client:
+ self.assertEqual(client.api_key, "test-key")
+
+ # Verify aclose was called
+ mock_client_instance.aclose.assert_called_once()
+
+ @patch("dify_client.async_client.httpx.AsyncClient")
+ async def test_async_send_request(self, mock_httpx_async_client):
+ """Test async _send_request method."""
+ mock_response = AsyncMock()
+ mock_response.json = AsyncMock(return_value={"result": "success"})
+ mock_response.status_code = 200
+
+ mock_client_instance = AsyncMock()
+ mock_client_instance.request = AsyncMock(return_value=mock_response)
+ mock_httpx_async_client.return_value = mock_client_instance
+
+ async with AsyncDifyClient("test-key") as client:
+ response = await client._send_request("GET", "/test")
+
+ # Verify request was called
+ mock_client_instance.request.assert_called_once()
+ call_args = mock_client_instance.request.call_args
+
+ # Verify parameters
+ self.assertEqual(call_args[0][0], "GET")
+ self.assertEqual(call_args[0][1], "/test")
+
+ @patch("dify_client.async_client.httpx.AsyncClient")
+ async def test_async_chat_client(self, mock_httpx_async_client):
+ """Test AsyncChatClient functionality."""
+ mock_response = AsyncMock()
+ mock_response.text = '{"answer": "Hello!"}'
+ mock_response.json = AsyncMock(return_value={"answer": "Hello!"})
+
+ mock_client_instance = AsyncMock()
+ mock_client_instance.request = AsyncMock(return_value=mock_response)
+ mock_httpx_async_client.return_value = mock_client_instance
+
+ async with AsyncChatClient("test-key") as client:
+ response = await client.create_chat_message({}, "Hi", "user123")
+ self.assertIn("answer", response.text)
+
+ @patch("dify_client.async_client.httpx.AsyncClient")
+ async def test_async_completion_client(self, mock_httpx_async_client):
+ """Test AsyncCompletionClient functionality."""
+ mock_response = AsyncMock()
+ mock_response.text = '{"answer": "Response"}'
+ mock_response.json = AsyncMock(return_value={"answer": "Response"})
+
+ mock_client_instance = AsyncMock()
+ mock_client_instance.request = AsyncMock(return_value=mock_response)
+ mock_httpx_async_client.return_value = mock_client_instance
+
+ async with AsyncCompletionClient("test-key") as client:
+ response = await client.create_completion_message({"query": "test"}, "blocking", "user123")
+ self.assertIn("answer", response.text)
+
+ @patch("dify_client.async_client.httpx.AsyncClient")
+ async def test_async_workflow_client(self, mock_httpx_async_client):
+ """Test AsyncWorkflowClient functionality."""
+ mock_response = AsyncMock()
+ mock_response.json = AsyncMock(return_value={"result": "success"})
+
+ mock_client_instance = AsyncMock()
+ mock_client_instance.request = AsyncMock(return_value=mock_response)
+ mock_httpx_async_client.return_value = mock_client_instance
+
+ async with AsyncWorkflowClient("test-key") as client:
+ response = await client.run({"input": "test"}, "blocking", "user123")
+ data = await response.json()
+ self.assertEqual(data["result"], "success")
+
+ @patch("dify_client.async_client.httpx.AsyncClient")
+ async def test_async_workspace_client(self, mock_httpx_async_client):
+ """Test AsyncWorkspaceClient functionality."""
+ mock_response = AsyncMock()
+ mock_response.json = AsyncMock(return_value={"data": []})
+
+ mock_client_instance = AsyncMock()
+ mock_client_instance.request = AsyncMock(return_value=mock_response)
+ mock_httpx_async_client.return_value = mock_client_instance
+
+ async with AsyncWorkspaceClient("test-key") as client:
+ response = await client.get_available_models("llm")
+ data = await response.json()
+ self.assertIn("data", data)
+
+ @patch("dify_client.async_client.httpx.AsyncClient")
+ async def test_async_knowledge_base_client(self, mock_httpx_async_client):
+ """Test AsyncKnowledgeBaseClient functionality."""
+ mock_response = AsyncMock()
+ mock_response.json = AsyncMock(return_value={"data": [], "total": 0})
+
+ mock_client_instance = AsyncMock()
+ mock_client_instance.request = AsyncMock(return_value=mock_response)
+ mock_httpx_async_client.return_value = mock_client_instance
+
+ async with AsyncKnowledgeBaseClient("test-key") as client:
+ response = await client.list_datasets()
+ data = await response.json()
+ self.assertIn("data", data)
+
+ @patch("dify_client.async_client.httpx.AsyncClient")
+ async def test_all_async_client_classes(self, mock_httpx_async_client):
+ """Test all async client classes work with httpx.AsyncClient."""
+ mock_client_instance = AsyncMock()
+ mock_httpx_async_client.return_value = mock_client_instance
+
+ clients = [
+ AsyncDifyClient("key"),
+ AsyncChatClient("key"),
+ AsyncCompletionClient("key"),
+ AsyncWorkflowClient("key"),
+ AsyncWorkspaceClient("key"),
+ AsyncKnowledgeBaseClient("key"),
+ ]
+
+ # Verify httpx.AsyncClient was called for each
+ self.assertEqual(mock_httpx_async_client.call_count, 6)
+
+ # Clean up
+ for client in clients:
+ await client.aclose()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/sdks/python-client/tests/test_httpx_migration.py b/sdks/python-client/tests/test_httpx_migration.py
new file mode 100644
index 0000000000..b8e434d7ec
--- /dev/null
+++ b/sdks/python-client/tests/test_httpx_migration.py
@@ -0,0 +1,331 @@
+#!/usr/bin/env python3
+"""
+Test suite for httpx migration in the Python SDK.
+
+This test validates that the migration from requests to httpx maintains
+backward compatibility and proper resource management.
+"""
+
+import unittest
+from unittest.mock import Mock, patch
+
+from dify_client import (
+ DifyClient,
+ ChatClient,
+ CompletionClient,
+ WorkflowClient,
+ WorkspaceClient,
+ KnowledgeBaseClient,
+)
+
+
+class TestHttpxMigrationMocked(unittest.TestCase):
+ """Test cases for httpx migration with mocked requests."""
+
+ def setUp(self):
+ """Set up test fixtures."""
+ self.api_key = "test-api-key"
+ self.base_url = "https://api.dify.ai/v1"
+
+ @patch("dify_client.client.httpx.Client")
+ def test_client_initialization(self, mock_httpx_client):
+ """Test that client initializes with httpx.Client."""
+ mock_client_instance = Mock()
+ mock_httpx_client.return_value = mock_client_instance
+
+ client = DifyClient(self.api_key, self.base_url)
+
+ # Verify httpx.Client was called with correct parameters
+ mock_httpx_client.assert_called_once()
+ call_kwargs = mock_httpx_client.call_args[1]
+ self.assertEqual(call_kwargs["base_url"], self.base_url)
+
+ # Verify client properties
+ self.assertEqual(client.api_key, self.api_key)
+ self.assertEqual(client.base_url, self.base_url)
+
+ client.close()
+
+ @patch("dify_client.client.httpx.Client")
+ def test_context_manager_support(self, mock_httpx_client):
+ """Test that client works as context manager."""
+ mock_client_instance = Mock()
+ mock_httpx_client.return_value = mock_client_instance
+
+ with DifyClient(self.api_key, self.base_url) as client:
+ self.assertEqual(client.api_key, self.api_key)
+
+ # Verify close was called
+ mock_client_instance.close.assert_called_once()
+
+ @patch("dify_client.client.httpx.Client")
+ def test_manual_close(self, mock_httpx_client):
+ """Test manual close() method."""
+ mock_client_instance = Mock()
+ mock_httpx_client.return_value = mock_client_instance
+
+ client = DifyClient(self.api_key, self.base_url)
+ client.close()
+
+ # Verify close was called
+ mock_client_instance.close.assert_called_once()
+
+ @patch("dify_client.client.httpx.Client")
+ def test_send_request_httpx_compatibility(self, mock_httpx_client):
+ """Test _send_request uses httpx.Client.request properly."""
+ mock_response = Mock()
+ mock_response.json.return_value = {"result": "success"}
+ mock_response.status_code = 200
+
+ mock_client_instance = Mock()
+ mock_client_instance.request.return_value = mock_response
+ mock_httpx_client.return_value = mock_client_instance
+
+ client = DifyClient(self.api_key, self.base_url)
+ response = client._send_request("GET", "/test-endpoint")
+
+ # Verify httpx.Client.request was called correctly
+ mock_client_instance.request.assert_called_once()
+ call_args = mock_client_instance.request.call_args
+
+ # Verify method and endpoint
+ self.assertEqual(call_args[0][0], "GET")
+ self.assertEqual(call_args[0][1], "/test-endpoint")
+
+ # Verify headers contain authorization
+ headers = call_args[1]["headers"]
+ self.assertEqual(headers["Authorization"], f"Bearer {self.api_key}")
+ self.assertEqual(headers["Content-Type"], "application/json")
+
+ client.close()
+
+ @patch("dify_client.client.httpx.Client")
+ def test_response_compatibility(self, mock_httpx_client):
+ """Test httpx.Response is compatible with requests.Response API."""
+ mock_response = Mock()
+ mock_response.json.return_value = {"key": "value"}
+ mock_response.text = '{"key": "value"}'
+ mock_response.content = b'{"key": "value"}'
+ mock_response.status_code = 200
+ mock_response.headers = {"Content-Type": "application/json"}
+
+ mock_client_instance = Mock()
+ mock_client_instance.request.return_value = mock_response
+ mock_httpx_client.return_value = mock_client_instance
+
+ client = DifyClient(self.api_key, self.base_url)
+ response = client._send_request("GET", "/test")
+
+ # Verify all common response methods work
+ self.assertEqual(response.json(), {"key": "value"})
+ self.assertEqual(response.text, '{"key": "value"}')
+ self.assertEqual(response.content, b'{"key": "value"}')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.headers["Content-Type"], "application/json")
+
+ client.close()
+
+ @patch("dify_client.client.httpx.Client")
+ def test_all_client_classes_use_httpx(self, mock_httpx_client):
+ """Test that all client classes properly use httpx."""
+ mock_client_instance = Mock()
+ mock_httpx_client.return_value = mock_client_instance
+
+ clients = [
+ DifyClient(self.api_key, self.base_url),
+ ChatClient(self.api_key, self.base_url),
+ CompletionClient(self.api_key, self.base_url),
+ WorkflowClient(self.api_key, self.base_url),
+ WorkspaceClient(self.api_key, self.base_url),
+ KnowledgeBaseClient(self.api_key, self.base_url),
+ ]
+
+ # Verify httpx.Client was called for each client
+ self.assertEqual(mock_httpx_client.call_count, 6)
+
+ # Clean up
+ for client in clients:
+ client.close()
+
+ @patch("dify_client.client.httpx.Client")
+ def test_json_parameter_handling(self, mock_httpx_client):
+ """Test that json parameter is passed correctly."""
+ mock_response = Mock()
+ mock_response.json.return_value = {"result": "success"}
+
+ mock_client_instance = Mock()
+ mock_client_instance.request.return_value = mock_response
+ mock_httpx_client.return_value = mock_client_instance
+
+ client = DifyClient(self.api_key, self.base_url)
+ test_data = {"key": "value", "number": 123}
+
+ client._send_request("POST", "/test", json=test_data)
+
+ # Verify json parameter was passed
+ call_args = mock_client_instance.request.call_args
+ self.assertEqual(call_args[1]["json"], test_data)
+
+ client.close()
+
+ @patch("dify_client.client.httpx.Client")
+ def test_params_parameter_handling(self, mock_httpx_client):
+ """Test that params parameter is passed correctly."""
+ mock_response = Mock()
+ mock_response.json.return_value = {"result": "success"}
+
+ mock_client_instance = Mock()
+ mock_client_instance.request.return_value = mock_response
+ mock_httpx_client.return_value = mock_client_instance
+
+ client = DifyClient(self.api_key, self.base_url)
+ test_params = {"page": 1, "limit": 20}
+
+ client._send_request("GET", "/test", params=test_params)
+
+ # Verify params parameter was passed
+ call_args = mock_client_instance.request.call_args
+ self.assertEqual(call_args[1]["params"], test_params)
+
+ client.close()
+
+ @patch("dify_client.client.httpx.Client")
+ def test_inheritance_chain(self, mock_httpx_client):
+ """Test that inheritance chain is maintained."""
+ mock_client_instance = Mock()
+ mock_httpx_client.return_value = mock_client_instance
+
+ # ChatClient inherits from DifyClient
+ chat_client = ChatClient(self.api_key, self.base_url)
+ self.assertIsInstance(chat_client, DifyClient)
+
+ # CompletionClient inherits from DifyClient
+ completion_client = CompletionClient(self.api_key, self.base_url)
+ self.assertIsInstance(completion_client, DifyClient)
+
+ # WorkflowClient inherits from DifyClient
+ workflow_client = WorkflowClient(self.api_key, self.base_url)
+ self.assertIsInstance(workflow_client, DifyClient)
+
+ # Clean up
+ chat_client.close()
+ completion_client.close()
+ workflow_client.close()
+
+ @patch("dify_client.client.httpx.Client")
+ def test_nested_context_managers(self, mock_httpx_client):
+ """Test nested context managers work correctly."""
+ mock_client_instance = Mock()
+ mock_httpx_client.return_value = mock_client_instance
+
+ with DifyClient(self.api_key, self.base_url) as client1:
+ with ChatClient(self.api_key, self.base_url) as client2:
+ self.assertEqual(client1.api_key, self.api_key)
+ self.assertEqual(client2.api_key, self.api_key)
+
+ # Both close methods should have been called
+ self.assertEqual(mock_client_instance.close.call_count, 2)
+
+
+class TestChatClientHttpx(unittest.TestCase):
+ """Test ChatClient specific httpx integration."""
+
+ @patch("dify_client.client.httpx.Client")
+ def test_create_chat_message_httpx(self, mock_httpx_client):
+ """Test create_chat_message works with httpx."""
+ mock_response = Mock()
+ mock_response.text = '{"answer": "Hello!"}'
+ mock_response.json.return_value = {"answer": "Hello!"}
+ mock_response.status_code = 200
+
+ mock_client_instance = Mock()
+ mock_client_instance.request.return_value = mock_response
+ mock_httpx_client.return_value = mock_client_instance
+
+ with ChatClient("test-key") as client:
+ response = client.create_chat_message({}, "Hi", "user123")
+ self.assertIn("answer", response.text)
+ self.assertEqual(response.json()["answer"], "Hello!")
+
+
+class TestCompletionClientHttpx(unittest.TestCase):
+ """Test CompletionClient specific httpx integration."""
+
+ @patch("dify_client.client.httpx.Client")
+ def test_create_completion_message_httpx(self, mock_httpx_client):
+ """Test create_completion_message works with httpx."""
+ mock_response = Mock()
+ mock_response.text = '{"answer": "Response"}'
+ mock_response.json.return_value = {"answer": "Response"}
+ mock_response.status_code = 200
+
+ mock_client_instance = Mock()
+ mock_client_instance.request.return_value = mock_response
+ mock_httpx_client.return_value = mock_client_instance
+
+ with CompletionClient("test-key") as client:
+ response = client.create_completion_message({"query": "test"}, "blocking", "user123")
+ self.assertIn("answer", response.text)
+
+
+class TestKnowledgeBaseClientHttpx(unittest.TestCase):
+ """Test KnowledgeBaseClient specific httpx integration."""
+
+ @patch("dify_client.client.httpx.Client")
+ def test_list_datasets_httpx(self, mock_httpx_client):
+ """Test list_datasets works with httpx."""
+ mock_response = Mock()
+ mock_response.json.return_value = {"data": [], "total": 0}
+ mock_response.status_code = 200
+
+ mock_client_instance = Mock()
+ mock_client_instance.request.return_value = mock_response
+ mock_httpx_client.return_value = mock_client_instance
+
+ with KnowledgeBaseClient("test-key") as client:
+ response = client.list_datasets()
+ data = response.json()
+ self.assertIn("data", data)
+ self.assertIn("total", data)
+
+
+class TestWorkflowClientHttpx(unittest.TestCase):
+ """Test WorkflowClient specific httpx integration."""
+
+ @patch("dify_client.client.httpx.Client")
+ def test_run_workflow_httpx(self, mock_httpx_client):
+ """Test run workflow works with httpx."""
+ mock_response = Mock()
+ mock_response.json.return_value = {"result": "success"}
+ mock_response.status_code = 200
+
+ mock_client_instance = Mock()
+ mock_client_instance.request.return_value = mock_response
+ mock_httpx_client.return_value = mock_client_instance
+
+ with WorkflowClient("test-key") as client:
+ response = client.run({"input": "test"}, "blocking", "user123")
+ self.assertEqual(response.json()["result"], "success")
+
+
+class TestWorkspaceClientHttpx(unittest.TestCase):
+ """Test WorkspaceClient specific httpx integration."""
+
+ @patch("dify_client.client.httpx.Client")
+ def test_get_available_models_httpx(self, mock_httpx_client):
+ """Test get_available_models works with httpx."""
+ mock_response = Mock()
+ mock_response.json.return_value = {"data": []}
+ mock_response.status_code = 200
+
+ mock_client_instance = Mock()
+ mock_client_instance.request.return_value = mock_response
+ mock_httpx_client.return_value = mock_client_instance
+
+ with WorkspaceClient("test-key") as client:
+ response = client.get_available_models("llm")
+ self.assertIn("data", response.json())
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/sdks/python-client/tests/test_new_apis.py b/sdks/python-client/tests/test_new_apis.py
deleted file mode 100644
index 09c62dfda7..0000000000
--- a/sdks/python-client/tests/test_new_apis.py
+++ /dev/null
@@ -1,416 +0,0 @@
-#!/usr/bin/env python3
-"""
-Test suite for the new Service API functionality in the Python SDK.
-
-This test validates the implementation of the missing Service API endpoints
-that were added to the Python SDK to achieve complete coverage.
-"""
-
-import unittest
-from unittest.mock import Mock, patch, MagicMock
-import json
-
-from dify_client import (
- DifyClient,
- ChatClient,
- WorkflowClient,
- KnowledgeBaseClient,
- WorkspaceClient,
-)
-
-
-class TestNewServiceAPIs(unittest.TestCase):
- """Test cases for new Service API implementations."""
-
- def setUp(self):
- """Set up test fixtures."""
- self.api_key = "test-api-key"
- self.base_url = "https://api.dify.ai/v1"
-
- @patch("dify_client.client.requests.request")
- def test_app_info_apis(self, mock_request):
- """Test application info APIs."""
- mock_response = Mock()
- mock_response.json.return_value = {
- "name": "Test App",
- "description": "Test Description",
- "tags": ["test", "api"],
- "mode": "chat",
- "author_name": "Test Author",
- }
- mock_request.return_value = mock_response
-
- client = DifyClient(self.api_key, self.base_url)
-
- # Test get_app_info
- result = client.get_app_info()
- mock_request.assert_called_with(
- "GET",
- f"{self.base_url}/info",
- json=None,
- params=None,
- headers={
- "Authorization": f"Bearer {self.api_key}",
- "Content-Type": "application/json",
- },
- stream=False,
- )
-
- # Test get_app_site_info
- client.get_app_site_info()
- mock_request.assert_called_with(
- "GET",
- f"{self.base_url}/site",
- json=None,
- params=None,
- headers={
- "Authorization": f"Bearer {self.api_key}",
- "Content-Type": "application/json",
- },
- stream=False,
- )
-
- # Test get_file_preview
- file_id = "test-file-id"
- client.get_file_preview(file_id)
- mock_request.assert_called_with(
- "GET",
- f"{self.base_url}/files/{file_id}/preview",
- json=None,
- params=None,
- headers={
- "Authorization": f"Bearer {self.api_key}",
- "Content-Type": "application/json",
- },
- stream=False,
- )
-
- @patch("dify_client.client.requests.request")
- def test_annotation_apis(self, mock_request):
- """Test annotation APIs."""
- mock_response = Mock()
- mock_response.json.return_value = {"result": "success"}
- mock_request.return_value = mock_response
-
- client = ChatClient(self.api_key, self.base_url)
-
- # Test annotation_reply_action - enable
- client.annotation_reply_action(
- action="enable",
- score_threshold=0.8,
- embedding_provider_name="openai",
- embedding_model_name="text-embedding-ada-002",
- )
- mock_request.assert_called_with(
- "POST",
- f"{self.base_url}/apps/annotation-reply/enable",
- json={
- "score_threshold": 0.8,
- "embedding_provider_name": "openai",
- "embedding_model_name": "text-embedding-ada-002",
- },
- params=None,
- headers={
- "Authorization": f"Bearer {self.api_key}",
- "Content-Type": "application/json",
- },
- stream=False,
- )
-
- # Test annotation_reply_action - disable (now requires same fields as enable)
- client.annotation_reply_action(
- action="disable",
- score_threshold=0.5,
- embedding_provider_name="openai",
- embedding_model_name="text-embedding-ada-002",
- )
-
- # Test annotation_reply_action with score_threshold=0 (edge case)
- client.annotation_reply_action(
- action="enable",
- score_threshold=0.0, # This should work and not raise ValueError
- embedding_provider_name="openai",
- embedding_model_name="text-embedding-ada-002",
- )
-
- # Test get_annotation_reply_status
- client.get_annotation_reply_status("enable", "job-123")
-
- # Test list_annotations
- client.list_annotations(page=1, limit=20, keyword="test")
-
- # Test create_annotation
- client.create_annotation("Test question?", "Test answer.")
-
- # Test update_annotation
- client.update_annotation("annotation-123", "Updated question?", "Updated answer.")
-
- # Test delete_annotation
- client.delete_annotation("annotation-123")
-
- # Verify all calls were made (8 calls: enable + disable + enable with 0.0 + 5 other operations)
- self.assertEqual(mock_request.call_count, 8)
-
- @patch("dify_client.client.requests.request")
- def test_knowledge_base_advanced_apis(self, mock_request):
- """Test advanced knowledge base APIs."""
- mock_response = Mock()
- mock_response.json.return_value = {"result": "success"}
- mock_request.return_value = mock_response
-
- dataset_id = "test-dataset-id"
- client = KnowledgeBaseClient(self.api_key, self.base_url, dataset_id)
-
- # Test hit_testing
- client.hit_testing("test query", {"type": "vector"})
- mock_request.assert_called_with(
- "POST",
- f"{self.base_url}/datasets/{dataset_id}/hit-testing",
- json={"query": "test query", "retrieval_model": {"type": "vector"}},
- params=None,
- headers={
- "Authorization": f"Bearer {self.api_key}",
- "Content-Type": "application/json",
- },
- stream=False,
- )
-
- # Test metadata operations
- client.get_dataset_metadata()
- client.create_dataset_metadata({"key": "value"})
- client.update_dataset_metadata("meta-123", {"key": "new_value"})
- client.get_built_in_metadata()
- client.manage_built_in_metadata("enable", {"type": "built_in"})
- client.update_documents_metadata([{"document_id": "doc1", "metadata": {"key": "value"}}])
-
- # Test tag operations
- client.list_dataset_tags()
- client.bind_dataset_tags(["tag1", "tag2"])
- client.unbind_dataset_tag("tag1")
- client.get_dataset_tags()
-
- # Verify multiple calls were made
- self.assertGreater(mock_request.call_count, 5)
-
- @patch("dify_client.client.requests.request")
- def test_rag_pipeline_apis(self, mock_request):
- """Test RAG pipeline APIs."""
- mock_response = Mock()
- mock_response.json.return_value = {"result": "success"}
- mock_request.return_value = mock_response
-
- dataset_id = "test-dataset-id"
- client = KnowledgeBaseClient(self.api_key, self.base_url, dataset_id)
-
- # Test get_datasource_plugins
- client.get_datasource_plugins(is_published=True)
- mock_request.assert_called_with(
- "GET",
- f"{self.base_url}/datasets/{dataset_id}/pipeline/datasource-plugins",
- json=None,
- params={"is_published": True},
- headers={
- "Authorization": f"Bearer {self.api_key}",
- "Content-Type": "application/json",
- },
- stream=False,
- )
-
- # Test run_datasource_node
- client.run_datasource_node(
- node_id="node-123",
- inputs={"param": "value"},
- datasource_type="online_document",
- is_published=True,
- credential_id="cred-123",
- )
-
- # Test run_rag_pipeline with blocking mode
- client.run_rag_pipeline(
- inputs={"query": "test"},
- datasource_type="online_document",
- datasource_info_list=[{"id": "ds1"}],
- start_node_id="start-node",
- is_published=True,
- response_mode="blocking",
- )
-
- # Test run_rag_pipeline with streaming mode
- client.run_rag_pipeline(
- inputs={"query": "test"},
- datasource_type="online_document",
- datasource_info_list=[{"id": "ds1"}],
- start_node_id="start-node",
- is_published=True,
- response_mode="streaming",
- )
-
- self.assertEqual(mock_request.call_count, 4)
-
- @patch("dify_client.client.requests.request")
- def test_workspace_apis(self, mock_request):
- """Test workspace APIs."""
- mock_response = Mock()
- mock_response.json.return_value = {
- "data": [{"name": "gpt-3.5-turbo", "type": "llm"}, {"name": "gpt-4", "type": "llm"}]
- }
- mock_request.return_value = mock_response
-
- client = WorkspaceClient(self.api_key, self.base_url)
-
- # Test get_available_models
- result = client.get_available_models("llm")
- mock_request.assert_called_with(
- "GET",
- f"{self.base_url}/workspaces/current/models/model-types/llm",
- json=None,
- params=None,
- headers={
- "Authorization": f"Bearer {self.api_key}",
- "Content-Type": "application/json",
- },
- stream=False,
- )
-
- @patch("dify_client.client.requests.request")
- def test_workflow_advanced_apis(self, mock_request):
- """Test advanced workflow APIs."""
- mock_response = Mock()
- mock_response.json.return_value = {"result": "success"}
- mock_request.return_value = mock_response
-
- client = WorkflowClient(self.api_key, self.base_url)
-
- # Test get_workflow_logs
- client.get_workflow_logs(keyword="test", status="succeeded", page=1, limit=20)
- mock_request.assert_called_with(
- "GET",
- f"{self.base_url}/workflows/logs",
- json=None,
- params={"page": 1, "limit": 20, "keyword": "test", "status": "succeeded"},
- headers={
- "Authorization": f"Bearer {self.api_key}",
- "Content-Type": "application/json",
- },
- stream=False,
- )
-
- # Test get_workflow_logs with additional filters
- client.get_workflow_logs(
- keyword="test",
- status="succeeded",
- page=1,
- limit=20,
- created_at__before="2024-01-01",
- created_at__after="2023-01-01",
- created_by_account="user123",
- )
-
- # Test run_specific_workflow
- client.run_specific_workflow(
- workflow_id="workflow-123", inputs={"param": "value"}, response_mode="streaming", user="user-123"
- )
-
- self.assertEqual(mock_request.call_count, 3)
-
- def test_error_handling(self):
- """Test error handling for required parameters."""
- client = ChatClient(self.api_key, self.base_url)
-
- # Test annotation_reply_action with missing required parameters would be a TypeError now
- # since parameters are required in method signature
- with self.assertRaises(TypeError):
- client.annotation_reply_action("enable")
-
- # Test annotation_reply_action with explicit None values should raise ValueError
- with self.assertRaises(ValueError) as context:
- client.annotation_reply_action("enable", None, "provider", "model")
-
- self.assertIn("cannot be None", str(context.exception))
-
- # Test KnowledgeBaseClient without dataset_id
- kb_client = KnowledgeBaseClient(self.api_key, self.base_url)
- with self.assertRaises(ValueError) as context:
- kb_client.hit_testing("test query")
-
- self.assertIn("dataset_id is not set", str(context.exception))
-
- @patch("dify_client.client.open")
- @patch("dify_client.client.requests.request")
- def test_file_upload_apis(self, mock_request, mock_open):
- """Test file upload APIs."""
- mock_response = Mock()
- mock_response.json.return_value = {"result": "success"}
- mock_request.return_value = mock_response
-
- mock_file = MagicMock()
- mock_open.return_value.__enter__.return_value = mock_file
-
- dataset_id = "test-dataset-id"
- client = KnowledgeBaseClient(self.api_key, self.base_url, dataset_id)
-
- # Test upload_pipeline_file
- client.upload_pipeline_file("/path/to/test.pdf")
-
- mock_open.assert_called_with("/path/to/test.pdf", "rb")
- mock_request.assert_called_once()
-
- def test_comprehensive_coverage(self):
- """Test that all previously missing APIs are now implemented."""
-
- # Test DifyClient methods
- dify_methods = ["get_app_info", "get_app_site_info", "get_file_preview"]
- client = DifyClient(self.api_key)
- for method in dify_methods:
- self.assertTrue(hasattr(client, method), f"DifyClient missing method: {method}")
-
- # Test ChatClient annotation methods
- chat_methods = [
- "annotation_reply_action",
- "get_annotation_reply_status",
- "list_annotations",
- "create_annotation",
- "update_annotation",
- "delete_annotation",
- ]
- chat_client = ChatClient(self.api_key)
- for method in chat_methods:
- self.assertTrue(hasattr(chat_client, method), f"ChatClient missing method: {method}")
-
- # Test WorkflowClient advanced methods
- workflow_methods = ["get_workflow_logs", "run_specific_workflow"]
- workflow_client = WorkflowClient(self.api_key)
- for method in workflow_methods:
- self.assertTrue(hasattr(workflow_client, method), f"WorkflowClient missing method: {method}")
-
- # Test KnowledgeBaseClient advanced methods
- kb_methods = [
- "hit_testing",
- "get_dataset_metadata",
- "create_dataset_metadata",
- "update_dataset_metadata",
- "get_built_in_metadata",
- "manage_built_in_metadata",
- "update_documents_metadata",
- "list_dataset_tags",
- "bind_dataset_tags",
- "unbind_dataset_tag",
- "get_dataset_tags",
- "get_datasource_plugins",
- "run_datasource_node",
- "run_rag_pipeline",
- "upload_pipeline_file",
- ]
- kb_client = KnowledgeBaseClient(self.api_key)
- for method in kb_methods:
- self.assertTrue(hasattr(kb_client, method), f"KnowledgeBaseClient missing method: {method}")
-
- # Test WorkspaceClient methods
- workspace_methods = ["get_available_models"]
- workspace_client = WorkspaceClient(self.api_key)
- for method in workspace_methods:
- self.assertTrue(hasattr(workspace_client, method), f"WorkspaceClient missing method: {method}")
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/sdks/python-client/uv.lock b/sdks/python-client/uv.lock
new file mode 100644
index 0000000000..19f348289b
--- /dev/null
+++ b/sdks/python-client/uv.lock
@@ -0,0 +1,271 @@
+version = 1
+revision = 3
+requires-python = ">=3.10"
+
+[[package]]
+name = "aiofiles"
+version = "25.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/41/c3/534eac40372d8ee36ef40df62ec129bee4fdb5ad9706e58a29be53b2c970/aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2", size = 46354, upload-time = "2025-10-09T20:51:04.358Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/bc/8a/340a1555ae33d7354dbca4faa54948d76d89a27ceef032c8c3bc661d003e/aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695", size = 14668, upload-time = "2025-10-09T20:51:03.174Z" },
+]
+
+[[package]]
+name = "anyio"
+version = "4.11.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
+ { name = "idna" },
+ { name = "sniffio" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" },
+]
+
+[[package]]
+name = "backports-asyncio-runner"
+version = "1.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" },
+]
+
+[[package]]
+name = "certifi"
+version = "2025.10.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" },
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
+]
+
+[[package]]
+name = "dify-client"
+version = "0.1.12"
+source = { editable = "." }
+dependencies = [
+ { name = "aiofiles" },
+ { name = "httpx" },
+]
+
+[package.optional-dependencies]
+dev = [
+ { name = "pytest" },
+ { name = "pytest-asyncio" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "aiofiles", specifier = ">=23.0.0" },
+ { name = "httpx", specifier = ">=0.27.0" },
+ { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0.0" },
+ { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.21.0" },
+]
+provides-extras = ["dev"]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
+]
+
+[[package]]
+name = "h11"
+version = "0.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
+]
+
+[[package]]
+name = "httpcore"
+version = "1.0.9"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "h11" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
+]
+
+[[package]]
+name = "httpx"
+version = "0.28.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "certifi" },
+ { name = "httpcore" },
+ { name = "idna" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
+]
+
+[[package]]
+name = "idna"
+version = "3.10"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "25.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.19.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "8.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
+ { name = "iniconfig" },
+ { name = "packaging" },
+ { name = "pluggy" },
+ { name = "pygments" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
+]
+
+[[package]]
+name = "pytest-asyncio"
+version = "1.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" },
+ { name = "pytest" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" },
+]
+
+[[package]]
+name = "sniffio"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
+]
+
+[[package]]
+name = "tomli"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" },
+ { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" },
+ { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" },
+ { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" },
+ { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" },
+ { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" },
+ { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" },
+ { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" },
+ { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" },
+ { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" },
+ { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" },
+ { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" },
+ { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" },
+ { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" },
+ { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" },
+ { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" },
+ { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" },
+ { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" },
+ { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" },
+ { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" },
+ { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" },
+ { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" },
+ { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" },
+ { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" },
+ { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" },
+ { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" },
+ { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
+]
diff --git a/web/__tests__/goto-anything/command-selector.test.tsx b/web/__tests__/goto-anything/command-selector.test.tsx
index 1db4be31fb..6d4e045d49 100644
--- a/web/__tests__/goto-anything/command-selector.test.tsx
+++ b/web/__tests__/goto-anything/command-selector.test.tsx
@@ -16,7 +16,7 @@ jest.mock('cmdk', () => ({
Item: ({ children, onSelect, value, className }: any) => (
onSelect && onSelect()}
+ onClick={() => onSelect?.()}
data-value={value}
data-testid={`command-item-${value}`}
>
diff --git a/web/__tests__/real-browser-flicker.test.tsx b/web/__tests__/real-browser-flicker.test.tsx
index 52bdf4777f..f71e8de515 100644
--- a/web/__tests__/real-browser-flicker.test.tsx
+++ b/web/__tests__/real-browser-flicker.test.tsx
@@ -13,39 +13,60 @@ import { ThemeProvider } from 'next-themes'
import useTheme from '@/hooks/use-theme'
import { useEffect, useState } from 'react'
+const DARK_MODE_MEDIA_QUERY = /prefers-color-scheme:\s*dark/i
+
// Setup browser environment for testing
const setupMockEnvironment = (storedTheme: string | null, systemPrefersDark = false) => {
- // Mock localStorage
- const mockStorage = {
- getItem: jest.fn((key: string) => {
- if (key === 'theme') return storedTheme
- return null
- }),
- setItem: jest.fn(),
- removeItem: jest.fn(),
+ if (typeof window === 'undefined')
+ return
+
+ try {
+ window.localStorage.clear()
+ }
+ catch {
+ // ignore if localStorage has been replaced by a throwing stub
}
- // Mock system theme preference
- const mockMatchMedia = jest.fn((query: string) => ({
- matches: query.includes('dark') && systemPrefersDark,
- media: query,
- addListener: jest.fn(),
- removeListener: jest.fn(),
- }))
+ if (storedTheme === null)
+ window.localStorage.removeItem('theme')
+ else
+ window.localStorage.setItem('theme', storedTheme)
- if (typeof window !== 'undefined') {
- Object.defineProperty(window, 'localStorage', {
- value: mockStorage,
- configurable: true,
- })
+ document.documentElement.removeAttribute('data-theme')
- Object.defineProperty(window, 'matchMedia', {
- value: mockMatchMedia,
- configurable: true,
- })
+ const mockMatchMedia: typeof window.matchMedia = (query: string) => {
+ const listeners = new Set<(event: MediaQueryListEvent) => void>()
+ const isDarkQuery = DARK_MODE_MEDIA_QUERY.test(query)
+ const matches = isDarkQuery ? systemPrefersDark : false
+
+ const mediaQueryList: MediaQueryList = {
+ matches,
+ media: query,
+ onchange: null,
+ addListener: (listener: MediaQueryListListener) => {
+ listeners.add(listener)
+ },
+ removeListener: (listener: MediaQueryListListener) => {
+ listeners.delete(listener)
+ },
+ addEventListener: (_event, listener: EventListener) => {
+ if (typeof listener === 'function')
+ listeners.add(listener as MediaQueryListListener)
+ },
+ removeEventListener: (_event, listener: EventListener) => {
+ if (typeof listener === 'function')
+ listeners.delete(listener as MediaQueryListListener)
+ },
+ dispatchEvent: (event: Event) => {
+ listeners.forEach(listener => listener(event as MediaQueryListEvent))
+ return true
+ },
+ }
+
+ return mediaQueryList
}
- return { mockStorage, mockMatchMedia }
+ jest.spyOn(window, 'matchMedia').mockImplementation(mockMatchMedia)
}
// Simulate real page component based on Dify's actual theme usage
@@ -94,7 +115,17 @@ const TestThemeProvider = ({ children }: { children: React.ReactNode }) => (
describe('Real Browser Environment Dark Mode Flicker Test', () => {
beforeEach(() => {
+ jest.restoreAllMocks()
jest.clearAllMocks()
+ if (typeof window !== 'undefined') {
+ try {
+ window.localStorage.clear()
+ }
+ catch {
+ // ignore when localStorage is replaced with an error-throwing stub
+ }
+ document.documentElement.removeAttribute('data-theme')
+ }
})
describe('Page Refresh Scenario Simulation', () => {
@@ -323,35 +354,40 @@ describe('Real Browser Environment Dark Mode Flicker Test', () => {
describe('Edge Cases and Error Handling', () => {
test('handles localStorage access errors gracefully', async () => {
- // Mock localStorage to throw an error
+ setupMockEnvironment(null)
+
const mockStorage = {
getItem: jest.fn(() => {
throw new Error('LocalStorage access denied')
}),
setItem: jest.fn(),
removeItem: jest.fn(),
+ clear: jest.fn(),
}
- if (typeof window !== 'undefined') {
- Object.defineProperty(window, 'localStorage', {
- value: mockStorage,
- configurable: true,
- })
- }
-
- render(
-
-
- ,
- )
-
- // Should fallback gracefully without crashing
- await waitFor(() => {
- expect(screen.getByTestId('theme-indicator')).toBeInTheDocument()
+ Object.defineProperty(window, 'localStorage', {
+ value: mockStorage,
+ configurable: true,
})
- // Should default to light theme when localStorage fails
- expect(screen.getByTestId('visual-appearance')).toHaveTextContent('Appearance: light')
+ try {
+ render(
+
+
+ ,
+ )
+
+ // Should fallback gracefully without crashing
+ await waitFor(() => {
+ expect(screen.getByTestId('theme-indicator')).toBeInTheDocument()
+ })
+
+ // Should default to light theme when localStorage fails
+ expect(screen.getByTestId('visual-appearance')).toHaveTextContent('Appearance: light')
+ }
+ finally {
+ Reflect.deleteProperty(window, 'localStorage')
+ }
})
test('handles invalid theme values in localStorage', async () => {
@@ -403,6 +439,8 @@ describe('Real Browser Environment Dark Mode Flicker Test', () => {
setupMockEnvironment('dark')
+ expect(window.localStorage.getItem('theme')).toBe('dark')
+
render(
diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx
index 1ab40e31bf..246a1eb6a3 100644
--- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx
+++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx
@@ -4,6 +4,7 @@ import React, { useCallback, useRef, useState } from 'react'
import type { PopupProps } from './config-popup'
import ConfigPopup from './config-popup'
+import cn from '@/utils/classnames'
import {
PortalToFollowElem,
PortalToFollowElemContent,
@@ -45,7 +46,7 @@ const ConfigBtn: FC = ({
offset={12}
>
-
+
{children}
diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx
index dfc8d10087..b98eb815f9 100644
--- a/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx
+++ b/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx
@@ -28,7 +28,8 @@ const CSVUploader: FC
= ({
const handleDragEnter = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
- e.target !== dragRef.current && setDragging(true)
+ if (e.target !== dragRef.current)
+ setDragging(true)
}
const handleDragOver = (e: DragEvent) => {
e.preventDefault()
@@ -37,7 +38,8 @@ const CSVUploader: FC = ({
const handleDragLeave = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
- e.target === dragRef.current && setDragging(false)
+ if (e.target === dragRef.current)
+ setDragging(false)
}
const handleDrop = (e: DragEvent) => {
e.preventDefault()
diff --git a/web/app/components/app/app-publisher/index.tsx b/web/app/components/app/app-publisher/index.tsx
index 53cceb8020..df2618b49c 100644
--- a/web/app/components/app/app-publisher/index.tsx
+++ b/web/app/components/app/app-publisher/index.tsx
@@ -348,7 +348,8 @@ const AppPublisher = ({
{
- publishedAt && handleOpenInExplore()
+ if (publishedAt)
+ handleOpenInExplore()
}}
disabled={!publishedAt || (systemFeatures.webapp_auth.enabled && !userCanAccessApp?.result)}
icon={ }
diff --git a/web/app/components/app/app-publisher/version-info-modal.tsx b/web/app/components/app/app-publisher/version-info-modal.tsx
index 4d5d3705c1..263f187736 100644
--- a/web/app/components/app/app-publisher/version-info-modal.tsx
+++ b/web/app/components/app/app-publisher/version-info-modal.tsx
@@ -40,7 +40,8 @@ const VersionInfoModal: FC = ({
return
}
else {
- titleError && setTitleError(false)
+ if (titleError)
+ setTitleError(false)
}
if (releaseNotes.length > RELEASE_NOTES_MAX_LENGTH) {
@@ -52,7 +53,8 @@ const VersionInfoModal: FC = ({
return
}
else {
- releaseNotesError && setReleaseNotesError(false)
+ if (releaseNotesError)
+ setReleaseNotesError(false)
}
onPublish({ title, releaseNotes, id: versionInfo?.id })
diff --git a/web/app/components/app/configuration/base/icons/citation.tsx b/web/app/components/app/configuration/base/icons/citation.tsx
index e69de29bb2..3aa6b0f0e1 100644
--- a/web/app/components/app/configuration/base/icons/citation.tsx
+++ b/web/app/components/app/configuration/base/icons/citation.tsx
@@ -0,0 +1,29 @@
+import type { SVGProps } from 'react'
+
+const CitationIcon = (props: SVGProps) => (
+
+
+
+
+)
+
+export default CitationIcon
diff --git a/web/app/components/app/configuration/config-var/config-modal/index.tsx b/web/app/components/app/configuration/config-var/config-modal/index.tsx
index b0f0ea8779..8a02ca8caa 100644
--- a/web/app/components/app/configuration/config-var/config-modal/index.tsx
+++ b/web/app/components/app/configuration/config-var/config-modal/index.tsx
@@ -32,6 +32,19 @@ import { TransferMethod } from '@/types/app'
import type { FileEntity } from '@/app/components/base/file-uploader/types'
const TEXT_MAX_LENGTH = 256
+const CHECKBOX_DEFAULT_TRUE_VALUE = 'true'
+const CHECKBOX_DEFAULT_FALSE_VALUE = 'false'
+
+const getCheckboxDefaultSelectValue = (value: InputVar['default']) => {
+ if (typeof value === 'boolean')
+ return value ? CHECKBOX_DEFAULT_TRUE_VALUE : CHECKBOX_DEFAULT_FALSE_VALUE
+ if (typeof value === 'string')
+ return value.toLowerCase() === CHECKBOX_DEFAULT_TRUE_VALUE ? CHECKBOX_DEFAULT_TRUE_VALUE : CHECKBOX_DEFAULT_FALSE_VALUE
+ return CHECKBOX_DEFAULT_FALSE_VALUE
+}
+
+const parseCheckboxSelectValue = (value: string) =>
+ value === CHECKBOX_DEFAULT_TRUE_VALUE
export type IConfigModalProps = {
isCreate?: boolean
@@ -66,7 +79,7 @@ const ConfigModal: FC = ({
try {
return JSON.stringify(JSON.parse(tempPayload.json_schema).properties, null, 2)
}
- catch (_e) {
+ catch {
return ''
}
}, [tempPayload.json_schema])
@@ -110,7 +123,7 @@ const ConfigModal: FC = ({
}
handlePayloadChange('json_schema')(JSON.stringify(res, null, 2))
}
- catch (_e) {
+ catch {
return null
}
}, [handlePayloadChange])
@@ -198,6 +211,8 @@ const ConfigModal: FC = ({
handlePayloadChange('variable')(e.target.value)
}, [handlePayloadChange, t])
+ const checkboxDefaultSelectValue = useMemo(() => getCheckboxDefaultSelectValue(tempPayload.default), [tempPayload.default])
+
const handleConfirm = () => {
const moreInfo = tempPayload.variable === payload?.variable
? undefined
@@ -324,6 +339,23 @@ const ConfigModal: FC = ({
)}
+ {type === InputVarType.checkbox && (
+
+ handlePayloadChange('default')(parseCheckboxSelectValue(String(item.value)))}
+ placeholder={t('appDebug.variableConfig.selectDefaultValue')}
+ allowSearch={false}
+ />
+
+ )}
+
{type === InputVarType.select && (
<>
diff --git a/web/app/components/app/configuration/index.tsx b/web/app/components/app/configuration/index.tsx
index f1f81ebf97..20229c9717 100644
--- a/web/app/components/app/configuration/index.tsx
+++ b/web/app/components/app/configuration/index.tsx
@@ -480,7 +480,7 @@ const Configuration: FC = () => {
Toast.notify({ type: 'warning', message: `${t('common.modelProvider.parametersInvalidRemoved')}: ${Object.entries(removedDetails).map(([k, reason]) => `${k} (${reason})`).join(', ')}` })
setCompletionParams(filtered)
}
- catch (e) {
+ catch {
Toast.notify({ type: 'error', message: t('common.error') })
setCompletionParams({})
}
diff --git a/web/app/components/app/configuration/prompt-value-panel/index.tsx b/web/app/components/app/configuration/prompt-value-panel/index.tsx
index e88268ba40..43c836132f 100644
--- a/web/app/components/app/configuration/prompt-value-panel/index.tsx
+++ b/web/app/components/app/configuration/prompt-value-panel/index.tsx
@@ -192,7 +192,7 @@ const PromptValuePanel: FC = ({
onSend && onSend()}
+ onClick={() => onSend?.()}
className="w-[96px]">
{t('appDebug.inputs.run')}
@@ -203,7 +203,7 @@ const PromptValuePanel: FC = ({
onSend && onSend()}
+ onClick={() => onSend?.()}
className="w-[96px]">
{t('appDebug.inputs.run')}
diff --git a/web/app/components/app/create-from-dsl-modal/uploader.tsx b/web/app/components/app/create-from-dsl-modal/uploader.tsx
index 654c7b5952..b6644da5a4 100644
--- a/web/app/components/app/create-from-dsl-modal/uploader.tsx
+++ b/web/app/components/app/create-from-dsl-modal/uploader.tsx
@@ -38,7 +38,8 @@ const Uploader: FC = ({
const handleDragEnter = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
- e.target !== dragRef.current && setDragging(true)
+ if (e.target !== dragRef.current)
+ setDragging(true)
}
const handleDragOver = (e: DragEvent) => {
e.preventDefault()
@@ -47,7 +48,8 @@ const Uploader: FC = ({
const handleDragLeave = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
- e.target === dragRef.current && setDragging(false)
+ if (e.target === dragRef.current)
+ setDragging(false)
}
const handleDrop = (e: DragEvent) => {
e.preventDefault()
diff --git a/web/app/components/app/overview/app-chart.tsx b/web/app/components/app/overview/app-chart.tsx
index 9d9b27f230..c550f0b23f 100644
--- a/web/app/components/app/overview/app-chart.tsx
+++ b/web/app/components/app/overview/app-chart.tsx
@@ -107,7 +107,8 @@ const Chart: React.FC = ({
const { t } = useTranslation()
const statistics = chartData.data
const statisticsLen = statistics.length
- const extraDataForMarkLine = new Array(statisticsLen >= 2 ? statisticsLen - 2 : statisticsLen).fill('1')
+ const markLineLength = statisticsLen >= 2 ? statisticsLen - 2 : statisticsLen
+ const extraDataForMarkLine = Array.from({ length: markLineLength }, () => '1')
extraDataForMarkLine.push('')
extraDataForMarkLine.unshift('')
diff --git a/web/app/components/base/audio-btn/audio.ts b/web/app/components/base/audio-btn/audio.ts
index 00797d04e4..b06458cfa8 100644
--- a/web/app/components/base/audio-btn/audio.ts
+++ b/web/app/components/base/audio-btn/audio.ts
@@ -127,7 +127,7 @@ export default class AudioPlayer {
}
catch {
this.isLoadData = false
- this.callback && this.callback('error')
+ this.callback?.('error')
}
}
@@ -137,15 +137,14 @@ export default class AudioPlayer {
if (this.audioContext.state === 'suspended') {
this.audioContext.resume().then((_) => {
this.audio.play()
- this.callback && this.callback('play')
+ this.callback?.('play')
})
}
else if (this.audio.ended) {
this.audio.play()
- this.callback && this.callback('play')
+ this.callback?.('play')
}
- if (this.callback)
- this.callback('play')
+ this.callback?.('play')
}
else {
this.isLoadData = true
@@ -189,24 +188,24 @@ export default class AudioPlayer {
if (this.audio.paused) {
this.audioContext.resume().then((_) => {
this.audio.play()
- this.callback && this.callback('play')
+ this.callback?.('play')
})
}
else if (this.audio.ended) {
this.audio.play()
- this.callback && this.callback('play')
+ this.callback?.('play')
}
else if (this.audio.played) { /* empty */ }
else {
this.audio.play()
- this.callback && this.callback('play')
+ this.callback?.('play')
}
}
}
public pauseAudio() {
- this.callback && this.callback('paused')
+ this.callback?.('paused')
this.audio.pause()
this.audioContext.suspend()
}
diff --git a/web/app/components/base/chat/chat-with-history/hooks.tsx b/web/app/components/base/chat/chat-with-history/hooks.tsx
index fb3e1bb8f3..c17ab26dfe 100644
--- a/web/app/components/base/chat/chat-with-history/hooks.tsx
+++ b/web/app/components/base/chat/chat-with-history/hooks.tsx
@@ -128,7 +128,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
const localState = localStorage.getItem('webappSidebarCollapse')
return localState === 'collapsed'
}
- catch (e) {
+ catch {
// localStorage may be disabled in private browsing mode or by security settings
// fallback to default value
return false
@@ -142,7 +142,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
try {
localStorage.setItem('webappSidebarCollapse', state ? 'collapsed' : 'expanded')
}
- catch (e) {
+ catch {
// localStorage may be disabled, continue without persisting state
}
}
@@ -235,13 +235,15 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
}
}
- if(item.checkbox) {
+ if (item.checkbox) {
+ const preset = initInputs[item.checkbox.variable] === true
return {
...item.checkbox,
- default: false,
+ default: preset || item.default || item.checkbox.default,
type: 'checkbox',
}
}
+
if (item.select) {
const isInputInOptions = item.select.options.includes(initInputs[item.select.variable])
return {
diff --git a/web/app/components/base/chat/chat/answer/index.tsx b/web/app/components/base/chat/chat/answer/index.tsx
index 993a3a5519..51eb00cfc5 100644
--- a/web/app/components/base/chat/chat/answer/index.tsx
+++ b/web/app/components/base/chat/chat/answer/index.tsx
@@ -101,10 +101,14 @@ const Answer: FC = ({
}, [])
const handleSwitchSibling = useCallback((direction: 'prev' | 'next') => {
- if (direction === 'prev')
- item.prevSibling && switchSibling?.(item.prevSibling)
- else
- item.nextSibling && switchSibling?.(item.nextSibling)
+ if (direction === 'prev') {
+ if (item.prevSibling)
+ switchSibling?.(item.prevSibling)
+ }
+ else {
+ if (item.nextSibling)
+ switchSibling?.(item.nextSibling)
+ }
}, [switchSibling, item.prevSibling, item.nextSibling])
return (
diff --git a/web/app/components/base/chat/chat/question.tsx b/web/app/components/base/chat/chat/question.tsx
index 6630d9bb9d..21b604b969 100644
--- a/web/app/components/base/chat/chat/question.tsx
+++ b/web/app/components/base/chat/chat/question.tsx
@@ -73,10 +73,14 @@ const Question: FC = ({
}, [content])
const handleSwitchSibling = useCallback((direction: 'prev' | 'next') => {
- if (direction === 'prev')
- item.prevSibling && switchSibling?.(item.prevSibling)
- else
- item.nextSibling && switchSibling?.(item.nextSibling)
+ if (direction === 'prev') {
+ if (item.prevSibling)
+ switchSibling?.(item.prevSibling)
+ }
+ else {
+ if (item.nextSibling)
+ switchSibling?.(item.nextSibling)
+ }
}, [switchSibling, item.prevSibling, item.nextSibling])
const getContentWidth = () => {
diff --git a/web/app/components/base/chat/embedded-chatbot/hooks.tsx b/web/app/components/base/chat/embedded-chatbot/hooks.tsx
index 14a32860b9..aa7006db25 100644
--- a/web/app/components/base/chat/embedded-chatbot/hooks.tsx
+++ b/web/app/components/base/chat/embedded-chatbot/hooks.tsx
@@ -195,13 +195,16 @@ export const useEmbeddedChatbot = () => {
type: 'number',
}
}
+
if (item.checkbox) {
+ const preset = initInputs[item.checkbox.variable] === true
return {
...item.checkbox,
- default: false,
+ default: preset || item.default || item.checkbox.default,
type: 'checkbox',
}
}
+
if (item.select) {
const isInputInOptions = item.select.options.includes(initInputs[item.select.variable])
return {
diff --git a/web/app/components/base/date-and-time-picker/time-picker/index.spec.tsx b/web/app/components/base/date-and-time-picker/time-picker/index.spec.tsx
new file mode 100644
index 0000000000..40bc2928c8
--- /dev/null
+++ b/web/app/components/base/date-and-time-picker/time-picker/index.spec.tsx
@@ -0,0 +1,95 @@
+import React from 'react'
+import { fireEvent, render, screen } from '@testing-library/react'
+import TimePicker from './index'
+import dayjs from '../utils/dayjs'
+import { isDayjsObject } from '../utils/dayjs'
+
+jest.mock('react-i18next', () => ({
+ useTranslation: () => ({
+ t: (key: string) => {
+ if (key === 'time.defaultPlaceholder') return 'Pick a time...'
+ if (key === 'time.operation.now') return 'Now'
+ if (key === 'time.operation.ok') return 'OK'
+ if (key === 'common.operation.clear') return 'Clear'
+ return key
+ },
+ }),
+}))
+
+jest.mock('@/app/components/base/portal-to-follow-elem', () => ({
+ PortalToFollowElem: ({ children }: { children: React.ReactNode }) => {children}
,
+ PortalToFollowElemTrigger: ({ children, onClick }: { children: React.ReactNode, onClick: (e: React.MouseEvent) => void }) => (
+ {children}
+ ),
+ PortalToFollowElemContent: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
+}))
+
+jest.mock('./options', () => () =>
)
+jest.mock('./header', () => () =>
)
+
+describe('TimePicker', () => {
+ const baseProps = {
+ onChange: jest.fn(),
+ onClear: jest.fn(),
+ }
+
+ beforeEach(() => {
+ jest.clearAllMocks()
+ })
+
+ test('renders formatted value for string input (Issue #26692 regression)', () => {
+ render(
+ ,
+ )
+
+ expect(screen.getByDisplayValue('06:45 PM')).toBeInTheDocument()
+ })
+
+ test('confirms cleared value when confirming without selection', () => {
+ render(
+ ,
+ )
+
+ const input = screen.getByRole('textbox')
+ fireEvent.click(input)
+
+ const clearButton = screen.getByRole('button', { name: /clear/i })
+ fireEvent.click(clearButton)
+
+ const confirmButton = screen.getByRole('button', { name: 'OK' })
+ fireEvent.click(confirmButton)
+
+ expect(baseProps.onChange).toHaveBeenCalledTimes(1)
+ expect(baseProps.onChange).toHaveBeenCalledWith(undefined)
+ expect(baseProps.onClear).not.toHaveBeenCalled()
+ })
+
+ test('selecting current time emits timezone-aware value', () => {
+ const onChange = jest.fn()
+ render(
+ ,
+ )
+
+ const nowButton = screen.getByRole('button', { name: 'Now' })
+ fireEvent.click(nowButton)
+
+ expect(onChange).toHaveBeenCalledTimes(1)
+ const emitted = onChange.mock.calls[0][0]
+ expect(isDayjsObject(emitted)).toBe(true)
+ expect(emitted?.utcOffset()).toBe(dayjs().tz('America/New_York').utcOffset())
+ })
+})
diff --git a/web/app/components/base/date-and-time-picker/time-picker/index.tsx b/web/app/components/base/date-and-time-picker/time-picker/index.tsx
index 1fb2cfed11..f23fcf8f4e 100644
--- a/web/app/components/base/date-and-time-picker/time-picker/index.tsx
+++ b/web/app/components/base/date-and-time-picker/time-picker/index.tsx
@@ -1,6 +1,13 @@
import React, { useCallback, useEffect, useRef, useState } from 'react'
-import type { Period, TimePickerProps } from '../types'
-import dayjs, { cloneTime, getDateWithTimezone, getHourIn12Hour } from '../utils/dayjs'
+import type { Dayjs } from 'dayjs'
+import { Period } from '../types'
+import type { TimePickerProps } from '../types'
+import dayjs, {
+ getDateWithTimezone,
+ getHourIn12Hour,
+ isDayjsObject,
+ toDayjs,
+} from '../utils/dayjs'
import {
PortalToFollowElem,
PortalToFollowElemContent,
@@ -13,6 +20,11 @@ import { useTranslation } from 'react-i18next'
import { RiCloseCircleFill, RiTimeLine } from '@remixicon/react'
import cn from '@/utils/classnames'
+const to24Hour = (hour12: string, period: Period) => {
+ const normalized = Number.parseInt(hour12, 10) % 12
+ return period === Period.PM ? normalized + 12 : normalized
+}
+
const TimePicker = ({
value,
timezone,
@@ -28,7 +40,11 @@ const TimePicker = ({
const [isOpen, setIsOpen] = useState(false)
const containerRef = useRef(null)
const isInitial = useRef(true)
- const [selectedTime, setSelectedTime] = useState(() => value ? getDateWithTimezone({ timezone, date: value }) : undefined)
+
+ // Initialize selectedTime
+ const [selectedTime, setSelectedTime] = useState(() => {
+ return toDayjs(value, { timezone })
+ })
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
@@ -39,20 +55,47 @@ const TimePicker = ({
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [])
+ // Track previous values to avoid unnecessary updates
+ const prevValueRef = useRef(value)
+ const prevTimezoneRef = useRef(timezone)
+
useEffect(() => {
if (isInitial.current) {
isInitial.current = false
+ // Save initial values on first render
+ prevValueRef.current = value
+ prevTimezoneRef.current = timezone
return
}
- if (value) {
- const newValue = getDateWithTimezone({ date: value, timezone })
- setSelectedTime(newValue)
- onChange(newValue)
+
+ // Only update when timezone changes but value doesn't
+ const valueChanged = prevValueRef.current !== value
+ const timezoneChanged = prevTimezoneRef.current !== timezone
+
+ // Update reference values
+ prevValueRef.current = value
+ prevTimezoneRef.current = timezone
+
+ // Skip if neither timezone changed nor value changed
+ if (!timezoneChanged && !valueChanged) return
+
+ if (value !== undefined && value !== null) {
+ const dayjsValue = toDayjs(value, { timezone })
+ if (!dayjsValue) return
+
+ setSelectedTime(dayjsValue)
+
+ if (timezoneChanged && !valueChanged)
+ onChange(dayjsValue)
+ return
}
- else {
- setSelectedTime(prev => prev ? getDateWithTimezone({ date: prev, timezone }) : undefined)
- }
- }, [timezone])
+
+ setSelectedTime((prev) => {
+ if (!isDayjsObject(prev))
+ return undefined
+ return timezone ? getDateWithTimezone({ date: prev, timezone }) : prev
+ })
+ }, [timezone, value, onChange])
const handleClickTrigger = (e: React.MouseEvent) => {
e.stopPropagation()
@@ -61,8 +104,16 @@ const TimePicker = ({
return
}
setIsOpen(true)
- if (value)
- setSelectedTime(value)
+
+ if (value) {
+ const dayjsValue = toDayjs(value, { timezone })
+ const needsUpdate = dayjsValue && (
+ !selectedTime
+ || !isDayjsObject(selectedTime)
+ || !dayjsValue.isSame(selectedTime, 'minute')
+ )
+ if (needsUpdate) setSelectedTime(dayjsValue)
+ }
}
const handleClear = (e: React.MouseEvent) => {
@@ -73,42 +124,68 @@ const TimePicker = ({
}
const handleTimeSelect = (hour: string, minute: string, period: Period) => {
- const newTime = cloneTime(dayjs(), dayjs(`1/1/2000 ${hour}:${minute} ${period}`))
+ const periodAdjustedHour = to24Hour(hour, period)
+ const nextMinute = Number.parseInt(minute, 10)
setSelectedTime((prev) => {
- return prev ? cloneTime(prev, newTime) : newTime
+ const reference = isDayjsObject(prev)
+ ? prev
+ : (timezone ? getDateWithTimezone({ timezone }) : dayjs()).startOf('minute')
+ return reference
+ .set('hour', periodAdjustedHour)
+ .set('minute', nextMinute)
+ .set('second', 0)
+ .set('millisecond', 0)
})
}
+ const getSafeTimeObject = useCallback(() => {
+ if (isDayjsObject(selectedTime))
+ return selectedTime
+ return (timezone ? getDateWithTimezone({ timezone }) : dayjs()).startOf('day')
+ }, [selectedTime, timezone])
+
const handleSelectHour = useCallback((hour: string) => {
- const time = selectedTime || dayjs().startOf('day')
+ const time = getSafeTimeObject()
handleTimeSelect(hour, time.minute().toString().padStart(2, '0'), time.format('A') as Period)
- }, [selectedTime])
+ }, [getSafeTimeObject])
const handleSelectMinute = useCallback((minute: string) => {
- const time = selectedTime || dayjs().startOf('day')
+ const time = getSafeTimeObject()
handleTimeSelect(getHourIn12Hour(time).toString().padStart(2, '0'), minute, time.format('A') as Period)
- }, [selectedTime])
+ }, [getSafeTimeObject])
const handleSelectPeriod = useCallback((period: Period) => {
- const time = selectedTime || dayjs().startOf('day')
+ const time = getSafeTimeObject()
handleTimeSelect(getHourIn12Hour(time).toString().padStart(2, '0'), time.minute().toString().padStart(2, '0'), period)
- }, [selectedTime])
+ }, [getSafeTimeObject])
const handleSelectCurrentTime = useCallback(() => {
const newDate = getDateWithTimezone({ timezone })
setSelectedTime(newDate)
onChange(newDate)
setIsOpen(false)
- }, [onChange, timezone])
+ }, [timezone, onChange])
const handleConfirm = useCallback(() => {
- onChange(selectedTime)
+ const valueToEmit = isDayjsObject(selectedTime) ? selectedTime : undefined
+ onChange(valueToEmit)
setIsOpen(false)
- }, [onChange, selectedTime])
+ }, [selectedTime, onChange])
const timeFormat = 'hh:mm A'
- const displayValue = value?.format(timeFormat) || ''
- const placeholderDate = isOpen && selectedTime ? selectedTime.format(timeFormat) : (placeholder || t('time.defaultPlaceholder'))
+
+ const formatTimeValue = useCallback((timeValue: string | Dayjs | undefined): string => {
+ if (!timeValue) return ''
+
+ const dayjsValue = toDayjs(timeValue, { timezone })
+ return dayjsValue?.format(timeFormat) || ''
+ }, [timezone])
+
+ const displayValue = formatTimeValue(value)
+
+ const placeholderDate = isOpen && isDayjsObject(selectedTime)
+ ? selectedTime.format(timeFormat)
+ : (placeholder || t('time.defaultPlaceholder'))
const inputElem = (
diff --git a/web/app/components/base/date-and-time-picker/types.ts b/web/app/components/base/date-and-time-picker/types.ts
index 4ac01c142a..b51c2ebb01 100644
--- a/web/app/components/base/date-and-time-picker/types.ts
+++ b/web/app/components/base/date-and-time-picker/types.ts
@@ -54,7 +54,7 @@ export type TriggerParams = {
onClick: (e: React.MouseEvent) => void
}
export type TimePickerProps = {
- value: Dayjs | undefined
+ value: Dayjs | string | undefined
timezone?: string
placeholder?: string
onChange: (date: Dayjs | undefined) => void
diff --git a/web/app/components/base/date-and-time-picker/utils/dayjs.spec.ts b/web/app/components/base/date-and-time-picker/utils/dayjs.spec.ts
new file mode 100644
index 0000000000..549ab01029
--- /dev/null
+++ b/web/app/components/base/date-and-time-picker/utils/dayjs.spec.ts
@@ -0,0 +1,67 @@
+import dayjs from './dayjs'
+import {
+ getDateWithTimezone,
+ isDayjsObject,
+ toDayjs,
+} from './dayjs'
+
+describe('dayjs utilities', () => {
+ const timezone = 'UTC'
+
+ test('toDayjs parses time-only strings with timezone support', () => {
+ const result = toDayjs('18:45', { timezone })
+ expect(result).toBeDefined()
+ expect(result?.format('HH:mm')).toBe('18:45')
+ expect(result?.utcOffset()).toBe(getDateWithTimezone({ timezone }).utcOffset())
+ })
+
+ test('toDayjs parses 12-hour time strings', () => {
+ const tz = 'America/New_York'
+ const result = toDayjs('07:15 PM', { timezone: tz })
+ expect(result).toBeDefined()
+ expect(result?.format('HH:mm')).toBe('19:15')
+ expect(result?.utcOffset()).toBe(getDateWithTimezone({ timezone: tz }).utcOffset())
+ })
+
+ test('isDayjsObject detects dayjs instances', () => {
+ const date = dayjs()
+ expect(isDayjsObject(date)).toBe(true)
+ expect(isDayjsObject(getDateWithTimezone({ timezone }))).toBe(true)
+ expect(isDayjsObject('2024-01-01')).toBe(false)
+ expect(isDayjsObject({})).toBe(false)
+ })
+
+ test('toDayjs parses datetime strings in target timezone', () => {
+ const value = '2024-05-01 12:00:00'
+ const tz = 'America/New_York'
+
+ const result = toDayjs(value, { timezone: tz })
+
+ expect(result).toBeDefined()
+ expect(result?.hour()).toBe(12)
+ expect(result?.format('YYYY-MM-DD HH:mm')).toBe('2024-05-01 12:00')
+ })
+
+ test('toDayjs parses ISO datetime strings in target timezone', () => {
+ const value = '2024-05-01T14:30:00'
+ const tz = 'Europe/London'
+
+ const result = toDayjs(value, { timezone: tz })
+
+ expect(result).toBeDefined()
+ expect(result?.hour()).toBe(14)
+ expect(result?.minute()).toBe(30)
+ })
+
+ test('toDayjs handles dates without time component', () => {
+ const value = '2024-05-01'
+ const tz = 'America/Los_Angeles'
+
+ const result = toDayjs(value, { timezone: tz })
+
+ expect(result).toBeDefined()
+ expect(result?.format('YYYY-MM-DD')).toBe('2024-05-01')
+ expect(result?.hour()).toBe(0)
+ expect(result?.minute()).toBe(0)
+ })
+})
diff --git a/web/app/components/base/date-and-time-picker/utils/dayjs.ts b/web/app/components/base/date-and-time-picker/utils/dayjs.ts
index 27200e76e9..808b50247a 100644
--- a/web/app/components/base/date-and-time-picker/utils/dayjs.ts
+++ b/web/app/components/base/date-and-time-picker/utils/dayjs.ts
@@ -10,6 +10,25 @@ dayjs.extend(timezone)
export default dayjs
const monthMaps: Record = {}
+const DEFAULT_OFFSET_STR = 'UTC+0'
+const TIME_ONLY_REGEX = /^(\d{1,2}):(\d{2})(?::(\d{2})(?:\.(\d{1,3}))?)?$/
+const TIME_ONLY_12H_REGEX = /^(\d{1,2}):(\d{2})(?::(\d{2}))?\s?(AM|PM)$/i
+
+const COMMON_PARSE_FORMATS = [
+ 'YYYY-MM-DD',
+ 'YYYY/MM/DD',
+ 'DD-MM-YYYY',
+ 'DD/MM/YYYY',
+ 'MM-DD-YYYY',
+ 'MM/DD/YYYY',
+ 'YYYY-MM-DDTHH:mm:ss.SSSZ',
+ 'YYYY-MM-DDTHH:mm:ssZ',
+ 'YYYY-MM-DD HH:mm:ss',
+ 'YYYY-MM-DDTHH:mm',
+ 'YYYY-MM-DDTHH:mmZ',
+ 'YYYY-MM-DDTHH:mm:ss',
+ 'YYYY-MM-DDTHH:mm:ss.SSS',
+]
export const cloneTime = (targetDate: Dayjs, sourceDate: Dayjs) => {
return targetDate.clone()
@@ -76,21 +95,116 @@ export const getHourIn12Hour = (date: Dayjs) => {
return hour === 0 ? 12 : hour >= 12 ? hour - 12 : hour
}
-export const getDateWithTimezone = (props: { date?: Dayjs, timezone?: string }) => {
- return props.date ? dayjs.tz(props.date, props.timezone) : dayjs().tz(props.timezone)
+export const getDateWithTimezone = ({ date, timezone }: { date?: Dayjs, timezone?: string }) => {
+ if (!timezone)
+ return (date ?? dayjs()).clone()
+ return date ? dayjs.tz(date, timezone) : dayjs().tz(timezone)
}
-// Asia/Shanghai -> UTC+8
-const DEFAULT_OFFSET_STR = 'UTC+0'
export const convertTimezoneToOffsetStr = (timezone?: string) => {
if (!timezone)
return DEFAULT_OFFSET_STR
const tzItem = tz.find(item => item.value === timezone)
- if(!tzItem)
+ if (!tzItem)
return DEFAULT_OFFSET_STR
return `UTC${tzItem.name.charAt(0)}${tzItem.name.charAt(2)}`
}
+export const isDayjsObject = (value: unknown): value is Dayjs => dayjs.isDayjs(value)
+
+export type ToDayjsOptions = {
+ timezone?: string
+ format?: string
+ formats?: string[]
+}
+
+const warnParseFailure = (value: string) => {
+ if (process.env.NODE_ENV !== 'production')
+ console.warn('[TimePicker] Failed to parse time value', value)
+}
+
+const normalizeMillisecond = (value: string | undefined) => {
+ if (!value) return 0
+ if (value.length === 3) return Number(value)
+ if (value.length > 3) return Number(value.slice(0, 3))
+ return Number(value.padEnd(3, '0'))
+}
+
+const applyTimezone = (date: Dayjs, timezone?: string) => {
+ return timezone ? getDateWithTimezone({ date, timezone }) : date
+}
+
+export const toDayjs = (value: string | Dayjs | undefined, options: ToDayjsOptions = {}): Dayjs | undefined => {
+ if (!value)
+ return undefined
+
+ const { timezone: tzName, format, formats } = options
+
+ if (isDayjsObject(value))
+ return applyTimezone(value, tzName)
+
+ if (typeof value !== 'string')
+ return undefined
+
+ const trimmed = value.trim()
+
+ if (format) {
+ const parsedWithFormat = tzName
+ ? dayjs.tz(trimmed, format, tzName, true)
+ : dayjs(trimmed, format, true)
+ if (parsedWithFormat.isValid())
+ return parsedWithFormat
+ }
+
+ const timeMatch = TIME_ONLY_REGEX.exec(trimmed)
+ if (timeMatch) {
+ const base = applyTimezone(dayjs(), tzName).startOf('day')
+ const rawHour = Number(timeMatch[1])
+ const minute = Number(timeMatch[2])
+ const second = timeMatch[3] ? Number(timeMatch[3]) : 0
+ const millisecond = normalizeMillisecond(timeMatch[4])
+
+ return base
+ .set('hour', rawHour)
+ .set('minute', minute)
+ .set('second', second)
+ .set('millisecond', millisecond)
+ }
+
+ const timeMatch12h = TIME_ONLY_12H_REGEX.exec(trimmed)
+ if (timeMatch12h) {
+ const base = applyTimezone(dayjs(), tzName).startOf('day')
+ let hour = Number(timeMatch12h[1]) % 12
+ const isPM = timeMatch12h[4]?.toUpperCase() === 'PM'
+ if (isPM)
+ hour += 12
+ const minute = Number(timeMatch12h[2])
+ const second = timeMatch12h[3] ? Number(timeMatch12h[3]) : 0
+
+ return base
+ .set('hour', hour)
+ .set('minute', minute)
+ .set('second', second)
+ .set('millisecond', 0)
+ }
+
+ const candidateFormats = formats ?? COMMON_PARSE_FORMATS
+ for (const fmt of candidateFormats) {
+ const parsed = tzName
+ ? dayjs.tz(trimmed, fmt, tzName, true)
+ : dayjs(trimmed, fmt, true)
+ if (parsed.isValid())
+ return parsed
+ }
+
+ const fallbackParsed = tzName ? dayjs.tz(trimmed, tzName) : dayjs(trimmed)
+ if (fallbackParsed.isValid())
+ return fallbackParsed
+
+ warnParseFailure(value)
+ return undefined
+}
+
// Parse date with multiple format support
export const parseDateWithFormat = (dateString: string, format?: string): Dayjs | null => {
if (!dateString) return null
@@ -103,15 +217,7 @@ export const parseDateWithFormat = (dateString: string, format?: string): Dayjs
// Try common date formats
const formats = [
- 'YYYY-MM-DD', // Standard format
- 'YYYY/MM/DD', // Slash format
- 'DD-MM-YYYY', // European format
- 'DD/MM/YYYY', // European slash format
- 'MM-DD-YYYY', // US format
- 'MM/DD/YYYY', // US slash format
- 'YYYY-MM-DDTHH:mm:ss.SSSZ', // ISO format
- 'YYYY-MM-DDTHH:mm:ssZ', // ISO format (no milliseconds)
- 'YYYY-MM-DD HH:mm:ss', // Standard datetime format
+ ...COMMON_PARSE_FORMATS,
]
for (const fmt of formats) {
@@ -124,7 +230,7 @@ export const parseDateWithFormat = (dateString: string, format?: string): Dayjs
}
// Format date output with localization support
-export const formatDateForOutput = (date: Dayjs, includeTime: boolean = false, locale: string = 'en-US'): string => {
+export const formatDateForOutput = (date: Dayjs, includeTime: boolean = false, _locale: string = 'en-US'): string => {
if (!date || !date.isValid()) return ''
if (includeTime) {
diff --git a/web/app/components/base/drawer/index.tsx b/web/app/components/base/drawer/index.tsx
index 8217caae97..c35acbeac7 100644
--- a/web/app/components/base/drawer/index.tsx
+++ b/web/app/components/base/drawer/index.tsx
@@ -47,7 +47,10 @@ export default function Drawer({
!clickOutsideNotOpen && onClose()}
+ onClose={() => {
+ if (!clickOutsideNotOpen)
+ onClose()
+ }}
className={cn('fixed inset-0 z-[30] overflow-y-auto', dialogClassName)}
>
@@ -55,7 +58,8 @@ export default function Drawer({
{
- !clickOutsideNotOpen && onClose()
+ if (!clickOutsideNotOpen)
+ onClose()
}}
/>
@@ -80,11 +84,11 @@ export default function Drawer({
{
- onCancel && onCancel()
+ onCancel?.()
}}>{t('common.operation.cancel')}
{
- onOk && onOk()
+ onOk?.()
}}>{t('common.operation.save')}
)}
diff --git a/web/app/components/base/emoji-picker/index.tsx b/web/app/components/base/emoji-picker/index.tsx
index d3b20bb507..7b91c62797 100644
--- a/web/app/components/base/emoji-picker/index.tsx
+++ b/web/app/components/base/emoji-picker/index.tsx
@@ -45,7 +45,7 @@ const EmojiPicker: FC = ({
{
- onClose && onClose()
+ onClose?.()
}}>
{t('app.iconPicker.cancel')}
@@ -54,7 +54,7 @@ const EmojiPicker: FC
= ({
variant="primary"
className='w-full'
onClick={() => {
- onSelect && onSelect(selectedEmoji, selectedBackground!)
+ onSelect?.(selectedEmoji, selectedBackground!)
}}>
{t('app.iconPicker.ok')}
diff --git a/web/app/components/base/file-uploader/store.tsx b/web/app/components/base/file-uploader/store.tsx
index 7f7cfd5693..cddfdf6f27 100644
--- a/web/app/components/base/file-uploader/store.tsx
+++ b/web/app/components/base/file-uploader/store.tsx
@@ -1,7 +1,6 @@
import {
createContext,
useContext,
- useEffect,
useRef,
} from 'react'
import {
@@ -19,11 +18,13 @@ type Shape = {
export const createFileStore = (
value: FileEntity[] = [],
+ onChange?: (files: FileEntity[]) => void,
) => {
return create(set => ({
files: value ? [...value] : [],
setFiles: (files) => {
set({ files })
+ onChange?.(files)
},
}))
}
@@ -54,35 +55,9 @@ export const FileContextProvider = ({
onChange,
}: FileProviderProps) => {
const storeRef = useRef(undefined)
- const onChangeRef = useRef(onChange)
- const isSyncingRef = useRef(false)
if (!storeRef.current)
- storeRef.current = createFileStore(value)
-
- // keep latest onChange
- useEffect(() => {
- onChangeRef.current = onChange
- }, [onChange])
-
- // subscribe to store changes and call latest onChange
- useEffect(() => {
- const store = storeRef.current!
- const unsubscribe = store.subscribe((state: Shape) => {
- if (isSyncingRef.current) return
- onChangeRef.current?.(state.files)
- })
- return unsubscribe
- }, [])
-
- // sync external value into internal store when value changes
- useEffect(() => {
- const store = storeRef.current!
- const nextFiles = value ? [...value] : []
- isSyncingRef.current = true
- store.setState({ files: nextFiles })
- isSyncingRef.current = false
- }, [value])
+ storeRef.current = createFileStore(value, onChange)
return (
diff --git a/web/app/components/base/form/components/field/select.tsx b/web/app/components/base/form/components/field/select.tsx
index f12b90335b..dee047e2eb 100644
--- a/web/app/components/base/form/components/field/select.tsx
+++ b/web/app/components/base/form/components/field/select.tsx
@@ -33,7 +33,10 @@ const SelectField = ({
field.handleChange(value)}
+ onChange={(value) => {
+ field.handleChange(value)
+ onChange?.(value)
+ }}
{...selectProps}
/>
diff --git a/web/app/components/base/icons/assets/public/tracing/aliyun-icon-big.svg b/web/app/components/base/icons/assets/public/tracing/aliyun-icon-big.svg
index 210a1cd00b..d82b9bc1e4 100644
--- a/web/app/components/base/icons/assets/public/tracing/aliyun-icon-big.svg
+++ b/web/app/components/base/icons/assets/public/tracing/aliyun-icon-big.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/web/app/components/base/icons/assets/public/tracing/aliyun-icon.svg b/web/app/components/base/icons/assets/public/tracing/aliyun-icon.svg
index 6f7645301c..cee8858471 100644
--- a/web/app/components/base/icons/assets/public/tracing/aliyun-icon.svg
+++ b/web/app/components/base/icons/assets/public/tracing/aliyun-icon.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/web/app/components/base/icons/src/public/tracing/AliyunIcon.json b/web/app/components/base/icons/src/public/tracing/AliyunIcon.json
index 5cbb52c237..154aeff8c6 100644
--- a/web/app/components/base/icons/src/public/tracing/AliyunIcon.json
+++ b/web/app/components/base/icons/src/public/tracing/AliyunIcon.json
@@ -1,118 +1,129 @@
{
- "icon": {
- "type": "element",
- "isRootNode": true,
- "name": "svg",
- "attributes": {
- "xmlns": "http://www.w3.org/2000/svg",
- "xmlns:xlink": "http://www.w3.org/1999/xlink",
- "fill": "none",
- "version": "1.1",
- "width": "65",
- "height": "16",
- "viewBox": "0 0 65 16"
- },
- "children": [
- {
- "type": "element",
- "name": "defs",
- "children": [
- {
- "type": "element",
- "name": "clipPath",
- "attributes": {
- "id": "master_svg0_42_34281"
- },
- "children": [
- {
- "type": "element",
- "name": "rect",
- "attributes": {
- "x": "0",
- "y": "0",
- "width": "19",
- "height": "16",
- "rx": "0"
- }
- }
- ]
- }
- ]
- },
- {
- "type": "element",
- "name": "g",
- "children": [
- {
- "type": "element",
- "name": "g",
- "attributes": {
- "clip-path": "url(#master_svg0_42_34281)"
- },
- "children": [
- {
- "type": "element",
- "name": "g",
- "children": [
- {
- "type": "element",
- "name": "g",
- "children": [
- {
- "type": "element",
- "name": "path",
- "attributes": {
- "d": "M4.06862,14.6667C3.79213,14.6667,3.45463,14.5688,3.05614,14.373C2.97908,14.3351,2.92692,14.3105,2.89968,14.2992C2.33193,14.0628,1.82911,13.7294,1.39123,13.2989C0.463742,12.3871,0,11.2874,0,10C0,8.71258,0.463742,7.61293,1.39123,6.70107C2.16172,5.94358,3.06404,5.50073,4.09819,5.37252C4.23172,3.98276,4.81755,2.77756,5.85569,1.75693C7.04708,0.585642,8.4857,0,10.1716,0C11.5256,0,12.743,0.396982,13.8239,1.19095C14.8847,1.97019,15.61,2.97855,16,4.21604L14.7045,4.61063C14.4016,3.64918,13.8374,2.86532,13.0121,2.25905C12.1719,1.64191,11.2251,1.33333,10.1716,1.33333C8.8602,1.33333,7.74124,1.7888,6.81467,2.69974C5.88811,3.61067,5.42483,4.71076,5.42483,6L5.42483,6.66667L4.74673,6.66667C3.81172,6.66667,3.01288,6.99242,2.35021,7.64393C1.68754,8.2954,1.35621,9.08076,1.35621,10C1.35621,10.9192,1.68754,11.7046,2.35021,12.3561C2.66354,12.6641,3.02298,12.9026,3.42852,13.0714C3.48193,13.0937,3.55988,13.13,3.66237,13.1803C3.87004,13.2823,4.00545,13.3333,4.06862,13.3333L4.06862,14.6667Z",
- "fill-rule": "evenodd",
- "fill": "#FF6A00",
- "fill-opacity": "1"
- }
- }
- ]
- },
- {
- "type": "element",
- "name": "g",
- "children": [
- {
- "type": "element",
- "name": "path",
- "attributes": {
- "d": "M13.458613505859375,7.779393492279053C12.975613505859375,7.717463492279053,12.484813505859375,7.686503492279053,11.993983505859376,7.686503492279053C11.152583505859376,7.686503492279053,10.303403505859375,7.779393492279053,9.493183505859374,7.941943492279053C8.682953505859375,8.104503492279052,7.903893505859375,8.359943492279053,7.155983505859375,8.654083492279053C6.657383505859375,8.870823492279053,6.158783505859375,9.128843492279053,5.660181505859375,9.428153492279053C5.332974751859375,9.621673492279053,5.239486705859375,10.070633492279054,5.434253505859375,10.395743492279053L7.413073505859375,13.298533492279052C7.639003505859375,13.623603492279052,8.090863505859375,13.716463492279052,8.418073505859375,13.523003492279052C8.547913505859375,13.435263492279052,8.763453505859374,13.326893492279053,9.064693505859374,13.197863492279053C9.516553505859374,13.004333492279052,9.976203505859374,12.872733492279053,10.459223505859375,12.779863492279052C10.942243505859375,12.679263492279052,11.433053505859375,12.617333492279052,11.955023505859375,12.617333492279052L13.380683505859375,7.810353492279052L13.458613505859375,7.779393492279053ZM15.273813505859374,8.135463492279053L15.016753505859375,5.333333492279053L13.458613505859375,7.787133492279053C13.817013505859375,7.818093492279052,14.144213505859375,7.880023492279053,14.494743505859375,7.949683492279053C14.494743505859375,7.944523492279053,14.754433505859375,8.006453492279054,15.273813505859374,8.135463492279053ZM12.064083505859376,12.648273492279053L11.378523505859375,14.970463492279054L12.515943505859376,16.00003349227905L14.074083505859376,15.643933492279054L14.525943505859376,13.027603492279052C14.198743505859374,12.934663492279054,13.879283505859375,12.834063492279054,13.552083505859375,12.772133492279053C13.069083505859375,12.717933492279052,12.578283505859375,12.648273492279053,12.064083505859376,12.648273492279053ZM18.327743505859374,9.428153492279053C17.829143505859374,9.128843492279053,17.330543505859374,8.870823492279053,16.831943505859375,8.654083492279053C16.348943505859374,8.460573492279053,15.826943505859376,8.267053492279054,15.305013505859375,8.135463492279053L15.305013505859375,8.267053492279054L14.463613505859374,13.043063492279053C14.596083505859376,13.105003492279053,14.759683505859375,13.135933492279053,14.884283505859376,13.205603492279053C15.185523505859376,13.334623492279052,15.401043505859375,13.443003492279052,15.530943505859375,13.530733492279053C15.858143505859376,13.724263492279054,16.341143505859375,13.623603492279052,16.535943505859375,13.306263492279053L18.514743505859375,10.403483492279053C18.779643505859376,10.039673492279054,18.686143505859377,9.621673492279053,18.327743505859374,9.428153492279053Z",
- "fill": "#FF6A00",
- "fill-opacity": "1"
- }
- }
- ]
- }
- ]
- }
- ]
- },
- {
- "type": "element",
- "name": "g",
- "children": [
- {
- "type": "element",
- "name": "g",
- "children": [
- {
- "type": "element",
- "name": "path",
- "attributes": {
- "d": "M25.044,2.668L34.676,2.668L34.676,4.04L25.044,4.04L25.044,2.668ZM29.958,7.82Q29.258,9.066,28.355,10.41Q27.451999999999998,11.754,26.92,12.3L32.506,11.782Q31.442,10.158,30.84,9.346L32.058,8.562000000000001Q32.786,9.5,33.843,11.012Q34.9,12.524,35.516,13.546L34.214,14.526Q33.891999999999996,13.966,33.346000000000004,13.098Q32.016,13.182,29.734,13.378Q27.451999999999998,13.574,25.87,13.742L25.31,13.812L24.834,13.882L24.414,12.468Q24.708,12.37,24.862000000000002,12.265Q25.016,12.16,25.121,12.069Q25.226,11.978,25.268,11.936Q25.912,11.32,26.724,10.165Q27.536,9.01,28.208,7.82L23.854,7.82L23.854,6.434L35.866,6.434L35.866,7.82L29.958,7.82ZM42.656,7.414L42.656,8.576L41.354,8.576L41.354,1.814L42.656,1.87L42.656,7.036Q43.314,5.846,43.888000000000005,4.369Q44.462,2.892,44.714,1.6600000000000001L46.086,1.981999Q45.96,2.612,45.722,3.41L49.6,3.41L49.6,4.74L45.274,4.74Q44.616,6.56,43.706,8.128L42.656,7.414ZM38.596000000000004,2.346L39.884,2.402L39.884,8.212L38.596000000000004,8.212L38.596000000000004,2.346ZM46.184,4.964Q46.688,5.356,47.5,6.175Q48.312,6.994,48.788,7.582L47.751999999999995,8.59Q47.346000000000004,8.072,46.576,7.274Q45.806,6.476,45.204,5.902L46.184,4.964ZM48.41,9.01L48.41,12.706L49.894,12.706L49.894,13.966L37.391999999999996,13.966L37.391999999999996,12.706L38.848,12.706L38.848,9.01L48.41,9.01ZM41.676,10.256L40.164,10.256L40.164,12.706L41.676,12.706L41.676,10.256ZM42.908,12.706L44.364000000000004,12.706L44.364000000000004,10.256L42.908,10.256L42.908,12.706ZM45.582,12.706L47.108000000000004,12.706L47.108000000000004,10.256L45.582,10.256L45.582,12.706ZM54.906,7.456L55.116,8.394L54.178,8.814L54.178,12.818Q54.178,13.434,54.031,13.735Q53.884,14.036,53.534,14.162Q53.184,14.288,52.456,14.358L51.867999999999995,14.414L51.476,13.084L52.162,13.028Q52.512,13,52.652,12.958Q52.792,12.916,52.841,12.797Q52.89,12.678,52.89,12.384L52.89,9.36Q51.980000000000004,9.724,51.322,9.948L51.013999999999996,8.576Q51.798,8.324,52.89,7.876L52.89,5.524L51.42,5.524L51.42,4.166L52.89,4.166L52.89,1.7579989999999999L54.178,1.814L54.178,4.166L55.214,4.166L55.214,5.524L54.178,5.524L54.178,7.316L54.808,7.022L54.906,7.456ZM56.894,4.5440000000000005L56.894,6.098L55.564,6.098L55.564,3.256L58.686,3.256Q58.42,2.346,58.266,1.9260000000000002L59.624,1.7579989999999999Q59.848,2.276,60.142,3.256L63.25,3.256L63.25,6.098L61.962,6.098L61.962,4.5440000000000005L56.894,4.5440000000000005ZM59.008,6.322Q58.392,6.938,57.685,7.512Q56.978,8.086,55.956,8.841999999999999L55.242,7.764Q56.824,6.728,58.126,5.37L59.008,6.322ZM60.422,5.37Q61.024,5.776,62.095,6.581Q63.166,7.386,63.656,7.806L62.942,8.982Q62.368,8.45,61.332,7.652Q60.296,6.854,59.666,6.434L60.422,5.37ZM62.592,10.256L60.044,10.256L60.044,12.566L63.572,12.566L63.572,13.826L55.144,13.826L55.144,12.566L58.63,12.566L58.63,10.256L56.054,10.256L56.054,8.982L62.592,8.982L62.592,10.256Z",
- "fill": "#FF6A00",
- "fill-opacity": "1"
- }
- }
- ]
- }
- ]
- }
- ]
- }
- ]
- },
- "name": "AliyunIcon"
+ "icon": {
+ "type": "element",
+ "isRootNode": true,
+ "name": "svg",
+ "attributes": {
+ "xmlns": "http://www.w3.org/2000/svg",
+ "xmlns:xlink": "http://www.w3.org/1999/xlink",
+ "fill": "none",
+ "version": "1.1",
+ "width": "65",
+ "height": "16",
+ "viewBox": "0 0 65 16"
+ },
+ "children": [
+ {
+ "type": "element",
+ "name": "defs",
+ "attributes": {},
+ "children": [
+ {
+ "type": "element",
+ "name": "clipPath",
+ "attributes": {
+ "id": "master_svg0_42_34281"
+ },
+ "children": [
+ {
+ "type": "element",
+ "name": "rect",
+ "attributes": {
+ "x": "0",
+ "y": "0",
+ "width": "19",
+ "height": "16",
+ "rx": "0"
+ },
+ "children": []
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "element",
+ "name": "g",
+ "attributes": {},
+ "children": [
+ {
+ "type": "element",
+ "name": "g",
+ "attributes": {
+ "clip-path": "url(#master_svg0_42_34281)"
+ },
+ "children": [
+ {
+ "type": "element",
+ "name": "g",
+ "attributes": {},
+ "children": [
+ {
+ "type": "element",
+ "name": "g",
+ "attributes": {},
+ "children": [
+ {
+ "type": "element",
+ "name": "path",
+ "attributes": {
+ "d": "M4.06862,14.6667C3.79213,14.6667,3.45463,14.5688,3.05614,14.373C2.97908,14.3351,2.92692,14.3105,2.89968,14.2992C2.33193,14.0628,1.82911,13.7294,1.39123,13.2989C0.463742,12.3871,0,11.2874,0,10C0,8.71258,0.463742,7.61293,1.39123,6.70107C2.16172,5.94358,3.06404,5.50073,4.09819,5.37252C4.23172,3.98276,4.81755,2.77756,5.85569,1.75693C7.04708,0.585642,8.4857,0,10.1716,0C11.5256,0,12.743,0.396982,13.8239,1.19095C14.8847,1.97019,15.61,2.97855,16,4.21604L14.7045,4.61063C14.4016,3.64918,13.8374,2.86532,13.0121,2.25905C12.1719,1.64191,11.2251,1.33333,10.1716,1.33333C8.8602,1.33333,7.74124,1.7888,6.81467,2.69974C5.88811,3.61067,5.42483,4.71076,5.42483,6L5.42483,6.66667L4.74673,6.66667C3.81172,6.66667,3.01288,6.99242,2.35021,7.64393C1.68754,8.2954,1.35621,9.08076,1.35621,10C1.35621,10.9192,1.68754,11.7046,2.35021,12.3561C2.66354,12.6641,3.02298,12.9026,3.42852,13.0714C3.48193,13.0937,3.55988,13.13,3.66237,13.1803C3.87004,13.2823,4.00545,13.3333,4.06862,13.3333L4.06862,14.6667Z",
+ "fill-rule": "evenodd",
+ "fill": "#FF6A00",
+ "fill-opacity": "1"
+ },
+ "children": []
+ }
+ ]
+ },
+ {
+ "type": "element",
+ "name": "g",
+ "attributes": {},
+ "children": [
+ {
+ "type": "element",
+ "name": "path",
+ "attributes": {
+ "d": "M13.458613505859375,7.779393492279053C12.975613505859375,7.717463492279053,12.484813505859375,7.686503492279053,11.993983505859376,7.686503492279053C11.152583505859376,7.686503492279053,10.303403505859375,7.779393492279053,9.493183505859374,7.941943492279053C8.682953505859375,8.104503492279052,7.903893505859375,8.359943492279053,7.155983505859375,8.654083492279053C6.657383505859375,8.870823492279053,6.158783505859375,9.128843492279053,5.660181505859375,9.428153492279053C5.332974751859375,9.621673492279053,5.239486705859375,10.070633492279054,5.434253505859375,10.395743492279053L7.413073505859375,13.298533492279052C7.639003505859375,13.623603492279052,8.090863505859375,13.716463492279052,8.418073505859375,13.523003492279052C8.547913505859375,13.435263492279052,8.763453505859374,13.326893492279053,9.064693505859374,13.197863492279053C9.516553505859374,13.004333492279052,9.976203505859374,12.872733492279053,10.459223505859375,12.779863492279052C10.942243505859375,12.679263492279052,11.433053505859375,12.617333492279052,11.955023505859375,12.617333492279052L13.380683505859375,7.810353492279052L13.458613505859375,7.779393492279053ZM15.273813505859374,8.135463492279053L15.016753505859375,5.333333492279053L13.458613505859375,7.787133492279053C13.817013505859375,7.818093492279052,14.144213505859375,7.880023492279053,14.494743505859375,7.949683492279053C14.494743505859375,7.944523492279053,14.754433505859375,8.006453492279054,15.273813505859374,8.135463492279053ZM12.064083505859376,12.648273492279053L11.378523505859375,14.970463492279054L12.515943505859376,16.00003349227905L14.074083505859376,15.643933492279054L14.525943505859376,13.027603492279052C14.198743505859374,12.934663492279054,13.879283505859375,12.834063492279054,13.552083505859375,12.772133492279053C13.069083505859375,12.717933492279052,12.578283505859375,12.648273492279053,12.064083505859376,12.648273492279053ZM18.327743505859374,9.428153492279053C17.829143505859374,9.128843492279053,17.330543505859374,8.870823492279053,16.831943505859375,8.654083492279053C16.348943505859374,8.460573492279053,15.826943505859376,8.267053492279054,15.305013505859375,8.135463492279053L15.305013505859375,8.267053492279054L14.463613505859374,13.043063492279053C14.596083505859376,13.105003492279053,14.759683505859375,13.135933492279053,14.884283505859376,13.205603492279053C15.185523505859376,13.334623492279052,15.401043505859375,13.443003492279052,15.530943505859375,13.530733492279053C15.858143505859376,13.724263492279054,16.341143505859375,13.623603492279052,16.535943505859375,13.306263492279053L18.514743505859375,10.403483492279053C18.779643505859376,10.039673492279054,18.686143505859377,9.621673492279053,18.327743505859374,9.428153492279053Z",
+ "fill": "#FF6A00",
+ "fill-opacity": "1"
+ },
+ "children": []
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "element",
+ "name": "g",
+ "attributes": {},
+ "children": [
+ {
+ "type": "element",
+ "name": "g",
+ "attributes": {},
+ "children": [
+ {
+ "type": "element",
+ "name": "path",
+ "attributes": {
+ "d": "M25.044,2.668L34.676,2.668L34.676,4.04L25.044,4.04L25.044,2.668ZM29.958,7.82Q29.258,9.066,28.355,10.41Q27.451999999999998,11.754,26.92,12.3L32.506,11.782Q31.442,10.158,30.84,9.346L32.058,8.562000000000001Q32.786,9.5,33.843,11.012Q34.9,12.524,35.516,13.546L34.214,14.526Q33.891999999999996,13.966,33.346000000000004,13.098Q32.016,13.182,29.734,13.378Q27.451999999999998,13.574,25.87,13.742L25.31,13.812L24.834,13.882L24.414,12.468Q24.708,12.37,24.862000000000002,12.265Q25.016,12.16,25.121,12.069Q25.226,11.978,25.268,11.936Q25.912,11.32,26.724,10.165Q27.536,9.01,28.208,7.82L23.854,7.82L23.854,6.434L35.866,6.434L35.866,7.82L29.958,7.82ZM42.656,7.414L42.656,8.576L41.354,8.576L41.354,1.814L42.656,1.87L42.656,7.036Q43.314,5.846,43.888000000000005,4.369Q44.462,2.892,44.714,1.6600000000000001L46.086,1.981999Q45.96,2.612,45.722,3.41L49.6,3.41L49.6,4.74L45.274,4.74Q44.616,6.56,43.706,8.128L42.656,7.414ZM38.596000000000004,2.346L39.884,2.402L39.884,8.212L38.596000000000004,8.212L38.596000000000004,2.346ZM46.184,4.964Q46.688,5.356,47.5,6.175Q48.312,6.994,48.788,7.582L47.751999999999995,8.59Q47.346000000000004,8.072,46.576,7.274Q45.806,6.476,45.204,5.902L46.184,4.964ZM48.41,9.01L48.41,12.706L49.894,12.706L49.894,13.966L37.391999999999996,13.966L37.391999999999996,12.706L38.848,12.706L38.848,9.01L48.41,9.01ZM41.676,10.256L40.164,10.256L40.164,12.706L41.676,12.706L41.676,10.256ZM42.908,12.706L44.364000000000004,12.706L44.364000000000004,10.256L42.908,10.256L42.908,12.706ZM45.582,12.706L47.108000000000004,12.706L47.108000000000004,10.256L45.582,10.256L45.582,12.706ZM54.906,7.456L55.116,8.394L54.178,8.814L54.178,12.818Q54.178,13.434,54.031,13.735Q53.884,14.036,53.534,14.162Q53.184,14.288,52.456,14.358L51.867999999999995,14.414L51.476,13.084L52.162,13.028Q52.512,13,52.652,12.958Q52.792,12.916,52.841,12.797Q52.89,12.678,52.89,12.384L52.89,9.36Q51.980000000000004,9.724,51.322,9.948L51.013999999999996,8.576Q51.798,8.324,52.89,7.876L52.89,5.524L51.42,5.524L51.42,4.166L52.89,4.166L52.89,1.7579989999999999L54.178,1.814L54.178,4.166L55.214,4.166L55.214,5.524L54.178,5.524L54.178,7.316L54.808,7.022L54.906,7.456ZM56.894,4.5440000000000005L56.894,6.098L55.564,6.098L55.564,3.256L58.686,3.256Q58.42,2.346,58.266,1.9260000000000002L59.624,1.7579989999999999Q59.848,2.276,60.142,3.256L63.25,3.256L63.25,6.098L61.962,6.098L61.962,4.5440000000000005L56.894,4.5440000000000005ZM59.008,6.322Q58.392,6.938,57.685,7.512Q56.978,8.086,55.956,8.841999999999999L55.242,7.764Q56.824,6.728,58.126,5.37L59.008,6.322ZM60.422,5.37Q61.024,5.776,62.095,6.581Q63.166,7.386,63.656,7.806L62.942,8.982Q62.368,8.45,61.332,7.652Q60.296,6.854,59.666,6.434L60.422,5.37ZM62.592,10.256L60.044,10.256L60.044,12.566L63.572,12.566L63.572,13.826L55.144,13.826L55.144,12.566L58.63,12.566L58.63,10.256L56.054,10.256L56.054,8.982L62.592,8.982L62.592,10.256Z",
+ "fill": "#FF6A00",
+ "fill-opacity": "1"
+ },
+ "children": []
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "name": "AliyunIcon"
}
diff --git a/web/app/components/base/icons/src/public/tracing/AliyunIconBig.json b/web/app/components/base/icons/src/public/tracing/AliyunIconBig.json
index ea60744daf..7ed5166461 100644
--- a/web/app/components/base/icons/src/public/tracing/AliyunIconBig.json
+++ b/web/app/components/base/icons/src/public/tracing/AliyunIconBig.json
@@ -1,71 +1,78 @@
{
- "icon": {
- "type": "element",
- "isRootNode": true,
- "name": "svg",
- "attributes": {
- "xmlns": "http://www.w3.org/2000/svg",
- "xmlns:xlink": "http://www.w3.org/1999/xlink",
- "fill": "none",
- "version": "1.1",
- "width": "96",
- "height": "24",
- "viewBox": "0 0 96 24"
- },
- "children": [
- {
- "type": "element",
- "name": "g",
- "children": [
- {
- "type": "element",
- "name": "g",
- "children": [
- {
- "type": "element",
- "name": "path",
- "attributes": {
- "d": "M6.10294,22C5.68819,22,5.18195,21.8532,4.58421,21.5595C4.46861,21.5027,4.39038,21.4658,4.34951,21.4488C3.49789,21.0943,2.74367,20.5941,2.08684,19.9484C0.695613,18.5806,0,16.9311,0,15C0,13.0689,0.695612,11.4194,2.08684,10.0516C3.24259,8.91537,4.59607,8.2511,6.14728,8.05878C6.34758,5.97414,7.22633,4.16634,8.78354,2.63539C10.5706,0.878463,12.7286,0,15.2573,0C17.2884,0,19.1146,0.595472,20.7358,1.78642C22.327,2.95528,23.4151,4.46783,24,6.32406L22.0568,6.91594C21.6024,5.47377,20.7561,4.29798,19.5181,3.38858C18.2579,2.46286,16.8377,2,15.2573,2C13.2903,2,11.6119,2.6832,10.222,4.04961C8.83217,5.41601,8.13725,7.06614,8.13725,9L8.13725,10L7.12009,10C5.71758,10,4.51932,10.4886,3.52532,11.4659C2.53132,12.4431,2.03431,13.6211,2.03431,15C2.03431,16.3789,2.53132,17.5569,3.52532,18.5341C3.99531,18.9962,4.53447,19.3538,5.14278,19.6071C5.2229,19.6405,5.33983,19.695,5.49356,19.7705C5.80505,19.9235,6.00818,20,6.10294,20L6.10294,22Z",
- "fill-rule": "evenodd",
- "fill": "#FF6A00",
- "fill-opacity": "1"
- }
- }
- ]
- },
- {
- "type": "element",
- "name": "g",
- "children": [
- {
- "type": "element",
- "name": "path",
- "attributes": {
- "d": "M20.18796103515625,11.66909C19.46346103515625,11.5762,18.72726103515625,11.52975,17.991011035156248,11.52975C16.728921035156247,11.52975,15.45515103515625,11.66909,14.23981103515625,11.91292C13.02447103515625,12.156749999999999,11.85588103515625,12.539909999999999,10.73402103515625,12.98113C9.98612103515625,13.306239999999999,9.23822103515625,13.69327,8.49031803515625,14.14223C7.99950790415625,14.43251,7.85927603515625,15.10595,8.15142503515625,15.59361L11.11966103515625,19.9478C11.45855103515625,20.4354,12.13634103515625,20.5747,12.627151035156249,20.2845C12.821921035156251,20.152900000000002,13.14523103515625,19.990299999999998,13.59708103515625,19.796799999999998C14.27487103515625,19.506500000000003,14.964341035156249,19.3091,15.68887103515625,19.169800000000002C16.413401035156248,19.018900000000002,17.14962103515625,18.926000000000002,17.93258103515625,18.926000000000002L20.071061035156248,11.715530000000001L20.18796103515625,11.66909ZM22.91076103515625,12.20319L22.525161035156252,8L20.18796103515625,11.6807C20.72556103515625,11.72714,21.21636103515625,11.82003,21.74216103515625,11.92453C21.74216103515625,11.91679,22.13166103515625,12.00968,22.91076103515625,12.20319ZM18.09616103515625,18.9724L17.06782103515625,22.4557L18.773961035156248,24L21.11116103515625,23.465899999999998L21.788961035156248,19.5414C21.298161035156248,19.402,20.81896103515625,19.2511,20.32816103515625,19.1582C19.60366103515625,19.076900000000002,18.86746103515625,18.9724,18.09616103515625,18.9724ZM27.49166103515625,14.14223C26.74376103515625,13.69327,25.99586103515625,13.306239999999999,25.24796103515625,12.98113C24.52346103515625,12.69086,23.74046103515625,12.40058,22.95756103515625,12.20319L22.95756103515625,12.40058L21.69546103515625,19.5646C21.89416103515625,19.6575,22.139561035156248,19.7039,22.32646103515625,19.8084C22.77836103515625,20.0019,23.101661035156248,20.1645,23.29646103515625,20.2961C23.78726103515625,20.586399999999998,24.51176103515625,20.4354,24.80396103515625,19.959400000000002L27.77216103515625,15.605229999999999C28.16946103515625,15.05951,28.02926103515625,14.43251,27.49166103515625,14.14223Z",
- "fill": "#FF6A00",
- "fill-opacity": "1"
- }
- }
- ]
- },
- {
- "type": "element",
- "name": "g",
- "children": [
- {
- "type": "element",
- "name": "path",
- "attributes": {
- "d": "M35.785,3.8624638671875L50.233000000000004,3.8624638671875L50.233000000000004,5.9204638671875L35.785,5.9204638671875L35.785,3.8624638671875ZM43.156,11.5904638671875Q42.106,13.4594638671875,40.7515,15.4754638671875Q39.397,17.4914638671875,38.599000000000004,18.3104638671875L46.978,17.5334638671875Q45.382,15.0974638671875,44.479,13.8794638671875L46.306,12.7034638671875Q47.397999999999996,14.1104638671875,48.9835,16.3784638671875Q50.569,18.6464638671875,51.492999999999995,20.1794638671875L49.54,21.6494638671875Q49.057,20.8094638671875,48.238,19.5074638671875Q46.243,19.6334638671875,42.82,19.9274638671875Q39.397,20.2214638671875,37.024,20.4734638671875L36.184,20.5784638671875L35.47,20.6834638671875L34.84,18.5624638671875Q35.281,18.4154638671875,35.512,18.2579638671875Q35.743,18.1004638671875,35.9005,17.963963867187502Q36.058,17.8274638671875,36.121,17.7644638671875Q37.087,16.840463867187502,38.305,15.1079638671875Q39.522999999999996,13.3754638671875,40.531,11.5904638671875L34,11.5904638671875L34,9.5114638671875L52.018,9.5114638671875L52.018,11.5904638671875L43.156,11.5904638671875ZM62.203,10.9814638671875L62.203,12.7244638671875L60.25,12.7244638671875L60.25,2.5814638671875L62.203,2.6654638671875L62.203,10.4144638671875Q63.19,8.6294638671875,64.051,6.4139638671875Q64.912,4.1984638671875,65.28999999999999,2.3504638671875L67.348,2.8334628671875Q67.15899999999999,3.7784638671875,66.80199999999999,4.9754638671875L72.619,4.9754638671875L72.619,6.9704638671875L66.13,6.9704638671875Q65.143,9.7004638671875,63.778,12.0524638671875L62.203,10.9814638671875ZM56.113,3.3794638671875L58.045,3.4634638671875L58.045,12.1784638671875L56.113,12.1784638671875L56.113,3.3794638671875ZM67.495,7.3064638671875Q68.251,7.8944638671875,69.469,9.1229638671875Q70.687,10.3514638671875,71.40100000000001,11.2334638671875L69.84700000000001,12.7454638671875Q69.238,11.9684638671875,68.083,10.7714638671875Q66.928,9.5744638671875,66.025,8.7134638671875L67.495,7.3064638671875ZM70.834,13.3754638671875L70.834,18.9194638671875L73.06,18.9194638671875L73.06,20.8094638671875L54.307,20.8094638671875L54.307,18.9194638671875L56.491,18.9194638671875L56.491,13.3754638671875L70.834,13.3754638671875ZM60.733000000000004,15.2444638671875L58.465,15.2444638671875L58.465,18.9194638671875L60.733000000000004,18.9194638671875L60.733000000000004,15.2444638671875ZM62.581,18.9194638671875L64.765,18.9194638671875L64.765,15.2444638671875L62.581,15.2444638671875L62.581,18.9194638671875ZM66.592,18.9194638671875L68.881,18.9194638671875L68.881,15.2444638671875L66.592,15.2444638671875L66.592,18.9194638671875ZM80.578,11.0444638671875L80.893,12.4514638671875L79.48599999999999,13.0814638671875L79.48599999999999,19.0874638671875Q79.48599999999999,20.0114638671875,79.2655,20.4629638671875Q79.045,20.9144638671875,78.52000000000001,21.1034638671875Q77.995,21.2924638671875,76.90299999999999,21.3974638671875L76.021,21.4814638671875L75.43299999999999,19.4864638671875L76.462,19.4024638671875Q76.987,19.3604638671875,77.197,19.2974638671875Q77.407,19.2344638671875,77.4805,19.0559638671875Q77.554,18.8774638671875,77.554,18.4364638671875L77.554,13.9004638671875Q76.189,14.4464638671875,75.202,14.7824638671875L74.74000000000001,12.7244638671875Q75.916,12.3464638671875,77.554,11.6744638671875L77.554,8.1464638671875L75.34899999999999,8.1464638671875L75.34899999999999,6.1094638671875L77.554,6.1094638671875L77.554,2.4974628671875L79.48599999999999,2.5814638671875L79.48599999999999,6.1094638671875L81.03999999999999,6.1094638671875L81.03999999999999,8.1464638671875L79.48599999999999,8.1464638671875L79.48599999999999,10.8344638671875L80.431,10.3934638671875L80.578,11.0444638671875ZM83.56,6.6764638671875L83.56,9.0074638671875L81.565,9.0074638671875L81.565,4.7444638671875L86.24799999999999,4.7444638671875Q85.84899999999999,3.3794638671875,85.618,2.7494638671875L87.655,2.4974628671875Q87.991,3.2744638671875,88.432,4.7444638671875L93.094,4.7444638671875L93.094,9.0074638671875L91.162,9.0074638671875L91.162,6.6764638671875L83.56,6.6764638671875ZM86.731,9.3434638671875Q85.807,10.2674638671875,84.7465,11.1284638671875Q83.686,11.9894638671875,82.15299999999999,13.1234638671875L81.082,11.5064638671875Q83.455,9.9524638671875,85.408,7.9154638671875L86.731,9.3434638671875ZM88.852,7.9154638671875Q89.755,8.5244638671875,91.3615,9.731963867187499Q92.968,10.9394638671875,93.703,11.5694638671875L92.632,13.3334638671875Q91.771,12.5354638671875,90.217,11.3384638671875Q88.663,10.1414638671875,87.718,9.5114638671875L88.852,7.9154638671875ZM92.107,15.2444638671875L88.285,15.2444638671875L88.285,18.7094638671875L93.577,18.7094638671875L93.577,20.5994638671875L80.935,20.5994638671875L80.935,18.7094638671875L86.164,18.7094638671875L86.164,15.2444638671875L82.3,15.2444638671875L82.3,13.3334638671875L92.107,13.3334638671875L92.107,15.2444638671875Z",
- "fill": "#FF6A00",
- "fill-opacity": "1"
- }
- }
- ]
- }
- ]
- }
- ]
- },
- "name": "AliyunBigIcon"
+ "icon": {
+ "type": "element",
+ "isRootNode": true,
+ "name": "svg",
+ "attributes": {
+ "xmlns": "http://www.w3.org/2000/svg",
+ "xmlns:xlink": "http://www.w3.org/1999/xlink",
+ "fill": "none",
+ "version": "1.1",
+ "width": "96",
+ "height": "24",
+ "viewBox": "0 0 96 24"
+ },
+ "children": [
+ {
+ "type": "element",
+ "name": "g",
+ "attributes": {},
+ "children": [
+ {
+ "type": "element",
+ "name": "g",
+ "attributes": {},
+ "children": [
+ {
+ "type": "element",
+ "name": "path",
+ "attributes": {
+ "d": "M6.10294,22C5.68819,22,5.18195,21.8532,4.58421,21.5595C4.46861,21.5027,4.39038,21.4658,4.34951,21.4488C3.49789,21.0943,2.74367,20.5941,2.08684,19.9484C0.695613,18.5806,0,16.9311,0,15C0,13.0689,0.695612,11.4194,2.08684,10.0516C3.24259,8.91537,4.59607,8.2511,6.14728,8.05878C6.34758,5.97414,7.22633,4.16634,8.78354,2.63539C10.5706,0.878463,12.7286,0,15.2573,0C17.2884,0,19.1146,0.595472,20.7358,1.78642C22.327,2.95528,23.4151,4.46783,24,6.32406L22.0568,6.91594C21.6024,5.47377,20.7561,4.29798,19.5181,3.38858C18.2579,2.46286,16.8377,2,15.2573,2C13.2903,2,11.6119,2.6832,10.222,4.04961C8.83217,5.41601,8.13725,7.06614,8.13725,9L8.13725,10L7.12009,10C5.71758,10,4.51932,10.4886,3.52532,11.4659C2.53132,12.4431,2.03431,13.6211,2.03431,15C2.03431,16.3789,2.53132,17.5569,3.52532,18.5341C3.99531,18.9962,4.53447,19.3538,5.14278,19.6071C5.2229,19.6405,5.33983,19.695,5.49356,19.7705C5.80505,19.9235,6.00818,20,6.10294,20L6.10294,22Z",
+ "fill-rule": "evenodd",
+ "fill": "#FF6A00",
+ "fill-opacity": "1"
+ },
+ "children": []
+ }
+ ]
+ },
+ {
+ "type": "element",
+ "name": "g",
+ "attributes": {},
+ "children": [
+ {
+ "type": "element",
+ "name": "path",
+ "attributes": {
+ "d": "M20.18796103515625,11.66909C19.46346103515625,11.5762,18.72726103515625,11.52975,17.991011035156248,11.52975C16.728921035156247,11.52975,15.45515103515625,11.66909,14.23981103515625,11.91292C13.02447103515625,12.156749999999999,11.85588103515625,12.539909999999999,10.73402103515625,12.98113C9.98612103515625,13.306239999999999,9.23822103515625,13.69327,8.49031803515625,14.14223C7.99950790415625,14.43251,7.85927603515625,15.10595,8.15142503515625,15.59361L11.11966103515625,19.9478C11.45855103515625,20.4354,12.13634103515625,20.5747,12.627151035156249,20.2845C12.821921035156251,20.152900000000002,13.14523103515625,19.990299999999998,13.59708103515625,19.796799999999998C14.27487103515625,19.506500000000003,14.964341035156249,19.3091,15.68887103515625,19.169800000000002C16.413401035156248,19.018900000000002,17.14962103515625,18.926000000000002,17.93258103515625,18.926000000000002L20.071061035156248,11.715530000000001L20.18796103515625,11.66909ZM22.91076103515625,12.20319L22.525161035156252,8L20.18796103515625,11.6807C20.72556103515625,11.72714,21.21636103515625,11.82003,21.74216103515625,11.92453C21.74216103515625,11.91679,22.13166103515625,12.00968,22.91076103515625,12.20319ZM18.09616103515625,18.9724L17.06782103515625,22.4557L18.773961035156248,24L21.11116103515625,23.465899999999998L21.788961035156248,19.5414C21.298161035156248,19.402,20.81896103515625,19.2511,20.32816103515625,19.1582C19.60366103515625,19.076900000000002,18.86746103515625,18.9724,18.09616103515625,18.9724ZM27.49166103515625,14.14223C26.74376103515625,13.69327,25.99586103515625,13.306239999999999,25.24796103515625,12.98113C24.52346103515625,12.69086,23.74046103515625,12.40058,22.95756103515625,12.20319L22.95756103515625,12.40058L21.69546103515625,19.5646C21.89416103515625,19.6575,22.139561035156248,19.7039,22.32646103515625,19.8084C22.77836103515625,20.0019,23.101661035156248,20.1645,23.29646103515625,20.2961C23.78726103515625,20.586399999999998,24.51176103515625,20.4354,24.80396103515625,19.959400000000002L27.77216103515625,15.605229999999999C28.16946103515625,15.05951,28.02926103515625,14.43251,27.49166103515625,14.14223Z",
+ "fill": "#FF6A00",
+ "fill-opacity": "1"
+ },
+ "children": []
+ }
+ ]
+ },
+ {
+ "type": "element",
+ "name": "g",
+ "attributes": {},
+ "children": [
+ {
+ "type": "element",
+ "name": "path",
+ "attributes": {
+ "d": "M35.785,3.8624638671875L50.233000000000004,3.8624638671875L50.233000000000004,5.9204638671875L35.785,5.9204638671875L35.785,3.8624638671875ZM43.156,11.5904638671875Q42.106,13.4594638671875,40.7515,15.4754638671875Q39.397,17.4914638671875,38.599000000000004,18.3104638671875L46.978,17.5334638671875Q45.382,15.0974638671875,44.479,13.8794638671875L46.306,12.7034638671875Q47.397999999999996,14.1104638671875,48.9835,16.3784638671875Q50.569,18.6464638671875,51.492999999999995,20.1794638671875L49.54,21.6494638671875Q49.057,20.8094638671875,48.238,19.5074638671875Q46.243,19.6334638671875,42.82,19.9274638671875Q39.397,20.2214638671875,37.024,20.4734638671875L36.184,20.5784638671875L35.47,20.6834638671875L34.84,18.5624638671875Q35.281,18.4154638671875,35.512,18.2579638671875Q35.743,18.1004638671875,35.9005,17.963963867187502Q36.058,17.8274638671875,36.121,17.7644638671875Q37.087,16.840463867187502,38.305,15.1079638671875Q39.522999999999996,13.3754638671875,40.531,11.5904638671875L34,11.5904638671875L34,9.5114638671875L52.018,9.5114638671875L52.018,11.5904638671875L43.156,11.5904638671875ZM62.203,10.9814638671875L62.203,12.7244638671875L60.25,12.7244638671875L60.25,2.5814638671875L62.203,2.6654638671875L62.203,10.4144638671875Q63.19,8.6294638671875,64.051,6.4139638671875Q64.912,4.1984638671875,65.28999999999999,2.3504638671875L67.348,2.8334628671875Q67.15899999999999,3.7784638671875,66.80199999999999,4.9754638671875L72.619,4.9754638671875L72.619,6.9704638671875L66.13,6.9704638671875Q65.143,9.7004638671875,63.778,12.0524638671875L62.203,10.9814638671875ZM56.113,3.3794638671875L58.045,3.4634638671875L58.045,12.1784638671875L56.113,12.1784638671875L56.113,3.3794638671875ZM67.495,7.3064638671875Q68.251,7.8944638671875,69.469,9.1229638671875Q70.687,10.3514638671875,71.40100000000001,11.2334638671875L69.84700000000001,12.7454638671875Q69.238,11.9684638671875,68.083,10.7714638671875Q66.928,9.5744638671875,66.025,8.7134638671875L67.495,7.3064638671875ZM70.834,13.3754638671875L70.834,18.9194638671875L73.06,18.9194638671875L73.06,20.8094638671875L54.307,20.8094638671875L54.307,18.9194638671875L56.491,18.9194638671875L56.491,13.3754638671875L70.834,13.3754638671875ZM60.733000000000004,15.2444638671875L58.465,15.2444638671875L58.465,18.9194638671875L60.733000000000004,18.9194638671875L60.733000000000004,15.2444638671875ZM62.581,18.9194638671875L64.765,18.9194638671875L64.765,15.2444638671875L62.581,15.2444638671875L62.581,18.9194638671875ZM66.592,18.9194638671875L68.881,18.9194638671875L68.881,15.2444638671875L66.592,15.2444638671875L66.592,18.9194638671875ZM80.578,11.0444638671875L80.893,12.4514638671875L79.48599999999999,13.0814638671875L79.48599999999999,19.0874638671875Q79.48599999999999,20.0114638671875,79.2655,20.4629638671875Q79.045,20.9144638671875,78.52000000000001,21.1034638671875Q77.995,21.2924638671875,76.90299999999999,21.3974638671875L76.021,21.4814638671875L75.43299999999999,19.4864638671875L76.462,19.4024638671875Q76.987,19.3604638671875,77.197,19.2974638671875Q77.407,19.2344638671875,77.4805,19.0559638671875Q77.554,18.8774638671875,77.554,18.4364638671875L77.554,13.9004638671875Q76.189,14.4464638671875,75.202,14.7824638671875L74.74000000000001,12.7244638671875Q75.916,12.3464638671875,77.554,11.6744638671875L77.554,8.1464638671875L75.34899999999999,8.1464638671875L75.34899999999999,6.1094638671875L77.554,6.1094638671875L77.554,2.4974628671875L79.48599999999999,2.5814638671875L79.48599999999999,6.1094638671875L81.03999999999999,6.1094638671875L81.03999999999999,8.1464638671875L79.48599999999999,8.1464638671875L79.48599999999999,10.8344638671875L80.431,10.3934638671875L80.578,11.0444638671875ZM83.56,6.6764638671875L83.56,9.0074638671875L81.565,9.0074638671875L81.565,4.7444638671875L86.24799999999999,4.7444638671875Q85.84899999999999,3.3794638671875,85.618,2.7494638671875L87.655,2.4974628671875Q87.991,3.2744638671875,88.432,4.7444638671875L93.094,4.7444638671875L93.094,9.0074638671875L91.162,9.0074638671875L91.162,6.6764638671875L83.56,6.6764638671875ZM86.731,9.3434638671875Q85.807,10.2674638671875,84.7465,11.1284638671875Q83.686,11.9894638671875,82.15299999999999,13.1234638671875L81.082,11.5064638671875Q83.455,9.9524638671875,85.408,7.9154638671875L86.731,9.3434638671875ZM88.852,7.9154638671875Q89.755,8.5244638671875,91.3615,9.731963867187499Q92.968,10.9394638671875,93.703,11.5694638671875L92.632,13.3334638671875Q91.771,12.5354638671875,90.217,11.3384638671875Q88.663,10.1414638671875,87.718,9.5114638671875L88.852,7.9154638671875ZM92.107,15.2444638671875L88.285,15.2444638671875L88.285,18.7094638671875L93.577,18.7094638671875L93.577,20.5994638671875L80.935,20.5994638671875L80.935,18.7094638671875L86.164,18.7094638671875L86.164,15.2444638671875L82.3,15.2444638671875L82.3,13.3334638671875L92.107,13.3334638671875L92.107,15.2444638671875Z",
+ "fill": "#FF6A00",
+ "fill-opacity": "1"
+ },
+ "children": []
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "name": "AliyunIconBig"
}
diff --git a/web/app/components/base/image-uploader/image-list.tsx b/web/app/components/base/image-uploader/image-list.tsx
index 758ffe99d5..3b5f6dee9c 100644
--- a/web/app/components/base/image-uploader/image-list.tsx
+++ b/web/app/components/base/image-uploader/image-list.tsx
@@ -62,7 +62,7 @@ const ImageList: FC = ({
{item.progress === -1 && (
onReUpload && onReUpload(item._id)}
+ onClick={() => onReUpload?.(item._id)}
/>
)}
@@ -122,7 +122,7 @@ const ImageList: FC
= ({
'rounded-2xl shadow-lg hover:bg-state-base-hover',
item.progress === -1 ? 'flex' : 'hidden group-hover:flex',
)}
- onClick={() => onRemove && onRemove(item._id)}
+ onClick={() => onRemove?.(item._id)}
>
diff --git a/web/app/components/base/image-uploader/image-preview.tsx b/web/app/components/base/image-uploader/image-preview.tsx
index e67edaa3ca..53c22e344f 100644
--- a/web/app/components/base/image-uploader/image-preview.tsx
+++ b/web/app/components/base/image-uploader/image-preview.tsx
@@ -20,7 +20,7 @@ const isBase64 = (str: string): boolean => {
try {
return btoa(atob(str)) === str
}
- catch (err) {
+ catch {
return false
}
}
diff --git a/web/app/components/base/inline-delete-confirm/index.spec.tsx b/web/app/components/base/inline-delete-confirm/index.spec.tsx
new file mode 100644
index 0000000000..c113c4ade9
--- /dev/null
+++ b/web/app/components/base/inline-delete-confirm/index.spec.tsx
@@ -0,0 +1,152 @@
+import React from 'react'
+import { cleanup, fireEvent, render } from '@testing-library/react'
+import InlineDeleteConfirm from './index'
+
+// Mock react-i18next
+jest.mock('react-i18next', () => ({
+ useTranslation: () => ({
+ t: (key: string) => {
+ const translations: Record = {
+ 'common.operation.deleteConfirmTitle': 'Delete?',
+ 'common.operation.yes': 'Yes',
+ 'common.operation.no': 'No',
+ 'common.operation.confirmAction': 'Please confirm your action.',
+ }
+ return translations[key] || key
+ },
+ }),
+}))
+
+afterEach(cleanup)
+
+describe('InlineDeleteConfirm', () => {
+ describe('Rendering', () => {
+ test('should render with default text', () => {
+ const onConfirm = jest.fn()
+ const onCancel = jest.fn()
+ const { getByText } = render(
+ ,
+ )
+
+ expect(getByText('Delete?')).toBeInTheDocument()
+ expect(getByText('No')).toBeInTheDocument()
+ expect(getByText('Yes')).toBeInTheDocument()
+ })
+
+ test('should render with custom text', () => {
+ const onConfirm = jest.fn()
+ const onCancel = jest.fn()
+ const { getByText } = render(
+ ,
+ )
+
+ expect(getByText('Remove?')).toBeInTheDocument()
+ expect(getByText('Cancel')).toBeInTheDocument()
+ expect(getByText('Confirm')).toBeInTheDocument()
+ })
+
+ test('should have proper ARIA attributes', () => {
+ const onConfirm = jest.fn()
+ const onCancel = jest.fn()
+ const { container } = render(
+ ,
+ )
+
+ const wrapper = container.firstChild as HTMLElement
+ expect(wrapper).toHaveAttribute('aria-labelledby', 'inline-delete-confirm-title')
+ expect(wrapper).toHaveAttribute('aria-describedby', 'inline-delete-confirm-description')
+ })
+ })
+
+ describe('Button interactions', () => {
+ test('should call onCancel when cancel button is clicked', () => {
+ const onConfirm = jest.fn()
+ const onCancel = jest.fn()
+ const { getByText } = render(
+ ,
+ )
+
+ fireEvent.click(getByText('No'))
+ expect(onCancel).toHaveBeenCalledTimes(1)
+ expect(onConfirm).not.toHaveBeenCalled()
+ })
+
+ test('should call onConfirm when confirm button is clicked', () => {
+ const onConfirm = jest.fn()
+ const onCancel = jest.fn()
+ const { getByText } = render(
+ ,
+ )
+
+ fireEvent.click(getByText('Yes'))
+ expect(onConfirm).toHaveBeenCalledTimes(1)
+ expect(onCancel).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('Variant prop', () => {
+ test('should render with delete variant by default', () => {
+ const onConfirm = jest.fn()
+ const onCancel = jest.fn()
+ const { getByText } = render(
+ ,
+ )
+
+ const confirmButton = getByText('Yes').closest('button')
+ expect(confirmButton?.className).toContain('btn-destructive')
+ })
+
+ test('should render without destructive class for warning variant', () => {
+ const onConfirm = jest.fn()
+ const onCancel = jest.fn()
+ const { getByText } = render(
+ ,
+ )
+
+ const confirmButton = getByText('Yes').closest('button')
+ expect(confirmButton?.className).not.toContain('btn-destructive')
+ })
+
+ test('should render without destructive class for info variant', () => {
+ const onConfirm = jest.fn()
+ const onCancel = jest.fn()
+ const { getByText } = render(
+ ,
+ )
+
+ const confirmButton = getByText('Yes').closest('button')
+ expect(confirmButton?.className).not.toContain('btn-destructive')
+ })
+ })
+
+ describe('Custom className', () => {
+ test('should apply custom className to wrapper', () => {
+ const onConfirm = jest.fn()
+ const onCancel = jest.fn()
+ const { container } = render(
+ ,
+ )
+
+ const wrapper = container.firstChild as HTMLElement
+ expect(wrapper.className).toContain('custom-class')
+ })
+ })
+})
diff --git a/web/app/components/base/inline-delete-confirm/index.tsx b/web/app/components/base/inline-delete-confirm/index.tsx
new file mode 100644
index 0000000000..eb671609cf
--- /dev/null
+++ b/web/app/components/base/inline-delete-confirm/index.tsx
@@ -0,0 +1,83 @@
+'use client'
+import type { FC } from 'react'
+import { useTranslation } from 'react-i18next'
+import Button from '@/app/components/base/button'
+import cn from '@/utils/classnames'
+
+export type InlineDeleteConfirmProps = {
+ title?: string
+ confirmText?: string
+ cancelText?: string
+ onConfirm: () => void
+ onCancel: () => void
+ className?: string
+ variant?: 'delete' | 'warning' | 'info'
+}
+
+const InlineDeleteConfirm: FC = ({
+ title,
+ confirmText,
+ cancelText,
+ onConfirm,
+ onCancel,
+ className,
+ variant = 'delete',
+}) => {
+ const { t } = useTranslation()
+
+ const titleText = title || t('common.operation.deleteConfirmTitle', 'Delete?')
+ const confirmTxt = confirmText || t('common.operation.yes', 'Yes')
+ const cancelTxt = cancelText || t('common.operation.no', 'No')
+
+ return (
+
+
+ {titleText}
+
+
+
+
+ {cancelTxt}
+
+
+ {confirmTxt}
+
+
+
+
+ {t('common.operation.confirmAction', 'Please confirm your action.')}
+
+
+ )
+}
+
+InlineDeleteConfirm.displayName = 'InlineDeleteConfirm'
+
+export default InlineDeleteConfirm
diff --git a/web/app/components/base/markdown-blocks/code-block.tsx b/web/app/components/base/markdown-blocks/code-block.tsx
index 48de8bf4ab..bc41c65fd5 100644
--- a/web/app/components/base/markdown-blocks/code-block.tsx
+++ b/web/app/components/base/markdown-blocks/code-block.tsx
@@ -8,12 +8,14 @@ import {
import ActionButton from '@/app/components/base/action-button'
import CopyIcon from '@/app/components/base/copy-icon'
import SVGBtn from '@/app/components/base/svg'
-import Flowchart from '@/app/components/base/mermaid'
import { Theme } from '@/types/app'
import useTheme from '@/hooks/use-theme'
import SVGRenderer from '../svg-gallery' // Assumes svg-gallery.tsx is in /base directory
import MarkdownMusic from '@/app/components/base/markdown-blocks/music'
import ErrorBoundary from '@/app/components/base/markdown/error-boundary'
+import dynamic from 'next/dynamic'
+
+const Flowchart = dynamic(() => import('@/app/components/base/mermaid'), { ssr: false })
// Available language https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_HLJS.MD
const capitalizationLanguageNameMap: Record = {
@@ -125,7 +127,7 @@ const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any
// Store event handlers in useMemo to avoid recreating them
const echartsEvents = useMemo(() => ({
- finished: (params: EChartsEventParams) => {
+ finished: (_params: EChartsEventParams) => {
// Limit finished event frequency to avoid infinite loops
finishedEventCountRef.current++
if (finishedEventCountRef.current > 3) {
diff --git a/web/app/components/base/markdown/index.tsx b/web/app/components/base/markdown/index.tsx
index bab5ac8eba..19f39d8aaa 100644
--- a/web/app/components/base/markdown/index.tsx
+++ b/web/app/components/base/markdown/index.tsx
@@ -1,25 +1,11 @@
-import ReactMarkdown from 'react-markdown'
+import dynamic from 'next/dynamic'
import 'katex/dist/katex.min.css'
-import RemarkMath from 'remark-math'
-import RemarkBreaks from 'remark-breaks'
-import RehypeKatex from 'rehype-katex'
-import RemarkGfm from 'remark-gfm'
-import RehypeRaw from 'rehype-raw'
import { flow } from 'lodash-es'
import cn from '@/utils/classnames'
-import { customUrlTransform, preprocessLaTeX, preprocessThinkTag } from './markdown-utils'
-import {
- AudioBlock,
- CodeBlock,
- Img,
- Link,
- MarkdownButton,
- MarkdownForm,
- Paragraph,
- ScriptBlock,
- ThinkBlock,
- VideoBlock,
-} from '@/app/components/base/markdown-blocks'
+import { preprocessLaTeX, preprocessThinkTag } from './markdown-utils'
+import type { ReactMarkdownWrapperProps } from './react-markdown-wrapper'
+
+const ReactMarkdown = dynamic(() => import('./react-markdown-wrapper').then(mod => mod.ReactMarkdownWrapper), { ssr: false })
/**
* @fileoverview Main Markdown rendering component.
@@ -31,9 +17,7 @@ import {
export type MarkdownProps = {
content: string
className?: string
- customDisallowedElements?: string[]
- customComponents?: Record>
-}
+} & Pick
export const Markdown = (props: MarkdownProps) => {
const { customComponents = {} } = props
@@ -44,53 +28,7 @@ export const Markdown = (props: MarkdownProps) => {
return (
- {
- return (tree: any) => {
- const iterate = (node: any) => {
- if (node.type === 'element' && node.properties?.ref)
- delete node.properties.ref
-
- if (node.type === 'element' && !/^[a-z][a-z0-9]*$/i.test(node.tagName)) {
- node.type = 'text'
- node.value = `<${node.tagName}`
- }
-
- if (node.children)
- node.children.forEach(iterate)
- }
- tree.children.forEach(iterate)
- }
- },
- ]}
- urlTransform={customUrlTransform}
- disallowedElements={['iframe', 'head', 'html', 'meta', 'link', 'style', 'body', ...(props.customDisallowedElements || [])]}
- components={{
- code: CodeBlock,
- img: Img,
- video: VideoBlock,
- audio: AudioBlock,
- a: Link,
- p: Paragraph,
- button: MarkdownButton,
- form: MarkdownForm,
- script: ScriptBlock as any,
- details: ThinkBlock,
- ...customComponents,
- }}
- >
- {/* Markdown detect has problem. */}
- {latexContent}
-
+
)
}
diff --git a/web/app/components/base/markdown/react-markdown-wrapper.tsx b/web/app/components/base/markdown/react-markdown-wrapper.tsx
new file mode 100644
index 0000000000..054b5f66cb
--- /dev/null
+++ b/web/app/components/base/markdown/react-markdown-wrapper.tsx
@@ -0,0 +1,82 @@
+import ReactMarkdown from 'react-markdown'
+import RemarkMath from 'remark-math'
+import RemarkBreaks from 'remark-breaks'
+import RehypeKatex from 'rehype-katex'
+import RemarkGfm from 'remark-gfm'
+import RehypeRaw from 'rehype-raw'
+import AudioBlock from '@/app/components/base/markdown-blocks/audio-block'
+import Img from '@/app/components/base/markdown-blocks/img'
+import Link from '@/app/components/base/markdown-blocks/link'
+import MarkdownButton from '@/app/components/base/markdown-blocks/button'
+import MarkdownForm from '@/app/components/base/markdown-blocks/form'
+import Paragraph from '@/app/components/base/markdown-blocks/paragraph'
+import ScriptBlock from '@/app/components/base/markdown-blocks/script-block'
+import ThinkBlock from '@/app/components/base/markdown-blocks/think-block'
+import VideoBlock from '@/app/components/base/markdown-blocks/video-block'
+import { customUrlTransform } from './markdown-utils'
+
+import type { FC } from 'react'
+
+import dynamic from 'next/dynamic'
+
+const CodeBlock = dynamic(() => import('@/app/components/base/markdown-blocks/code-block'), { ssr: false })
+
+export type ReactMarkdownWrapperProps = {
+ latexContent: any
+ customDisallowedElements?: string[]
+ customComponents?: Record>
+}
+
+export const ReactMarkdownWrapper: FC = (props) => {
+ const { customComponents, latexContent } = props
+
+ return (
+ {
+ return (tree: any) => {
+ const iterate = (node: any) => {
+ if (node.type === 'element' && node.properties?.ref)
+ delete node.properties.ref
+
+ if (node.type === 'element' && !/^[a-z][a-z0-9]*$/i.test(node.tagName)) {
+ node.type = 'text'
+ node.value = `<${node.tagName}`
+ }
+
+ if (node.children)
+ node.children.forEach(iterate)
+ }
+ tree.children.forEach(iterate)
+ }
+ },
+ ]}
+ urlTransform={customUrlTransform}
+ disallowedElements={['iframe', 'head', 'html', 'meta', 'link', 'style', 'body', ...(props.customDisallowedElements || [])]}
+ components={{
+ code: CodeBlock,
+ img: Img,
+ video: VideoBlock,
+ audio: AudioBlock,
+ a: Link,
+ p: Paragraph,
+ button: MarkdownButton,
+ form: MarkdownForm,
+ script: ScriptBlock as any,
+ details: ThinkBlock,
+ ...customComponents,
+ }}
+ >
+ {/* Markdown detect has problem. */}
+ {latexContent}
+
+ )
+}
diff --git a/web/app/components/base/mermaid/utils.ts b/web/app/components/base/mermaid/utils.ts
index 9d56494227..7e59869de1 100644
--- a/web/app/components/base/mermaid/utils.ts
+++ b/web/app/components/base/mermaid/utils.ts
@@ -60,7 +60,7 @@ export function svgToBase64(svgGraph: string): Promise {
reader.readAsDataURL(blob)
})
}
- catch (error) {
+ catch {
return Promise.resolve('')
}
}
diff --git a/web/app/components/base/pagination/hook.ts b/web/app/components/base/pagination/hook.ts
index 32a2af8013..9b9d86a4ef 100644
--- a/web/app/components/base/pagination/hook.ts
+++ b/web/app/components/base/pagination/hook.ts
@@ -10,9 +10,7 @@ const usePagination = ({
edgePageCount,
middlePagesSiblingCount,
}: IPaginationProps): IUsePagination => {
- const pages = new Array(totalPages)
- .fill(0)
- .map((_, i) => i + 1)
+ const pages = React.useMemo(() => Array.from({ length: totalPages }, (_, i) => i + 1), [totalPages])
const hasPreviousPage = currentPage > 1
const hasNextPage = currentPage < totalPages
diff --git a/web/app/components/base/pagination/index.tsx b/web/app/components/base/pagination/index.tsx
index 8126f663dd..e0c02df253 100644
--- a/web/app/components/base/pagination/index.tsx
+++ b/web/app/components/base/pagination/index.tsx
@@ -57,7 +57,34 @@ const CustomizedPagination: FC = ({
if (isNaN(Number.parseInt(value)))
return setInputValue('')
setInputValue(Number.parseInt(value))
- handlePaging(value)
+ }
+
+ const handleInputConfirm = () => {
+ if (inputValue !== '' && String(inputValue) !== String(current + 1)) {
+ handlePaging(String(inputValue))
+ return
+ }
+
+ if (inputValue === '')
+ setInputValue(current + 1)
+
+ setShowInput(false)
+ }
+
+ const handleInputKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ e.preventDefault()
+ handleInputConfirm()
+ }
+ else if (e.key === 'Escape') {
+ e.preventDefault()
+ setInputValue(current + 1)
+ setShowInput(false)
+ }
+ }
+
+ const handleInputBlur = () => {
+ handleInputConfirm()
}
return (
@@ -105,7 +132,8 @@ const CustomizedPagination: FC = ({
autoFocus
value={inputValue}
onChange={handleInputChange}
- onBlur={() => setShowInput(false)}
+ onKeyDown={handleInputKeyDown}
+ onBlur={handleInputBlur}
/>
)}
(null)
const onMouseEnter = (isOpen: boolean) => {
- timeOutRef.current && window.clearTimeout(timeOutRef.current)
- !isOpen && buttonRef.current?.click()
+ if (timeOutRef.current != null)
+ window.clearTimeout(timeOutRef.current)
+ if (!isOpen)
+ buttonRef.current?.click()
}
const onMouseLeave = (isOpen: boolean) => {
timeOutRef.current = window.setTimeout(() => {
- isOpen && buttonRef.current?.click()
+ if (isOpen)
+ buttonRef.current?.click()
}, timeoutDuration)
}
diff --git a/web/app/components/base/select/locale-signin.tsx b/web/app/components/base/select/locale-signin.tsx
index 2d487c4be3..2ec491f4bf 100644
--- a/web/app/components/base/select/locale-signin.tsx
+++ b/web/app/components/base/select/locale-signin.tsx
@@ -43,7 +43,7 @@ export default function LocaleSigninSelect({
className={'group flex w-full items-center rounded-lg px-3 py-2 text-sm text-text-secondary data-[active]:bg-state-base-hover'}
onClick={(evt) => {
evt.preventDefault()
- onChange && onChange(item.value)
+ onChange?.(item.value)
}}
>
{item.name}
diff --git a/web/app/components/base/select/locale.tsx b/web/app/components/base/select/locale.tsx
index cc5662f53b..2033488435 100644
--- a/web/app/components/base/select/locale.tsx
+++ b/web/app/components/base/select/locale.tsx
@@ -43,7 +43,7 @@ export default function Select({
className={'group flex w-full items-center rounded-lg px-3 py-2 text-sm text-text-secondary data-[active]:bg-state-base-hover'}
onClick={(evt) => {
evt.preventDefault()
- onChange && onChange(item.value)
+ onChange?.(item.value)
}}
>
{item.name}
diff --git a/web/app/components/base/tag-management/panel.tsx b/web/app/components/base/tag-management/panel.tsx
index 79031e9f8a..3cfb68fef0 100644
--- a/web/app/components/base/tag-management/panel.tsx
+++ b/web/app/components/base/tag-management/panel.tsx
@@ -97,10 +97,13 @@ const Panel = (props: PanelProps) => {
const removeTagIDs = value.filter(v => !selectedTagIDs.includes(v))
const selectedTags = tagList.filter(tag => selectedTagIDs.includes(tag.id))
onCacheUpdate(selectedTags)
- Promise.all([
- ...(addTagIDs.length ? [bind(addTagIDs)] : []),
- ...[removeTagIDs.length ? removeTagIDs.map(tagID => unbind(tagID)) : []],
- ]).finally(() => {
+ const operations: Promise[] = []
+ if (addTagIDs.length)
+ operations.push(bind(addTagIDs))
+ if (removeTagIDs.length)
+ operations.push(...removeTagIDs.map(tagID => unbind(tagID)))
+
+ Promise.all(operations).finally(() => {
if (onChange)
onChange()
})
diff --git a/web/app/components/base/voice-input/index.tsx b/web/app/components/base/voice-input/index.tsx
index 5a5400ad30..6587a61217 100644
--- a/web/app/components/base/voice-input/index.tsx
+++ b/web/app/components/base/voice-input/index.tsx
@@ -81,7 +81,8 @@ const VoiceInput = ({
setStartRecord(false)
setStartConvert(true)
recorder.current.stop()
- drawRecordId.current && cancelAnimationFrame(drawRecordId.current)
+ if (drawRecordId.current)
+ cancelAnimationFrame(drawRecordId.current)
drawRecordId.current = null
const canvas = canvasRef.current!
const ctx = ctxRef.current!
diff --git a/web/app/components/billing/pricing/footer.tsx b/web/app/components/billing/pricing/footer.tsx
index 4e3cdfee3d..fd713eb3da 100644
--- a/web/app/components/billing/pricing/footer.tsx
+++ b/web/app/components/billing/pricing/footer.tsx
@@ -2,19 +2,29 @@ import React from 'react'
import Link from 'next/link'
import { useTranslation } from 'react-i18next'
import { RiArrowRightUpLine } from '@remixicon/react'
+import { type Category, CategoryEnum } from '.'
+import cn from '@/utils/classnames'
type FooterProps = {
pricingPageURL: string
+ currentCategory: Category
}
const Footer = ({
pricingPageURL,
+ currentCategory,
}: FooterProps) => {
const { t } = useTranslation()
return (
-
+
+ {currentCategory === CategoryEnum.CLOUD && (
+
+ {t('billing.plansCommon.taxTip')}
+ {t('billing.plansCommon.taxTipSecond')}
+
+ )}
void
@@ -25,7 +30,7 @@ const Pricing: FC = ({
const { plan } = useProviderContext()
const { isCurrentWorkspaceManager } = useAppContext()
const [planRange, setPlanRange] = React.useState(PlanRange.monthly)
- const [currentCategory, setCurrentCategory] = useState('cloud')
+ const [currentCategory, setCurrentCategory] = useState(CategoryEnum.CLOUD)
const canPay = isCurrentWorkspaceManager
useKeyPress(['esc'], onCancel)
@@ -57,7 +62,7 @@ const Pricing: FC = ({
planRange={planRange}
canPay={canPay}
/>
-
+
diff --git a/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/uploader.tsx b/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/uploader.tsx
index 433b7851d7..57509b646f 100644
--- a/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/uploader.tsx
+++ b/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/uploader.tsx
@@ -34,7 +34,8 @@ const Uploader: FC = ({
const handleDragEnter = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
- e.target !== dragRef.current && setDragging(true)
+ if (e.target !== dragRef.current)
+ setDragging(true)
}
const handleDragOver = (e: DragEvent) => {
e.preventDefault()
@@ -43,7 +44,8 @@ const Uploader: FC = ({
const handleDragLeave = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
- e.target === dragRef.current && setDragging(false)
+ if (e.target === dragRef.current)
+ setDragging(false)
}
const handleDrop = (e: DragEvent) => {
e.preventDefault()
diff --git a/web/app/components/datasets/create/file-uploader/index.tsx b/web/app/components/datasets/create/file-uploader/index.tsx
index 4dfdbc4e96..e2bbad2776 100644
--- a/web/app/components/datasets/create/file-uploader/index.tsx
+++ b/web/app/components/datasets/create/file-uploader/index.tsx
@@ -185,7 +185,8 @@ const FileUploader = ({
const handleDragEnter = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
- e.target !== dragRef.current && setDragging(true)
+ if (e.target !== dragRef.current)
+ setDragging(true)
}
const handleDragOver = (e: DragEvent) => {
e.preventDefault()
@@ -194,7 +195,8 @@ const FileUploader = ({
const handleDragLeave = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
- e.target === dragRef.current && setDragging(false)
+ if (e.target === dragRef.current)
+ setDragging(false)
}
type FileWithPath = {
relativePath?: string
diff --git a/web/app/components/datasets/create/step-two/index.tsx b/web/app/components/datasets/create/step-two/index.tsx
index 650dc9425a..4c2e129cd2 100644
--- a/web/app/components/datasets/create/step-two/index.tsx
+++ b/web/app/components/datasets/create/step-two/index.tsx
@@ -568,9 +568,9 @@ const StepTwo = ({
params,
{
onSuccess(data) {
- updateIndexingTypeCache && updateIndexingTypeCache(indexType as string)
- updateResultCache && updateResultCache(data)
- updateRetrievalMethodCache && updateRetrievalMethodCache(retrievalConfig.search_method as string)
+ updateIndexingTypeCache?.(indexType as string)
+ updateResultCache?.(data)
+ updateRetrievalMethodCache?.(retrievalConfig.search_method as string)
},
},
)
@@ -578,17 +578,18 @@ const StepTwo = ({
else {
await createDocumentMutation.mutateAsync(params, {
onSuccess(data) {
- updateIndexingTypeCache && updateIndexingTypeCache(indexType as string)
- updateResultCache && updateResultCache(data)
- updateRetrievalMethodCache && updateRetrievalMethodCache(retrievalConfig.search_method as string)
+ updateIndexingTypeCache?.(indexType as string)
+ updateResultCache?.(data)
+ updateRetrievalMethodCache?.(retrievalConfig.search_method as string)
},
})
}
if (mutateDatasetRes)
mutateDatasetRes()
invalidDatasetList()
- onStepChange && onStepChange(+1)
- isSetting && onSave && onSave()
+ onStepChange?.(+1)
+ if (isSetting)
+ onSave?.()
}
useEffect(() => {
@@ -1026,7 +1027,7 @@ const StepTwo = ({
{!isSetting
? (
-
onStepChange && onStepChange(-1)}>
+ onStepChange?.(-1)}>
{t('datasetCreation.stepTwo.previousStep')}
diff --git a/web/app/components/datasets/create/website/jina-reader/base/options-wrap.tsx b/web/app/components/datasets/create/website/jina-reader/base/options-wrap.tsx
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/index.tsx
index 144decada5..ad05f0729b 100644
--- a/web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/index.tsx
+++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/index.tsx
@@ -7,7 +7,6 @@ import DocumentFileIcon from '@/app/components/datasets/common/document-file-ico
import cn from '@/utils/classnames'
import type { CustomFile as File, FileItem } from '@/models/datasets'
import { ToastContext } from '@/app/components/base/toast'
-import SimplePieChart from '@/app/components/base/simple-pie-chart'
import { upload } from '@/service/base'
import I18n from '@/context/i18n'
import { LanguagesSupported } from '@/i18n-config/language'
@@ -17,6 +16,9 @@ import useTheme from '@/hooks/use-theme'
import { useFileUploadConfig } from '@/service/use-common'
import { useDataSourceStore, useDataSourceStoreWithSelector } from '../store'
import produce from 'immer'
+import dynamic from 'next/dynamic'
+
+const SimplePieChart = dynamic(() => import('@/app/components/base/simple-pie-chart'), { ssr: false })
const FILES_NUMBER_LIMIT = 20
@@ -198,7 +200,8 @@ const LocalFile = ({
const handleDragEnter = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
- e.target !== dragRef.current && setDragging(true)
+ if (e.target !== dragRef.current)
+ setDragging(true)
}
const handleDragOver = (e: DragEvent) => {
e.preventDefault()
@@ -207,7 +210,8 @@ const LocalFile = ({
const handleDragLeave = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
- e.target === dragRef.current && setDragging(false)
+ if (e.target === dragRef.current)
+ setDragging(false)
}
const handleDrop = useCallback((e: DragEvent) => {
diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/crawled-result.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/crawled-result.tsx
index 28c9ae456e..cd410c4d1e 100644
--- a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/crawled-result.tsx
+++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/crawled-result.tsx
@@ -45,10 +45,13 @@ const CrawledResult = ({
const handleItemCheckChange = useCallback((item: CrawlResultItem) => {
return (checked: boolean) => {
- if (checked)
- isMultipleChoice ? onSelectedChange([...checkedList, item]) : onSelectedChange([item])
- else
- onSelectedChange(checkedList.filter(checkedItem => checkedItem.source_url !== item.source_url))
+ if (checked) {
+ if (isMultipleChoice)
+ onSelectedChange([...checkedList, item])
+ else
+ onSelectedChange([item])
+ }
+ else { onSelectedChange(checkedList.filter(checkedItem => checkedItem.source_url !== item.source_url)) }
}
}, [checkedList, onSelectedChange, isMultipleChoice])
diff --git a/web/app/components/datasets/documents/create-from-pipeline/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/index.tsx
index 75c31acd1b..77b77700ca 100644
--- a/web/app/components/datasets/documents/create-from-pipeline/index.tsx
+++ b/web/app/components/datasets/documents/create-from-pipeline/index.tsx
@@ -326,7 +326,10 @@ const CreateFormPipeline = () => {
}, [])
const handleSubmit = useCallback((data: Record) => {
- isPreview.current ? handlePreviewChunks(data) : handleProcess(data)
+ if (isPreview.current)
+ handlePreviewChunks(data)
+ else
+ handleProcess(data)
}, [handlePreviewChunks, handleProcess])
const handlePreviewFileChange = useCallback((file: DocumentItem) => {
diff --git a/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx b/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx
index aaf9ed8ffd..7e8749f0bf 100644
--- a/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx
+++ b/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx
@@ -99,7 +99,8 @@ const CSVUploader: FC = ({
const handleDragEnter = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
- e.target !== dragRef.current && setDragging(true)
+ if (e.target !== dragRef.current)
+ setDragging(true)
}
const handleDragOver = (e: DragEvent) => {
e.preventDefault()
@@ -108,7 +109,8 @@ const CSVUploader: FC = ({
const handleDragLeave = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
- e.target === dragRef.current && setDragging(false)
+ if (e.target === dragRef.current)
+ setDragging(false)
}
const handleDrop = (e: DragEvent) => {
e.preventDefault()
diff --git a/web/app/components/datasets/documents/detail/completed/index.tsx b/web/app/components/datasets/documents/detail/completed/index.tsx
index 726be7519a..8fa167f976 100644
--- a/web/app/components/datasets/documents/detail/completed/index.tsx
+++ b/web/app/components/datasets/documents/detail/completed/index.tsx
@@ -284,7 +284,8 @@ const Completed: FC = ({
onSuccess: () => {
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
resetList()
- !segId && setSelectedSegmentIds([])
+ if (!segId)
+ setSelectedSegmentIds([])
},
onError: () => {
notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
@@ -438,7 +439,8 @@ const Completed: FC = ({
}
else {
resetList()
- currentPage !== totalPages && setCurrentPage(totalPages)
+ if (currentPage !== totalPages)
+ setCurrentPage(totalPages)
}
}, [segmentListData, limit, currentPage, resetList])
@@ -491,7 +493,8 @@ const Completed: FC = ({
}
else {
resetChildList()
- currentPage !== totalPages && setCurrentPage(totalPages)
+ if (currentPage !== totalPages)
+ setCurrentPage(totalPages)
}
}, [childChunkListData, limit, currentPage, resetChildList])
diff --git a/web/app/components/datasets/documents/detail/metadata/index.tsx b/web/app/components/datasets/documents/detail/metadata/index.tsx
index 4cb5fe97e9..88c359f8b2 100644
--- a/web/app/components/datasets/documents/detail/metadata/index.tsx
+++ b/web/app/components/datasets/documents/detail/metadata/index.tsx
@@ -66,7 +66,7 @@ export const FieldInfo: FC = ({
? displayedValue
: inputType === 'select'
? onUpdate && onUpdate(value as string)}
+ onSelect={({ value }) => onUpdate?.(value as string)}
items={selectOptions}
defaultValue={value}
className={s.select}
@@ -75,7 +75,7 @@ export const FieldInfo: FC = ({
/>
: inputType === 'textarea'
? onUpdate && onUpdate(e.target.value)}
+ onChange={e => onUpdate?.(e.target.value)}
value={value}
className={s.textArea}
placeholder={`${t('datasetDocuments.metadata.placeholder.add')}${label}`}
diff --git a/web/app/components/datasets/documents/detail/settings/pipeline-settings/index.tsx b/web/app/components/datasets/documents/detail/settings/pipeline-settings/index.tsx
index e69481c3ea..1ab47be445 100644
--- a/web/app/components/datasets/documents/detail/settings/pipeline-settings/index.tsx
+++ b/web/app/components/datasets/documents/detail/settings/pipeline-settings/index.tsx
@@ -148,7 +148,10 @@ const PipelineSettings = ({
}, [])
const handleSubmit = useCallback((data: Record) => {
- isPreview.current ? handlePreviewChunks(data) : handleProcess(data)
+ if (isPreview.current)
+ handlePreviewChunks(data)
+ else
+ handleProcess(data)
}, [handlePreviewChunks, handleProcess])
if (isFetchingLastRunData) {
diff --git a/web/app/components/datasets/extra-info/service-api/index.tsx b/web/app/components/datasets/extra-info/service-api/index.tsx
index b1843682ee..af7ce946ad 100644
--- a/web/app/components/datasets/extra-info/service-api/index.tsx
+++ b/web/app/components/datasets/extra-info/service-api/index.tsx
@@ -52,7 +52,7 @@ const ServiceApi = ({
/>
-
+
{
diff --git a/web/app/components/datasets/list/dataset-card/index.tsx b/web/app/components/datasets/list/dataset-card/index.tsx
index db8ee0226d..b1304e578e 100644
--- a/web/app/components/datasets/list/dataset-card/index.tsx
+++ b/web/app/components/datasets/list/dataset-card/index.tsx
@@ -157,12 +157,12 @@ const DatasetCard = ({
data-disable-nprogress={true}
onClick={(e) => {
e.preventDefault()
- isExternalProvider
- ? push(`/datasets/${dataset.id}/hitTesting`)
- // eslint-disable-next-line sonarjs/no-nested-conditional
- : isPipelineUnpublished
- ? push(`/datasets/${dataset.id}/pipeline`)
- : push(`/datasets/${dataset.id}/documents`)
+ if (isExternalProvider)
+ push(`/datasets/${dataset.id}/hitTesting`)
+ else if (isPipelineUnpublished)
+ push(`/datasets/${dataset.id}/pipeline`)
+ else
+ push(`/datasets/${dataset.id}/documents`)
}}
>
{!dataset.embedding_available && (
diff --git a/web/app/components/datasets/loading.tsx b/web/app/components/datasets/loading.tsx
index e69de29bb2..182c1f91de 100644
--- a/web/app/components/datasets/loading.tsx
+++ b/web/app/components/datasets/loading.tsx
@@ -0,0 +1,3 @@
+const DatasetsLoading = () => null
+
+export default DatasetsLoading
diff --git a/web/app/components/datasets/preview/index.tsx b/web/app/components/datasets/preview/index.tsx
index e69de29bb2..e71c440c20 100644
--- a/web/app/components/datasets/preview/index.tsx
+++ b/web/app/components/datasets/preview/index.tsx
@@ -0,0 +1,3 @@
+const DatasetPreview = () => null
+
+export default DatasetPreview
diff --git a/web/app/components/develop/code.tsx b/web/app/components/develop/code.tsx
index eadc87a5ca..69d5624966 100644
--- a/web/app/components/develop/code.tsx
+++ b/web/app/components/develop/code.tsx
@@ -193,8 +193,8 @@ function CodeGroupPanels({ children, targetCode, ...props }: ICodeGroupPanelsPro
if ((targetCode?.length ?? 0) > 1) {
return (
- {targetCode!.map(code => (
-
+ {targetCode!.map((code, index) => (
+
))}
@@ -206,8 +206,8 @@ function CodeGroupPanels({ children, targetCode, ...props }: ICodeGroupPanelsPro
}
function usePreventLayoutShift() {
- const positionRef = useRef()
- const rafRef = useRef()
+ const positionRef = useRef(null)
+ const rafRef = useRef(null)
useEffect(() => {
return () => {
diff --git a/web/app/components/header/account-setting/collapse/index.tsx b/web/app/components/header/account-setting/collapse/index.tsx
index 2ad4a97cd1..44360df8cd 100644
--- a/web/app/components/header/account-setting/collapse/index.tsx
+++ b/web/app/components/header/account-setting/collapse/index.tsx
@@ -39,7 +39,7 @@ const Collapse = ({
{
items.map(item => (
-
onSelect && onSelect(item)}>
+
onSelect?.(item)}>
{renderItem(item)}
))
diff --git a/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx b/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx
index 7c259f1a78..bdaeacb5c0 100644
--- a/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx
+++ b/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx
@@ -276,7 +276,7 @@ function Form<
- {label[language] || label.en_US}
+ {label[language] || label.en_US}
{required && (
*
)}
diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx
index 29da0ffc0c..291ba013f7 100644
--- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx
+++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx
@@ -49,7 +49,7 @@ const ModelLoadBalancingConfigs = ({
provider,
model,
configurationMethod,
- currentCustomConfigurationModelFixedFields,
+ currentCustomConfigurationModelFixedFields: _currentCustomConfigurationModelFixedFields,
withSwitch = false,
className,
modelCredential,
diff --git a/web/app/components/plugins/card/base/card-icon.tsx b/web/app/components/plugins/card/base/card-icon.tsx
index 7f7468ece2..b4c052c13c 100644
--- a/web/app/components/plugins/card/base/card-icon.tsx
+++ b/web/app/components/plugins/card/base/card-icon.tsx
@@ -1,6 +1,8 @@
import { RiCheckLine, RiCloseLine } from '@remixicon/react'
+import { Mcp } from '@/app/components/base/icons/src/vender/other'
import AppIcon from '@/app/components/base/app-icon'
import cn from '@/utils/classnames'
+import { shouldUseMcpIcon } from '@/utils/mcp'
const iconSizeMap = {
xs: 'w-4 h-4 text-base',
@@ -35,6 +37,7 @@ const Icon = ({
icon={src.content}
background={src.background}
className='rounded-md'
+ innerIcon={shouldUseMcpIcon(src) ? : undefined}
/>
)
diff --git a/web/app/components/plugins/hooks.ts b/web/app/components/plugins/hooks.ts
index 0af7c1a170..f22b2c4d69 100644
--- a/web/app/components/plugins/hooks.ts
+++ b/web/app/components/plugins/hooks.ts
@@ -1,3 +1,4 @@
+import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import type { TFunction } from 'i18next'
import {
@@ -14,23 +15,29 @@ export const useTags = (translateFromOut?: TFunction) => {
const { t: translation } = useTranslation()
const t = translateFromOut || translation
- const tags = tagKeys.map((tag) => {
- return {
- name: tag,
- label: t(`pluginTags.tags.${tag}`),
+ const tags = useMemo(() => {
+ return tagKeys.map((tag) => {
+ return {
+ name: tag,
+ label: t(`pluginTags.tags.${tag}`),
+ }
+ })
+ }, [t])
+
+ const tagsMap = useMemo(() => {
+ return tags.reduce((acc, tag) => {
+ acc[tag.name] = tag
+ return acc
+ }, {} as Record
)
+ }, [tags])
+
+ const getTagLabel = useMemo(() => {
+ return (name: string) => {
+ if (!tagsMap[name])
+ return name
+ return tagsMap[name].label
}
- })
-
- const tagsMap = tags.reduce((acc, tag) => {
- acc[tag.name] = tag
- return acc
- }, {} as Record)
-
- const getTagLabel = (name: string) => {
- if (!tagsMap[name])
- return name
- return tagsMap[name].label
- }
+ }, [tagsMap])
return {
tags,
@@ -48,23 +55,27 @@ export const useCategories = (translateFromOut?: TFunction) => {
const { t: translation } = useTranslation()
const t = translateFromOut || translation
- const categories = categoryKeys.map((category) => {
- if (category === 'agent-strategy') {
- return {
- name: 'agent-strategy',
- label: t('plugin.category.agents'),
+ const categories = useMemo(() => {
+ return categoryKeys.map((category) => {
+ if (category === 'agent-strategy') {
+ return {
+ name: 'agent-strategy',
+ label: t('plugin.category.agents'),
+ }
}
- }
- return {
- name: category,
- label: t(`plugin.category.${category}s`),
- }
- })
+ return {
+ name: category,
+ label: t(`plugin.category.${category}s`),
+ }
+ })
+ }, [t])
- const categoriesMap = categories.reduce((acc, category) => {
- acc[category.name] = category
- return acc
- }, {} as Record)
+ const categoriesMap = useMemo(() => {
+ return categories.reduce((acc, category) => {
+ acc[category.name] = category
+ return acc
+ }, {} as Record)
+ }, [categories])
return {
categories,
@@ -76,23 +87,27 @@ export const useSingleCategories = (translateFromOut?: TFunction) => {
const { t: translation } = useTranslation()
const t = translateFromOut || translation
- const categories = categoryKeys.map((category) => {
- if (category === 'agent-strategy') {
- return {
- name: 'agent-strategy',
- label: t('plugin.categorySingle.agent'),
+ const categories = useMemo(() => {
+ return categoryKeys.map((category) => {
+ if (category === 'agent-strategy') {
+ return {
+ name: 'agent-strategy',
+ label: t('plugin.categorySingle.agent'),
+ }
}
- }
- return {
- name: category,
- label: t(`plugin.categorySingle.${category}`),
- }
- })
+ return {
+ name: category,
+ label: t(`plugin.categorySingle.${category}`),
+ }
+ })
+ }, [t])
- const categoriesMap = categories.reduce((acc, category) => {
- acc[category.name] = category
- return acc
- }, {} as Record)
+ const categoriesMap = useMemo(() => {
+ return categories.reduce((acc, category) => {
+ acc[category.name] = category
+ return acc
+ }, {} as Record)
+ }, [categories])
return {
categories,
diff --git a/web/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list.tsx b/web/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list.tsx
index 6294887356..024444cd6a 100644
--- a/web/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list.tsx
+++ b/web/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list.tsx
@@ -7,6 +7,7 @@ import { useInvalidateStrategyProviders } from '@/service/use-strategy'
import type { Plugin, PluginDeclaration, PluginManifestInMarket } from '../../types'
import { PluginType } from '../../types'
import { useInvalidDataSourceList } from '@/service/use-pipeline'
+import { useInvalidDataSourceListAuth } from '@/service/use-datasource'
const useRefreshPluginList = () => {
const invalidateInstalledPluginList = useInvalidateInstalledPluginList()
@@ -19,6 +20,8 @@ const useRefreshPluginList = () => {
const invalidateAllBuiltInTools = useInvalidateAllBuiltInTools()
const invalidateAllDataSources = useInvalidDataSourceList()
+ const invalidateDataSourceListAuth = useInvalidDataSourceListAuth()
+
const invalidateStrategyProviders = useInvalidateStrategyProviders()
return {
refreshPluginList: (manifest?: PluginManifestInMarket | Plugin | PluginDeclaration | null, refreshAllType?: boolean) => {
@@ -32,8 +35,10 @@ const useRefreshPluginList = () => {
// TODO: update suggested tools. It's a function in hook useMarketplacePlugins,handleUpdatePlugins
}
- if ((manifest && PluginType.datasource.includes(manifest.category)) || refreshAllType)
+ if ((manifest && PluginType.datasource.includes(manifest.category)) || refreshAllType) {
invalidateAllDataSources()
+ invalidateDataSourceListAuth()
+ }
// model select
if ((manifest && PluginType.model.includes(manifest.category)) || refreshAllType) {
diff --git a/web/app/components/plugins/plugin-detail-panel/app-selector/app-picker.tsx b/web/app/components/plugins/plugin-detail-panel/app-selector/app-picker.tsx
index 3c79acb653..10c28507f7 100644
--- a/web/app/components/plugins/plugin-detail-panel/app-selector/app-picker.tsx
+++ b/web/app/components/plugins/plugin-detail-panel/app-selector/app-picker.tsx
@@ -33,7 +33,7 @@ type Props = {
}
const AppPicker: FC = ({
- scope,
+ scope: _scope,
disabled,
trigger,
placement = 'right-start',
@@ -90,7 +90,7 @@ const AppPicker: FC = ({
}
// Set up MutationObserver to watch DOM changes
- mutationObserver = new MutationObserver((mutations) => {
+ mutationObserver = new MutationObserver((_mutations) => {
if (observerTarget.current) {
setupIntersectionObserver()
mutationObserver?.disconnect()
diff --git a/web/app/components/plugins/plugin-detail-panel/model-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/model-selector/index.tsx
index fb4c99e1e4..873f187e8f 100644
--- a/web/app/components/plugins/plugin-detail-panel/model-selector/index.tsx
+++ b/web/app/components/plugins/plugin-detail-panel/model-selector/index.tsx
@@ -148,7 +148,7 @@ const ModelParameterModal: FC = ({
})
}
}
- catch (e) {
+ catch {
Toast.notify({ type: 'error', message: t('common.error') })
}
}
diff --git a/web/app/components/rag-pipeline/components/panel/input-field/field-list/hooks.ts b/web/app/components/rag-pipeline/components/panel/input-field/field-list/hooks.ts
index d5c0797c9b..376894b1bb 100644
--- a/web/app/components/rag-pipeline/components/panel/input-field/field-list/hooks.ts
+++ b/web/app/components/rag-pipeline/components/panel/input-field/field-list/hooks.ts
@@ -51,7 +51,7 @@ export const useFieldList = ({
const handleListSortChange = useCallback((list: SortableItem[]) => {
const newInputFields = list.map((item) => {
- const { id, chosen, selected, ...filed } = item
+ const { id: _id, chosen: _chosen, selected: _selected, ...filed } = item
return filed
})
handleInputFieldsChange(newInputFields)
diff --git a/web/app/components/rag-pipeline/components/panel/test-run/header.tsx b/web/app/components/rag-pipeline/components/panel/test-run/header.tsx
index 16291f868b..a536f66137 100644
--- a/web/app/components/rag-pipeline/components/panel/test-run/header.tsx
+++ b/web/app/components/rag-pipeline/components/panel/test-run/header.tsx
@@ -15,7 +15,8 @@ const Header = () => {
isPreparingDataSource,
setIsPreparingDataSource,
} = workflowStore.getState()
- isPreparingDataSource && setIsPreparingDataSource?.(false)
+ if (isPreparingDataSource)
+ setIsPreparingDataSource?.(false)
handleCancelDebugAndPreviewPanel()
}, [workflowStore])
diff --git a/web/app/components/rag-pipeline/hooks/use-nodes-sync-draft.ts b/web/app/components/rag-pipeline/hooks/use-nodes-sync-draft.ts
index 86e44dced7..ad757f36a7 100644
--- a/web/app/components/rag-pipeline/hooks/use-nodes-sync-draft.ts
+++ b/web/app/components/rag-pipeline/hooks/use-nodes-sync-draft.ts
@@ -104,7 +104,7 @@ export const useNodesSyncDraft = () => {
const res = await syncWorkflowDraft(postParams)
setSyncWorkflowDraftHash(res.hash)
setDraftUpdatedAt(res.updated_at)
- callback?.onSuccess && callback.onSuccess()
+ callback?.onSuccess?.()
}
catch (error: any) {
if (error && error.json && !error.bodyUsed) {
@@ -113,10 +113,10 @@ export const useNodesSyncDraft = () => {
handleRefreshWorkflowDraft()
})
}
- callback?.onError && callback.onError()
+ callback?.onError?.()
}
finally {
- callback?.onSettled && callback.onSettled()
+ callback?.onSettled?.()
}
}
}, [getPostParams, getNodesReadOnly, workflowStore, handleRefreshWorkflowDraft])
diff --git a/web/app/components/share/text-generation/index.tsx b/web/app/components/share/text-generation/index.tsx
index da5b09b065..98804c7311 100644
--- a/web/app/components/share/text-generation/index.tsx
+++ b/web/app/components/share/text-generation/index.tsx
@@ -363,7 +363,8 @@ const TextGeneration: FC = ({
(async () => {
if (!appData || !appParams)
return
- !isWorkflow && fetchSavedMessage()
+ if (!isWorkflow)
+ fetchSavedMessage()
const { app_id: appId, site: siteInfo, custom_config } = appData
setAppId(appId)
setSiteInfo(siteInfo as SiteInfo)
diff --git a/web/app/components/share/text-generation/result/index.tsx b/web/app/components/share/text-generation/result/index.tsx
index a7eb7f7591..7a4e606636 100644
--- a/web/app/components/share/text-generation/result/index.tsx
+++ b/web/app/components/share/text-generation/result/index.tsx
@@ -78,15 +78,15 @@ const Result: FC = ({
setRespondingFalse()
}, [controlStopResponding])
- const [completionRes, doSetCompletionRes] = useState('')
- const completionResRef = useRef()
- const setCompletionRes = (res: any) => {
+ const [completionRes, doSetCompletionRes] = useState('')
+ const completionResRef = useRef('')
+ const setCompletionRes = (res: string) => {
completionResRef.current = res
doSetCompletionRes(res)
}
const getCompletionRes = () => completionResRef.current
const [workflowProcessData, doSetWorkflowProcessData] = useState()
- const workflowProcessDataRef = useRef()
+ const workflowProcessDataRef = useRef(undefined)
const setWorkflowProcessData = (data: WorkflowProcess) => {
workflowProcessDataRef.current = data
doSetWorkflowProcessData(data)
@@ -126,8 +126,8 @@ const Result: FC = ({
let hasEmptyInput = ''
const requiredVars = prompt_variables?.filter(({ key, name, required, type }) => {
- if(type === 'boolean')
- return false // boolean input is not required
+ if(type === 'boolean' || type === 'checkbox')
+ return false // boolean/checkbox input is not required
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
return res
}) || [] // compatible with old version
diff --git a/web/app/components/share/text-generation/run-once/index.tsx b/web/app/components/share/text-generation/run-once/index.tsx
index 7896776f35..4f94aa1fe8 100644
--- a/web/app/components/share/text-generation/run-once/index.tsx
+++ b/web/app/components/share/text-generation/run-once/index.tsx
@@ -51,6 +51,8 @@ const RunOnce: FC = ({
promptConfig.prompt_variables.forEach((item) => {
if (item.type === 'string' || item.type === 'paragraph')
newInputs[item.key] = ''
+ else if (item.type === 'checkbox')
+ newInputs[item.key] = false
else
newInputs[item.key] = undefined
})
@@ -77,6 +79,8 @@ const RunOnce: FC = ({
newInputs[item.key] = item.default || ''
else if (item.type === 'number')
newInputs[item.key] = item.default
+ else if (item.type === 'checkbox')
+ newInputs[item.key] = item.default || false
else if (item.type === 'file')
newInputs[item.key] = item.default
else if (item.type === 'file-list')
@@ -96,7 +100,7 @@ const RunOnce: FC = ({
{(inputs === null || inputs === undefined || Object.keys(inputs).length === 0) || !isInitialized ? null
: promptConfig.prompt_variables.map(item => (
- {item.type !== 'boolean' && (
+ {item.type !== 'checkbox' && (
{item.name}
)}
@@ -134,7 +138,7 @@ const RunOnce: FC
= ({
onChange={(e: ChangeEvent) => { handleInputsChange({ ...inputsRef.current, [item.key]: e.target.value }) }}
/>
)}
- {item.type === 'boolean' && (
+ {item.type === 'checkbox' && (
void
}
-const DEFAULT_ICON = { type: 'emoji', icon: '🧿', background: '#EFF1F5' }
+const DEFAULT_ICON = { type: 'emoji', icon: '🔗', background: '#6366F1' }
const extractFileId = (url: string) => {
const match = url.match(/files\/(.+?)\/file-preview/)
return match ? match[1] : null
@@ -208,6 +210,7 @@ const MCPModal = ({
icon={appIcon.type === 'emoji' ? appIcon.icon : appIcon.fileId}
background={appIcon.type === 'emoji' ? appIcon.background : undefined}
imageUrl={appIcon.type === 'image' ? appIcon.url : undefined}
+ innerIcon={shouldUseMcpIconForAppIcon(appIcon.type, appIcon.type === 'emoji' ? appIcon.icon : '') ? : undefined}
size='xxl'
className='relative cursor-pointer rounded-2xl'
coverElement={
diff --git a/web/app/components/tools/provider-list.tsx b/web/app/components/tools/provider-list.tsx
index 08a4aa0b5d..1679b4469b 100644
--- a/web/app/components/tools/provider-list.tsx
+++ b/web/app/components/tools/provider-list.tsx
@@ -21,6 +21,7 @@ import { useCheckInstalled, useInvalidateInstalledPluginList } from '@/service/u
import { useGlobalPublicStore } from '@/context/global-public-context'
import { ToolTypeEnum } from '../workflow/block-selector/types'
import { useMarketplace } from './marketplace/hooks'
+import { useTags } from '@/app/components/plugins/hooks'
const getToolType = (type: string) => {
switch (type) {
@@ -40,6 +41,7 @@ const ProviderList = () => {
// const searchParams = useSearchParams()
// searchParams.get('category') === 'workflow'
const { t } = useTranslation()
+ const { getTagLabel } = useTags()
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
const containerRef = useRef(null)
@@ -180,7 +182,7 @@ const ProviderList = () => {
} as any}
footer={
getTagLabel(label)) || []}
/>
}
/>
diff --git a/web/app/components/tools/utils/to-form-schema.ts b/web/app/components/tools/utils/to-form-schema.ts
index c1d17b48ef..8e85a5f9b0 100644
--- a/web/app/components/tools/utils/to-form-schema.ts
+++ b/web/app/components/tools/utils/to-form-schema.ts
@@ -45,6 +45,7 @@ export const toolCredentialToFormSchemas = (parameters: ToolCredential[]) => {
return {
...parameter,
variable: parameter.name,
+ type: toType(parameter.type),
label: parameter.label,
tooltip: parameter.help,
show_on: [],
diff --git a/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts b/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts
index 654977fb15..5705deb0c0 100644
--- a/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts
+++ b/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts
@@ -124,7 +124,7 @@ export const useNodesSyncDraft = () => {
const res = await syncWorkflowDraft(postParams)
setSyncWorkflowDraftHash(res.hash)
setDraftUpdatedAt(res.updated_at)
- callback?.onSuccess && callback.onSuccess()
+ callback?.onSuccess?.()
}
catch (error: any) {
if (error && error.json && !error.bodyUsed) {
@@ -133,10 +133,10 @@ export const useNodesSyncDraft = () => {
handleRefreshWorkflowDraft()
})
}
- callback?.onError && callback.onError()
+ callback?.onError?.()
}
finally {
- callback?.onSettled && callback.onSettled()
+ callback?.onSettled?.()
}
}
}, [workflowStore, getPostParams, getNodesReadOnly, handleRefreshWorkflowDraft])
diff --git a/web/app/components/workflow-app/index.tsx b/web/app/components/workflow-app/index.tsx
index c18a86f981..05654f4e74 100644
--- a/web/app/components/workflow-app/index.tsx
+++ b/web/app/components/workflow-app/index.tsx
@@ -8,7 +8,7 @@ import {
} from '@/app/components/workflow/types'
import {
useWorkflowInit,
-} from './hooks'
+} from './hooks/use-workflow-init'
import {
initialEdges,
initialNodes,
diff --git a/web/app/components/workflow/block-selector/data-sources.tsx b/web/app/components/workflow/block-selector/data-sources.tsx
index 294c7c1c79..441ede2334 100644
--- a/web/app/components/workflow/block-selector/data-sources.tsx
+++ b/web/app/components/workflow/block-selector/data-sources.tsx
@@ -1,10 +1,9 @@
import {
useCallback,
+ useEffect,
+ useMemo,
useRef,
} from 'react'
-import Link from 'next/link'
-import { useTranslation } from 'react-i18next'
-import { RiArrowRightUpLine } from '@remixicon/react'
import { BlockEnum } from '../types'
import type {
OnSelectBlock,
@@ -14,10 +13,12 @@ import type { DataSourceDefaultValue, ToolDefaultValue } from './types'
import Tools from './tools'
import { ViewType } from './view-type-select'
import cn from '@/utils/classnames'
-import type { ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list'
-import { getMarketplaceUrl } from '@/utils/var'
+import PluginList, { type ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { DEFAULT_FILE_EXTENSIONS_IN_LOCAL_FILE_DATA_SOURCE } from './constants'
+import { useMarketplacePlugins } from '../../plugins/marketplace/hooks'
+import { PluginType } from '../../plugins/types'
+import { useGetLanguage } from '@/context/i18n'
type AllToolsProps = {
className?: string
@@ -34,9 +35,26 @@ const DataSources = ({
onSelect,
dataSources,
}: AllToolsProps) => {
- const { t } = useTranslation()
+ const language = useGetLanguage()
const pluginRef = useRef(null)
const wrapElemRef = useRef(null)
+
+ const isMatchingKeywords = (text: string, keywords: string) => {
+ return text.toLowerCase().includes(keywords.toLowerCase())
+ }
+
+ const filteredDatasources = useMemo(() => {
+ const hasFilter = searchText
+ if (!hasFilter)
+ return dataSources.filter(toolWithProvider => toolWithProvider.tools.length > 0)
+
+ return dataSources.filter((toolWithProvider) => {
+ return isMatchingKeywords(toolWithProvider.name, searchText) || toolWithProvider.tools.some((tool) => {
+ return tool.label[language].toLowerCase().includes(searchText.toLowerCase()) || tool.name.toLowerCase().includes(searchText.toLowerCase())
+ })
+ })
+ }, [searchText, dataSources, language])
+
const handleSelect = useCallback((_: any, toolDefaultValue: ToolDefaultValue) => {
let defaultValue: DataSourceDefaultValue = {
plugin_id: toolDefaultValue?.provider_id,
@@ -55,8 +73,24 @@ const DataSources = ({
}
onSelect(BlockEnum.DataSource, toolDefaultValue && defaultValue)
}, [onSelect])
+
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
+ const {
+ queryPluginsWithDebounced: fetchPlugins,
+ plugins: notInstalledPlugins = [],
+ } = useMarketplacePlugins()
+
+ useEffect(() => {
+ if (!enable_marketplace) return
+ if (searchText) {
+ fetchPlugins({
+ query: searchText,
+ category: PluginType.datasource,
+ })
+ }
+ }, [searchText, enable_marketplace])
+
return (
- {
- enable_marketplace && (
-
-
{t('plugin.findMoreInMarketplace')}
-
-
- )
- }
+ {/* Plugins from marketplace */}
+ {enable_marketplace && (
+
+ )}
)
diff --git a/web/app/components/workflow/block-selector/tool/tool.tsx b/web/app/components/workflow/block-selector/tool/tool.tsx
index 6004bb119a..30d3e218d2 100644
--- a/web/app/components/workflow/block-selector/tool/tool.tsx
+++ b/web/app/components/workflow/block-selector/tool/tool.tsx
@@ -73,7 +73,7 @@ const Tool: FC = ({
if (isHovering && !isAllSelected) {
return (
{
+ onClick={() => {
onSelectMultiple?.(BlockEnum.Tool, actions.filter(action => !getIsDisabled(action)).map((tool) => {
const params: Record = {}
if (tool.parameters) {
diff --git a/web/app/components/workflow/hooks/use-shortcuts.ts b/web/app/components/workflow/hooks/use-shortcuts.ts
index b2d71555d7..a744fefd50 100644
--- a/web/app/components/workflow/hooks/use-shortcuts.ts
+++ b/web/app/components/workflow/hooks/use-shortcuts.ts
@@ -107,7 +107,8 @@ export const useShortcuts = (): void => {
const { showDebugAndPreviewPanel } = workflowStore.getState()
if (shouldHandleShortcut(e) && !showDebugAndPreviewPanel) {
e.preventDefault()
- workflowHistoryShortcutsEnabled && handleHistoryBack()
+ if (workflowHistoryShortcutsEnabled)
+ handleHistoryBack()
}
}, { exactMatch: true, useCapture: true })
@@ -116,7 +117,8 @@ export const useShortcuts = (): void => {
(e) => {
if (shouldHandleShortcut(e)) {
e.preventDefault()
- workflowHistoryShortcutsEnabled && handleHistoryForward()
+ if (workflowHistoryShortcutsEnabled)
+ handleHistoryForward()
}
},
{ exactMatch: true, useCapture: true },
diff --git a/web/app/components/workflow/hooks/use-workflow-history.ts b/web/app/components/workflow/hooks/use-workflow-history.ts
index b7338dc4f8..58bbe415a8 100644
--- a/web/app/components/workflow/hooks/use-workflow-history.ts
+++ b/web/app/components/workflow/hooks/use-workflow-history.ts
@@ -16,39 +16,41 @@ import type { WorkflowHistoryEventMeta } from '../workflow-history-store'
* - InputChange events in Node Panels do not trigger state changes.
* - Resizing UI elements does not trigger state changes.
*/
-export enum WorkflowHistoryEvent {
- NodeTitleChange = 'NodeTitleChange',
- NodeDescriptionChange = 'NodeDescriptionChange',
- NodeDragStop = 'NodeDragStop',
- NodeChange = 'NodeChange',
- NodeConnect = 'NodeConnect',
- NodePaste = 'NodePaste',
- NodeDelete = 'NodeDelete',
- EdgeDelete = 'EdgeDelete',
- EdgeDeleteByDeleteBranch = 'EdgeDeleteByDeleteBranch',
- NodeAdd = 'NodeAdd',
- NodeResize = 'NodeResize',
- NoteAdd = 'NoteAdd',
- NoteChange = 'NoteChange',
- NoteDelete = 'NoteDelete',
- LayoutOrganize = 'LayoutOrganize',
-}
+export const WorkflowHistoryEvent = {
+ NodeTitleChange: 'NodeTitleChange',
+ NodeDescriptionChange: 'NodeDescriptionChange',
+ NodeDragStop: 'NodeDragStop',
+ NodeChange: 'NodeChange',
+ NodeConnect: 'NodeConnect',
+ NodePaste: 'NodePaste',
+ NodeDelete: 'NodeDelete',
+ EdgeDelete: 'EdgeDelete',
+ EdgeDeleteByDeleteBranch: 'EdgeDeleteByDeleteBranch',
+ NodeAdd: 'NodeAdd',
+ NodeResize: 'NodeResize',
+ NoteAdd: 'NoteAdd',
+ NoteChange: 'NoteChange',
+ NoteDelete: 'NoteDelete',
+ LayoutOrganize: 'LayoutOrganize',
+} as const
+
+export type WorkflowHistoryEventT = keyof typeof WorkflowHistoryEvent
export const useWorkflowHistory = () => {
const store = useStoreApi()
const { store: workflowHistoryStore } = useWorkflowHistoryStore()
const { t } = useTranslation()
- const [undoCallbacks, setUndoCallbacks] = useState([])
- const [redoCallbacks, setRedoCallbacks] = useState([])
+ const [undoCallbacks, setUndoCallbacks] = useState<(() => void)[]>([])
+ const [redoCallbacks, setRedoCallbacks] = useState<(() => void)[]>([])
- const onUndo = useCallback((callback: unknown) => {
- setUndoCallbacks((prev: any) => [...prev, callback])
+ const onUndo = useCallback((callback: () => void) => {
+ setUndoCallbacks(prev => [...prev, callback])
return () => setUndoCallbacks(prev => prev.filter(cb => cb !== callback))
}, [])
- const onRedo = useCallback((callback: unknown) => {
- setRedoCallbacks((prev: any) => [...prev, callback])
+ const onRedo = useCallback((callback: () => void) => {
+ setRedoCallbacks(prev => [...prev, callback])
return () => setRedoCallbacks(prev => prev.filter(cb => cb !== callback))
}, [])
@@ -65,7 +67,7 @@ export const useWorkflowHistory = () => {
// Some events may be triggered multiple times in a short period of time.
// We debounce the history state update to avoid creating multiple history states
// with minimal changes.
- const saveStateToHistoryRef = useRef(debounce((event: WorkflowHistoryEvent, meta?: WorkflowHistoryEventMeta) => {
+ const saveStateToHistoryRef = useRef(debounce((event: WorkflowHistoryEventT, meta?: WorkflowHistoryEventMeta) => {
workflowHistoryStore.setState({
workflowHistoryEvent: event,
workflowHistoryEventMeta: meta,
@@ -74,7 +76,7 @@ export const useWorkflowHistory = () => {
})
}, 500))
- const saveStateToHistory = useCallback((event: WorkflowHistoryEvent, meta?: WorkflowHistoryEventMeta) => {
+ const saveStateToHistory = useCallback((event: WorkflowHistoryEventT, meta?: WorkflowHistoryEventMeta) => {
switch (event) {
case WorkflowHistoryEvent.NoteChange:
// Hint: Note change does not trigger when note text changes,
@@ -105,7 +107,7 @@ export const useWorkflowHistory = () => {
}
}, [])
- const getHistoryLabel = useCallback((event: WorkflowHistoryEvent) => {
+ const getHistoryLabel = useCallback((event: WorkflowHistoryEventT) => {
switch (event) {
case WorkflowHistoryEvent.NodeTitleChange:
return t('workflow.changeHistory.nodeTitleChange')
diff --git a/web/app/components/workflow/hooks/use-workflow-interactions.ts b/web/app/components/workflow/hooks/use-workflow-interactions.ts
index c508eea0ba..f63250dd42 100644
--- a/web/app/components/workflow/hooks/use-workflow-interactions.ts
+++ b/web/app/components/workflow/hooks/use-workflow-interactions.ts
@@ -10,7 +10,7 @@ import {
NODE_LAYOUT_VERTICAL_PADDING,
WORKFLOW_DATA_UPDATE,
} from '../constants'
-import type { Node, WorkflowDataUpdater } from '../types'
+import type { WorkflowDataUpdater } from '../types'
import { BlockEnum, ControlMode } from '../types'
import {
getLayoutByDagre,
@@ -18,6 +18,7 @@ import {
initialEdges,
initialNodes,
} from '../utils'
+import type { LayoutResult } from '../utils'
import {
useNodesReadOnly,
useSelectionInteractions,
@@ -102,10 +103,17 @@ export const useWorkflowOrganize = () => {
&& node.type === CUSTOM_NODE,
)
- const childLayoutsMap: Record = {}
- loopAndIterationNodes.forEach((node) => {
- childLayoutsMap[node.id] = getLayoutForChildNodes(node.id, nodes, edges)
- })
+ const childLayoutEntries = await Promise.all(
+ loopAndIterationNodes.map(async node => [
+ node.id,
+ await getLayoutForChildNodes(node.id, nodes, edges),
+ ] as const),
+ )
+ const childLayoutsMap = childLayoutEntries.reduce((acc, [nodeId, layout]) => {
+ if (layout)
+ acc[nodeId] = layout
+ return acc
+ }, {} as Record)
const containerSizeChanges: Record = {}
@@ -113,37 +121,20 @@ export const useWorkflowOrganize = () => {
const childLayout = childLayoutsMap[parentNode.id]
if (!childLayout) return
- let minX = Infinity
- let minY = Infinity
- let maxX = -Infinity
- let maxY = -Infinity
- let hasChildren = false
+ const {
+ bounds,
+ nodes: layoutNodes,
+ } = childLayout
- const childNodes = nodes.filter(node => node.parentId === parentNode.id)
+ if (!layoutNodes.size)
+ return
- childNodes.forEach((node) => {
- if (childLayout.node(node.id)) {
- hasChildren = true
- const childNodeWithPosition = childLayout.node(node.id)
+ const requiredWidth = (bounds.maxX - bounds.minX) + NODE_LAYOUT_HORIZONTAL_PADDING * 2
+ const requiredHeight = (bounds.maxY - bounds.minY) + NODE_LAYOUT_VERTICAL_PADDING * 2
- const nodeX = childNodeWithPosition.x - node.width! / 2
- const nodeY = childNodeWithPosition.y - node.height! / 2
-
- minX = Math.min(minX, nodeX)
- minY = Math.min(minY, nodeY)
- maxX = Math.max(maxX, nodeX + node.width!)
- maxY = Math.max(maxY, nodeY + node.height!)
- }
- })
-
- if (hasChildren) {
- const requiredWidth = maxX - minX + NODE_LAYOUT_HORIZONTAL_PADDING * 2
- const requiredHeight = maxY - minY + NODE_LAYOUT_VERTICAL_PADDING * 2
-
- containerSizeChanges[parentNode.id] = {
- width: Math.max(parentNode.width || 0, requiredWidth),
- height: Math.max(parentNode.height || 0, requiredHeight),
- }
+ containerSizeChanges[parentNode.id] = {
+ width: Math.max(parentNode.width || 0, requiredWidth),
+ height: Math.max(parentNode.height || 0, requiredHeight),
}
})
@@ -166,63 +157,65 @@ export const useWorkflowOrganize = () => {
})
})
- const layout = getLayoutByDagre(nodesWithUpdatedSizes, edges)
+ const layout = await getLayoutByDagre(nodesWithUpdatedSizes, edges)
- const rankMap = {} as Record
- nodesWithUpdatedSizes.forEach((node) => {
- if (!node.parentId && node.type === CUSTOM_NODE) {
- const rank = layout.node(node.id).rank!
-
- if (!rankMap[rank]) {
- rankMap[rank] = node
- }
- else {
- if (rankMap[rank].position.y > node.position.y)
- rankMap[rank] = node
+ // Build layer map for vertical alignment - nodes in the same layer should align
+ const layerMap = new Map()
+ layout.nodes.forEach((layoutInfo) => {
+ if (layoutInfo.layer !== undefined) {
+ const existing = layerMap.get(layoutInfo.layer)
+ const newLayerInfo = {
+ minY: existing ? Math.min(existing.minY, layoutInfo.y) : layoutInfo.y,
+ maxHeight: existing ? Math.max(existing.maxHeight, layoutInfo.height) : layoutInfo.height,
}
+ layerMap.set(layoutInfo.layer, newLayerInfo)
}
})
const newNodes = produce(nodesWithUpdatedSizes, (draft) => {
draft.forEach((node) => {
if (!node.parentId && node.type === CUSTOM_NODE) {
- const nodeWithPosition = layout.node(node.id)
+ const layoutInfo = layout.nodes.get(node.id)
+ if (!layoutInfo)
+ return
+
+ // Calculate vertical position with layer alignment
+ let yPosition = layoutInfo.y
+ if (layoutInfo.layer !== undefined) {
+ const layerInfo = layerMap.get(layoutInfo.layer)
+ if (layerInfo) {
+ // Align to the center of the tallest node in this layer
+ const layerCenterY = layerInfo.minY + layerInfo.maxHeight / 2
+ yPosition = layerCenterY - layoutInfo.height / 2
+ }
+ }
node.position = {
- x: nodeWithPosition.x - node.width! / 2,
- y: nodeWithPosition.y - node.height! / 2 + rankMap[nodeWithPosition.rank!].height! / 2,
+ x: layoutInfo.x,
+ y: yPosition,
}
}
})
loopAndIterationNodes.forEach((parentNode) => {
const childLayout = childLayoutsMap[parentNode.id]
- if (!childLayout) return
+ if (!childLayout)
+ return
const childNodes = draft.filter(node => node.parentId === parentNode.id)
+ const {
+ bounds,
+ nodes: layoutNodes,
+ } = childLayout
- let minX = Infinity
- let minY = Infinity
+ childNodes.forEach((childNode) => {
+ const layoutInfo = layoutNodes.get(childNode.id)
+ if (!layoutInfo)
+ return
- childNodes.forEach((node) => {
- if (childLayout.node(node.id)) {
- const childNodeWithPosition = childLayout.node(node.id)
- const nodeX = childNodeWithPosition.x - node.width! / 2
- const nodeY = childNodeWithPosition.y - node.height! / 2
-
- minX = Math.min(minX, nodeX)
- minY = Math.min(minY, nodeY)
- }
- })
-
- childNodes.forEach((node) => {
- if (childLayout.node(node.id)) {
- const childNodeWithPosition = childLayout.node(node.id)
-
- node.position = {
- x: NODE_LAYOUT_HORIZONTAL_PADDING + (childNodeWithPosition.x - node.width! / 2 - minX),
- y: NODE_LAYOUT_VERTICAL_PADDING + (childNodeWithPosition.y - node.height! / 2 - minY),
- }
+ childNode.position = {
+ x: NODE_LAYOUT_HORIZONTAL_PADDING + (layoutInfo.x - bounds.minX),
+ y: NODE_LAYOUT_VERTICAL_PADDING + (layoutInfo.y - bounds.minY),
}
})
})
diff --git a/web/app/components/workflow/hooks/use-workflow.ts b/web/app/components/workflow/hooks/use-workflow.ts
index 02a2f09d63..3f9f8106cf 100644
--- a/web/app/components/workflow/hooks/use-workflow.ts
+++ b/web/app/components/workflow/hooks/use-workflow.ts
@@ -354,7 +354,7 @@ export const useWorkflow = () => {
return startNodes
}, [nodesMap, getRootNodesById])
- const isValidConnection = useCallback(({ source, sourceHandle, target }: Connection) => {
+ const isValidConnection = useCallback(({ source, sourceHandle: _sourceHandle, target }: Connection) => {
const {
edges,
getNodes,
diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx
index 75c4d51390..b289cafefd 100644
--- a/web/app/components/workflow/index.tsx
+++ b/web/app/components/workflow/index.tsx
@@ -412,10 +412,10 @@ export const Workflow: FC = memo(({
nodesFocusable={!nodesReadOnly}
edgesFocusable={!nodesReadOnly}
panOnScroll={false}
- panOnDrag={controlMode === ControlMode.Hand && !workflowReadOnly}
- zoomOnPinch={!workflowReadOnly}
- zoomOnScroll={!workflowReadOnly}
- zoomOnDoubleClick={!workflowReadOnly}
+ panOnDrag={controlMode === ControlMode.Hand}
+ zoomOnPinch={true}
+ zoomOnScroll={true}
+ zoomOnDoubleClick={true}
isValidConnection={isValidConnection}
selectionKeyCode={null}
selectionMode={SelectionMode.Partial}
diff --git a/web/app/components/workflow/nodes/_base/components/retry/utils.ts b/web/app/components/workflow/nodes/_base/components/retry/utils.ts
index e69de29bb2..336ce12bb9 100644
--- a/web/app/components/workflow/nodes/_base/components/retry/utils.ts
+++ b/web/app/components/workflow/nodes/_base/components/retry/utils.ts
@@ -0,0 +1 @@
+export {}
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 273e100f8e..4d74e09fde 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
@@ -127,7 +127,7 @@ const VarReferencePicker: FC = ({
const reactflow = useReactFlow()
- const startNode = availableNodes.find((node: any) => {
+ const startNode = availableNodes.find((node: Node) => {
return node.data.type === BlockEnum.Start
})
@@ -407,7 +407,10 @@ const VarReferencePicker: FC = ({
{
if (readonly)
return
- !isConstant ? setOpen(!open) : setControlFocus(Date.now())
+ if (!isConstant)
+ setOpen(!open)
+ else
+ setControlFocus(Date.now())
}} className='group/picker-trigger-wrap relative !flex'>
<>
{isAddBtnTrigger
@@ -457,7 +460,10 @@ const VarReferencePicker: FC = ({
onClick={() => {
if (readonly)
return
- !isConstant ? setOpen(!open) : setControlFocus(Date.now())
+ if (!isConstant)
+ setOpen(!open)
+ else
+ setControlFocus(Date.now())
}}
className='h-full grow'
>
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 067dbf8652..e70cfed97c 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
@@ -137,7 +137,7 @@ const Item: FC = ({
const isHovering = isItemHovering || isChildrenHovering
const open = (isObj || isStructureOutput) && isHovering
useEffect(() => {
- onHovering && onHovering(isHovering)
+ onHovering?.(isHovering)
}, [isHovering])
const handleChosen = (e: React.MouseEvent) => {
e.stopPropagation()
diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx
index 06962389c9..b26dd74714 100644
--- a/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx
+++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx
@@ -25,12 +25,12 @@ type Props = {
} & Partial
const LastRun: FC = ({
- appId,
+ appId: _appId,
nodeId,
canSingleRun,
isRunAfterSingleRun,
updateNodeRunningStatus,
- nodeInfo,
+ nodeInfo: _nodeInfo,
runningStatus: oneStepRunRunningStatus,
onSingleRunClicked,
singleRunResult,
diff --git a/web/app/components/workflow/nodes/components.ts b/web/app/components/workflow/nodes/components.ts
new file mode 100644
index 0000000000..cdf3a21598
--- /dev/null
+++ b/web/app/components/workflow/nodes/components.ts
@@ -0,0 +1,94 @@
+import type { ComponentType } from 'react'
+import { BlockEnum } from '../types'
+import StartNode from './start/node'
+import StartPanel from './start/panel'
+import EndNode from './end/node'
+import EndPanel from './end/panel'
+import AnswerNode from './answer/node'
+import AnswerPanel from './answer/panel'
+import LLMNode from './llm/node'
+import LLMPanel from './llm/panel'
+import KnowledgeRetrievalNode from './knowledge-retrieval/node'
+import KnowledgeRetrievalPanel from './knowledge-retrieval/panel'
+import QuestionClassifierNode from './question-classifier/node'
+import QuestionClassifierPanel from './question-classifier/panel'
+import IfElseNode from './if-else/node'
+import IfElsePanel from './if-else/panel'
+import CodeNode from './code/node'
+import CodePanel from './code/panel'
+import TemplateTransformNode from './template-transform/node'
+import TemplateTransformPanel from './template-transform/panel'
+import HttpNode from './http/node'
+import HttpPanel from './http/panel'
+import ToolNode from './tool/node'
+import ToolPanel from './tool/panel'
+import VariableAssignerNode from './variable-assigner/node'
+import VariableAssignerPanel from './variable-assigner/panel'
+import AssignerNode from './assigner/node'
+import AssignerPanel from './assigner/panel'
+import ParameterExtractorNode from './parameter-extractor/node'
+import ParameterExtractorPanel from './parameter-extractor/panel'
+import IterationNode from './iteration/node'
+import IterationPanel from './iteration/panel'
+import LoopNode from './loop/node'
+import LoopPanel from './loop/panel'
+import DocExtractorNode from './document-extractor/node'
+import DocExtractorPanel from './document-extractor/panel'
+import ListFilterNode from './list-operator/node'
+import ListFilterPanel from './list-operator/panel'
+import AgentNode from './agent/node'
+import AgentPanel from './agent/panel'
+import DataSourceNode from './data-source/node'
+import DataSourcePanel from './data-source/panel'
+import KnowledgeBaseNode from './knowledge-base/node'
+import KnowledgeBasePanel from './knowledge-base/panel'
+
+export const NodeComponentMap: Record> = {
+ [BlockEnum.Start]: StartNode,
+ [BlockEnum.End]: EndNode,
+ [BlockEnum.Answer]: AnswerNode,
+ [BlockEnum.LLM]: LLMNode,
+ [BlockEnum.KnowledgeRetrieval]: KnowledgeRetrievalNode,
+ [BlockEnum.QuestionClassifier]: QuestionClassifierNode,
+ [BlockEnum.IfElse]: IfElseNode,
+ [BlockEnum.Code]: CodeNode,
+ [BlockEnum.TemplateTransform]: TemplateTransformNode,
+ [BlockEnum.HttpRequest]: HttpNode,
+ [BlockEnum.Tool]: ToolNode,
+ [BlockEnum.VariableAssigner]: VariableAssignerNode,
+ [BlockEnum.Assigner]: AssignerNode,
+ [BlockEnum.VariableAggregator]: VariableAssignerNode,
+ [BlockEnum.ParameterExtractor]: ParameterExtractorNode,
+ [BlockEnum.Iteration]: IterationNode,
+ [BlockEnum.Loop]: LoopNode,
+ [BlockEnum.DocExtractor]: DocExtractorNode,
+ [BlockEnum.ListFilter]: ListFilterNode,
+ [BlockEnum.Agent]: AgentNode,
+ [BlockEnum.DataSource]: DataSourceNode,
+ [BlockEnum.KnowledgeBase]: KnowledgeBaseNode,
+}
+
+export const PanelComponentMap: Record> = {
+ [BlockEnum.Start]: StartPanel,
+ [BlockEnum.End]: EndPanel,
+ [BlockEnum.Answer]: AnswerPanel,
+ [BlockEnum.LLM]: LLMPanel,
+ [BlockEnum.KnowledgeRetrieval]: KnowledgeRetrievalPanel,
+ [BlockEnum.QuestionClassifier]: QuestionClassifierPanel,
+ [BlockEnum.IfElse]: IfElsePanel,
+ [BlockEnum.Code]: CodePanel,
+ [BlockEnum.TemplateTransform]: TemplateTransformPanel,
+ [BlockEnum.HttpRequest]: HttpPanel,
+ [BlockEnum.Tool]: ToolPanel,
+ [BlockEnum.VariableAssigner]: VariableAssignerPanel,
+ [BlockEnum.VariableAggregator]: VariableAssignerPanel,
+ [BlockEnum.Assigner]: AssignerPanel,
+ [BlockEnum.ParameterExtractor]: ParameterExtractorPanel,
+ [BlockEnum.Iteration]: IterationPanel,
+ [BlockEnum.Loop]: LoopPanel,
+ [BlockEnum.DocExtractor]: DocExtractorPanel,
+ [BlockEnum.ListFilter]: ListFilterPanel,
+ [BlockEnum.Agent]: AgentPanel,
+ [BlockEnum.DataSource]: DataSourcePanel,
+ [BlockEnum.KnowledgeBase]: KnowledgeBasePanel,
+}
diff --git a/web/app/components/workflow/nodes/constants.ts b/web/app/components/workflow/nodes/constants.ts
index 3efc7189ed..78684577f2 100644
--- a/web/app/components/workflow/nodes/constants.ts
+++ b/web/app/components/workflow/nodes/constants.ts
@@ -1,101 +1,5 @@
-import type { ComponentType } from 'react'
-import { BlockEnum } from '../types'
-import StartNode from './start/node'
-import StartPanel from './start/panel'
-import EndNode from './end/node'
-import EndPanel from './end/panel'
-import AnswerNode from './answer/node'
-import AnswerPanel from './answer/panel'
-import LLMNode from './llm/node'
-import LLMPanel from './llm/panel'
-import KnowledgeRetrievalNode from './knowledge-retrieval/node'
-import KnowledgeRetrievalPanel from './knowledge-retrieval/panel'
-import QuestionClassifierNode from './question-classifier/node'
-import QuestionClassifierPanel from './question-classifier/panel'
-import IfElseNode from './if-else/node'
-import IfElsePanel from './if-else/panel'
-import CodeNode from './code/node'
-import CodePanel from './code/panel'
-import TemplateTransformNode from './template-transform/node'
-import TemplateTransformPanel from './template-transform/panel'
-import HttpNode from './http/node'
-import HttpPanel from './http/panel'
-import ToolNode from './tool/node'
-import ToolPanel from './tool/panel'
-import VariableAssignerNode from './variable-assigner/node'
-import VariableAssignerPanel from './variable-assigner/panel'
-import AssignerNode from './assigner/node'
-import AssignerPanel from './assigner/panel'
-import ParameterExtractorNode from './parameter-extractor/node'
-import ParameterExtractorPanel from './parameter-extractor/panel'
-import IterationNode from './iteration/node'
-import IterationPanel from './iteration/panel'
-import LoopNode from './loop/node'
-import LoopPanel from './loop/panel'
-import DocExtractorNode from './document-extractor/node'
-import DocExtractorPanel from './document-extractor/panel'
-import ListFilterNode from './list-operator/node'
-import ListFilterPanel from './list-operator/panel'
-import AgentNode from './agent/node'
-import AgentPanel from './agent/panel'
-import DataSourceNode from './data-source/node'
-import DataSourcePanel from './data-source/panel'
-import KnowledgeBaseNode from './knowledge-base/node'
-import KnowledgeBasePanel from './knowledge-base/panel'
import { TransferMethod } from '@/types/app'
-export const NodeComponentMap: Record> = {
- [BlockEnum.Start]: StartNode,
- [BlockEnum.End]: EndNode,
- [BlockEnum.Answer]: AnswerNode,
- [BlockEnum.LLM]: LLMNode,
- [BlockEnum.KnowledgeRetrieval]: KnowledgeRetrievalNode,
- [BlockEnum.QuestionClassifier]: QuestionClassifierNode,
- [BlockEnum.IfElse]: IfElseNode,
- [BlockEnum.Code]: CodeNode,
- [BlockEnum.TemplateTransform]: TemplateTransformNode,
- [BlockEnum.HttpRequest]: HttpNode,
- [BlockEnum.Tool]: ToolNode,
- [BlockEnum.VariableAssigner]: VariableAssignerNode,
- [BlockEnum.Assigner]: AssignerNode,
- [BlockEnum.VariableAggregator]: VariableAssignerNode,
- [BlockEnum.ParameterExtractor]: ParameterExtractorNode,
- [BlockEnum.Iteration]: IterationNode,
- [BlockEnum.Loop]: LoopNode,
- [BlockEnum.DocExtractor]: DocExtractorNode,
- [BlockEnum.ListFilter]: ListFilterNode,
- [BlockEnum.Agent]: AgentNode,
- [BlockEnum.DataSource]: DataSourceNode,
- [BlockEnum.KnowledgeBase]: KnowledgeBaseNode,
-}
-
-export const PanelComponentMap: Record> = {
- [BlockEnum.Start]: StartPanel,
- [BlockEnum.End]: EndPanel,
- [BlockEnum.Answer]: AnswerPanel,
- [BlockEnum.LLM]: LLMPanel,
- [BlockEnum.KnowledgeRetrieval]: KnowledgeRetrievalPanel,
- [BlockEnum.QuestionClassifier]: QuestionClassifierPanel,
- [BlockEnum.IfElse]: IfElsePanel,
- [BlockEnum.Code]: CodePanel,
- [BlockEnum.TemplateTransform]: TemplateTransformPanel,
- [BlockEnum.HttpRequest]: HttpPanel,
- [BlockEnum.Tool]: ToolPanel,
- [BlockEnum.VariableAssigner]: VariableAssignerPanel,
- [BlockEnum.VariableAggregator]: VariableAssignerPanel,
- [BlockEnum.Assigner]: AssignerPanel,
- [BlockEnum.ParameterExtractor]: ParameterExtractorPanel,
- [BlockEnum.Iteration]: IterationPanel,
- [BlockEnum.Loop]: LoopPanel,
- [BlockEnum.DocExtractor]: DocExtractorPanel,
- [BlockEnum.ListFilter]: ListFilterPanel,
- [BlockEnum.Agent]: AgentPanel,
- [BlockEnum.DataSource]: DataSourcePanel,
- [BlockEnum.KnowledgeBase]: KnowledgeBasePanel,
-}
-
-export const CUSTOM_NODE_TYPE = 'custom'
-
export const FILE_TYPE_OPTIONS = [
{ value: 'image', i18nKey: 'image' },
{ value: 'document', i18nKey: 'doc' },
diff --git a/web/app/components/workflow/nodes/http/components/timeout/index.tsx b/web/app/components/workflow/nodes/http/components/timeout/index.tsx
index 40ebab0e2a..bb84091d67 100644
--- a/web/app/components/workflow/nodes/http/components/timeout/index.tsx
+++ b/web/app/components/workflow/nodes/http/components/timeout/index.tsx
@@ -5,6 +5,8 @@ import { useTranslation } from 'react-i18next'
import type { Timeout as TimeoutPayloadType } from '../../types'
import Input from '@/app/components/base/input'
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
+import { useStore } from '@/app/components/workflow/store'
+import { BlockEnum } from '@/app/components/workflow/types'
type Props = {
readonly: boolean
@@ -61,6 +63,11 @@ const Timeout: FC = ({ readonly, payload, onChange }) => {
const { t } = useTranslation()
const { connect, read, write, max_connect_timeout, max_read_timeout, max_write_timeout } = payload ?? {}
+ // Get default config from store for max timeout values
+ const nodesDefaultConfigs = useStore(s => s.nodesDefaultConfigs)
+ const defaultConfig = nodesDefaultConfigs?.[BlockEnum.HttpRequest]
+ const defaultTimeout = defaultConfig?.timeout || {}
+
return (
@@ -73,7 +80,7 @@ const Timeout: FC
= ({ readonly, payload, onChange }) => {
value={connect}
onChange={v => onChange?.({ ...payload, connect: v })}
min={1}
- max={max_connect_timeout || 300}
+ max={max_connect_timeout || defaultTimeout.max_connect_timeout || 10}
/>
= ({ readonly, payload, onChange }) => {
value={read}
onChange={v => onChange?.({ ...payload, read: v })}
min={1}
- max={max_read_timeout || 600}
+ max={max_read_timeout || defaultTimeout.max_read_timeout || 600}
/>
= ({ readonly, payload, onChange }) => {
value={write}
onChange={v => onChange?.({ ...payload, write: v })}
min={1}
- max={max_write_timeout || 600}
+ max={max_write_timeout || defaultTimeout.max_write_timeout || 600}
/>
diff --git a/web/app/components/workflow/nodes/index.tsx b/web/app/components/workflow/nodes/index.tsx
index 8458051da2..ba880b398b 100644
--- a/web/app/components/workflow/nodes/index.tsx
+++ b/web/app/components/workflow/nodes/index.tsx
@@ -8,7 +8,7 @@ import { CUSTOM_NODE } from '../constants'
import {
NodeComponentMap,
PanelComponentMap,
-} from './constants'
+} from './components'
import BaseNode from './_base/node'
import BasePanel from './_base/components/workflow-panel'
diff --git a/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/index.tsx b/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/index.tsx
index 6410ab706f..60aa3d5590 100644
--- a/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/index.tsx
+++ b/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/index.tsx
@@ -29,7 +29,7 @@ const ChunkStructure = ({
{
e.stopPropagation()
- !readonly && enableSelect && id && onClick?.(id)
+ if (!readonly && enableSelect && id)
+ onClick?.(id)
}}
>
= ({
setJson(JSON.stringify(schema, null, 2))
}, [currentTab])
- const handleSubmit = useCallback((schema: any) => {
+ const handleSubmit = useCallback((schema: Record
) => {
const jsonSchema = jsonToSchema(schema) as SchemaRoot
if (currentTab === SchemaView.VisualEditor)
setJsonSchema(jsonSchema)
@@ -139,8 +139,10 @@ const JsonSchemaConfig: FC = ({
const handleResetDefaults = useCallback(() => {
if (currentTab === SchemaView.VisualEditor) {
setHoveringProperty(null)
- advancedEditing && setAdvancedEditing(false)
- isAddingNewField && setIsAddingNewField(false)
+ if (advancedEditing)
+ setAdvancedEditing(false)
+ if (isAddingNewField)
+ setIsAddingNewField(false)
}
setJsonSchema(DEFAULT_SCHEMA)
setJson(JSON.stringify(DEFAULT_SCHEMA, null, 2))
diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx
index ae72d494d1..4aa0f99d3f 100644
--- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx
+++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx
@@ -87,8 +87,10 @@ const EditCard: FC = ({
})
useSubscribe('fieldChangeSuccess', () => {
- isAddingNewField && setIsAddingNewField(false)
- advancedEditing && setAdvancedEditing(false)
+ if (isAddingNewField)
+ setIsAddingNewField(false)
+ if (advancedEditing)
+ setAdvancedEditing(false)
})
const emitPropertyNameChange = useCallback(() => {
@@ -150,14 +152,16 @@ const EditCard: FC = ({
}, [isAdvancedEditing, emitPropertyOptionsChange, currentFields])
const handleAdvancedOptionsChange = useCallback((options: AdvancedOptionsType) => {
- let enumValue: any = options.enum
- if (enumValue === '') {
+ let enumValue: SchemaEnumType | undefined
+ if (options.enum === '') {
enumValue = undefined
}
else {
- enumValue = options.enum.replace(/\s/g, '').split(',')
+ const stringArray = options.enum.replace(/\s/g, '').split(',')
if (currentFields.type === Type.number)
- enumValue = (enumValue as SchemaEnumType).map(value => Number(value)).filter(num => !Number.isNaN(num))
+ enumValue = stringArray.map(value => Number(value)).filter(num => !Number.isNaN(num))
+ else
+ enumValue = stringArray
}
setCurrentFields(prev => ({ ...prev, enum: enumValue }))
if (isAdvancedEditing) return
diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts
index 8256a3c862..4f7e1e6f00 100644
--- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts
+++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts
@@ -45,8 +45,10 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
onChange(backupSchema)
setBackupSchema(null)
}
- isAddingNewField && setIsAddingNewField(false)
- advancedEditing && setAdvancedEditing(false)
+ if (isAddingNewField)
+ setIsAddingNewField(false)
+ if (advancedEditing)
+ setAdvancedEditing(false)
setHoveringProperty(null)
})
@@ -221,7 +223,8 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
})
useSubscribe('addField', (params) => {
- advancedEditing && setAdvancedEditing(false)
+ if (advancedEditing)
+ setAdvancedEditing(false)
setBackupSchema(jsonSchema)
const { path } = params as AddEventParams
setIsAddingNewField(true)
diff --git a/web/app/components/workflow/nodes/llm/panel.tsx b/web/app/components/workflow/nodes/llm/panel.tsx
index 03569f6f7a..cd79b9f3d9 100644
--- a/web/app/components/workflow/nodes/llm/panel.tsx
+++ b/web/app/components/workflow/nodes/llm/panel.tsx
@@ -293,6 +293,11 @@ const Panel: FC> = ({
type='string'
description={t(`${i18nPrefix}.outputVars.output`)}
/>
+
{
diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal-trigger.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal-trigger.tsx
index 07eb79a269..9d19b61093 100644
--- a/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal-trigger.tsx
+++ b/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal-trigger.tsx
@@ -35,7 +35,8 @@ const VariableModalTrigger = ({
open={open}
onOpenChange={() => {
setOpen(v => !v)
- open && onClose()
+ if (open)
+ onClose()
}}
placement='left-start'
offset={{
@@ -45,7 +46,8 @@ const VariableModalTrigger = ({
>
{
setOpen(v => !v)
- open && onClose()
+ if (open)
+ onClose()
}}>
diff --git a/web/app/components/workflow/panel/env-panel/variable-trigger.tsx b/web/app/components/workflow/panel/env-panel/variable-trigger.tsx
index 52474860b1..604fceef81 100644
--- a/web/app/components/workflow/panel/env-panel/variable-trigger.tsx
+++ b/web/app/components/workflow/panel/env-panel/variable-trigger.tsx
@@ -33,7 +33,8 @@ const VariableTrigger = ({
open={open}
onOpenChange={() => {
setOpen(v => !v)
- open && onClose()
+ if (open)
+ onClose()
}}
placement='left-start'
offset={{
@@ -43,7 +44,8 @@ const VariableTrigger = ({
>
{
setOpen(v => !v)
- open && onClose()
+ if (open)
+ onClose()
}}>
diff --git a/web/app/components/workflow/run/index.tsx b/web/app/components/workflow/run/index.tsx
index 2c5cc165c1..2e9ae392a6 100644
--- a/web/app/components/workflow/run/index.tsx
+++ b/web/app/components/workflow/run/index.tsx
@@ -81,9 +81,12 @@ const RunPanel: FC = ({
const switchTab = async (tab: string) => {
setCurrentTab(tab)
- if (tab === 'RESULT')
- runDetailUrl && await getResult()
- tracingListUrl && await getTracingList()
+ if (tab === 'RESULT') {
+ if (runDetailUrl)
+ await getResult()
+ }
+ if (tracingListUrl)
+ await getTracingList()
}
useEffect(() => {
diff --git a/web/app/components/workflow/utils/dagre-layout.ts b/web/app/components/workflow/utils/dagre-layout.ts
deleted file mode 100644
index 5eafe77586..0000000000
--- a/web/app/components/workflow/utils/dagre-layout.ts
+++ /dev/null
@@ -1,246 +0,0 @@
-import dagre from '@dagrejs/dagre'
-import {
- cloneDeep,
-} from 'lodash-es'
-import type {
- Edge,
- Node,
-} from '../types'
-import {
- BlockEnum,
-} from '../types'
-import {
- CUSTOM_NODE,
- NODE_LAYOUT_HORIZONTAL_PADDING,
- NODE_LAYOUT_MIN_DISTANCE,
- NODE_LAYOUT_VERTICAL_PADDING,
-} from '../constants'
-import { CUSTOM_ITERATION_START_NODE } from '../nodes/iteration-start/constants'
-import { CUSTOM_LOOP_START_NODE } from '../nodes/loop-start/constants'
-
-export const getLayoutByDagre = (originNodes: Node[], originEdges: Edge[]) => {
- const dagreGraph = new dagre.graphlib.Graph({ compound: true })
- dagreGraph.setDefaultEdgeLabel(() => ({}))
-
- const nodes = cloneDeep(originNodes).filter(node => !node.parentId && node.type === CUSTOM_NODE)
- const edges = cloneDeep(originEdges).filter(edge => (!edge.data?.isInIteration && !edge.data?.isInLoop))
-
-// The default dagre layout algorithm often fails to correctly order the branches
-// of an If/Else node, leading to crossed edges.
-//
-// To solve this, we employ a "virtual container" strategy:
-// 1. A virtual, compound parent node (the "container") is created for each If/Else node's branches.
-// 2. Each direct child of the If/Else node is preceded by a virtual dummy node. These dummies are placed inside the container.
-// 3. A rigid, sequential chain of invisible edges is created between these dummy nodes (e.g., dummy_IF -> dummy_ELIF -> dummy_ELSE).
-//
-// This forces dagre to treat the ordered branches as an unbreakable, atomic group,
-// ensuring their layout respects the intended logical sequence.
- const ifElseNodes = nodes.filter(node => node.data.type === BlockEnum.IfElse)
- let virtualLogicApplied = false
-
- ifElseNodes.forEach((ifElseNode) => {
- const childEdges = edges.filter(e => e.source === ifElseNode.id)
- if (childEdges.length <= 1)
- return
-
- virtualLogicApplied = true
- const sortedChildEdges = childEdges.sort((edgeA, edgeB) => {
- const handleA = edgeA.sourceHandle
- const handleB = edgeB.sourceHandle
-
- if (handleA && handleB) {
- const cases = (ifElseNode.data as any).cases || []
- const isAElse = handleA === 'false'
- const isBElse = handleB === 'false'
-
- if (isAElse) return 1
- if (isBElse) return -1
-
- const indexA = cases.findIndex((c: any) => c.case_id === handleA)
- const indexB = cases.findIndex((c: any) => c.case_id === handleB)
-
- if (indexA !== -1 && indexB !== -1)
- return indexA - indexB
- }
- return 0
- })
-
- const parentDummyId = `dummy-parent-${ifElseNode.id}`
- dagreGraph.setNode(parentDummyId, { width: 1, height: 1 })
-
- const dummyNodes: string[] = []
- sortedChildEdges.forEach((edge) => {
- const dummyNodeId = `dummy-${edge.source}-${edge.target}`
- dummyNodes.push(dummyNodeId)
- dagreGraph.setNode(dummyNodeId, { width: 1, height: 1 })
- dagreGraph.setParent(dummyNodeId, parentDummyId)
-
- const edgeIndex = edges.findIndex(e => e.id === edge.id)
- if (edgeIndex > -1)
- edges.splice(edgeIndex, 1)
-
- edges.push({ id: `e-${edge.source}-${dummyNodeId}`, source: edge.source, target: dummyNodeId, sourceHandle: edge.sourceHandle } as Edge)
- edges.push({ id: `e-${dummyNodeId}-${edge.target}`, source: dummyNodeId, target: edge.target, targetHandle: edge.targetHandle } as Edge)
- })
-
- for (let i = 0; i < dummyNodes.length - 1; i++) {
- const sourceDummy = dummyNodes[i]
- const targetDummy = dummyNodes[i + 1]
- edges.push({ id: `e-dummy-${sourceDummy}-${targetDummy}`, source: sourceDummy, target: targetDummy } as Edge)
- }
- })
-
- dagreGraph.setGraph({
- rankdir: 'LR',
- align: 'UL',
- nodesep: 40,
- ranksep: virtualLogicApplied ? 30 : 60,
- ranker: 'tight-tree',
- marginx: 30,
- marginy: 200,
- })
-
- nodes.forEach((node) => {
- dagreGraph.setNode(node.id, {
- width: node.width!,
- height: node.height!,
- })
- })
- edges.forEach((edge) => {
- dagreGraph.setEdge(edge.source, edge.target)
- })
- dagre.layout(dagreGraph)
- return dagreGraph
-}
-
-export const getLayoutForChildNodes = (parentNodeId: string, originNodes: Node[], originEdges: Edge[]) => {
- const dagreGraph = new dagre.graphlib.Graph()
- dagreGraph.setDefaultEdgeLabel(() => ({}))
-
- const nodes = cloneDeep(originNodes).filter(node => node.parentId === parentNodeId)
- const edges = cloneDeep(originEdges).filter(edge =>
- (edge.data?.isInIteration && edge.data?.iteration_id === parentNodeId)
- || (edge.data?.isInLoop && edge.data?.loop_id === parentNodeId),
- )
-
- const startNode = nodes.find(node =>
- node.type === CUSTOM_ITERATION_START_NODE
- || node.type === CUSTOM_LOOP_START_NODE
- || node.data?.type === BlockEnum.LoopStart
- || node.data?.type === BlockEnum.IterationStart,
- )
-
- if (!startNode) {
- dagreGraph.setGraph({
- rankdir: 'LR',
- align: 'UL',
- nodesep: 40,
- ranksep: 60,
- marginx: NODE_LAYOUT_HORIZONTAL_PADDING,
- marginy: NODE_LAYOUT_VERTICAL_PADDING,
- })
-
- nodes.forEach((node) => {
- dagreGraph.setNode(node.id, {
- width: node.width || 244,
- height: node.height || 100,
- })
- })
-
- edges.forEach((edge) => {
- dagreGraph.setEdge(edge.source, edge.target)
- })
-
- dagre.layout(dagreGraph)
- return dagreGraph
- }
-
- const startNodeOutEdges = edges.filter(edge => edge.source === startNode.id)
- const firstConnectedNodes = startNodeOutEdges.map(edge =>
- nodes.find(node => node.id === edge.target),
- ).filter(Boolean) as Node[]
-
- const nonStartNodes = nodes.filter(node => node.id !== startNode.id)
- const nonStartEdges = edges.filter(edge => edge.source !== startNode.id && edge.target !== startNode.id)
-
- dagreGraph.setGraph({
- rankdir: 'LR',
- align: 'UL',
- nodesep: 40,
- ranksep: 60,
- marginx: NODE_LAYOUT_HORIZONTAL_PADDING / 2,
- marginy: NODE_LAYOUT_VERTICAL_PADDING / 2,
- })
-
- nonStartNodes.forEach((node) => {
- dagreGraph.setNode(node.id, {
- width: node.width || 244,
- height: node.height || 100,
- })
- })
-
- nonStartEdges.forEach((edge) => {
- dagreGraph.setEdge(edge.source, edge.target)
- })
-
- dagre.layout(dagreGraph)
-
- const startNodeSize = {
- width: startNode.width || 44,
- height: startNode.height || 48,
- }
-
- const startNodeX = NODE_LAYOUT_HORIZONTAL_PADDING / 1.5
- let startNodeY = 100
-
- let minFirstLayerX = Infinity
- let avgFirstLayerY = 0
- let firstLayerCount = 0
-
- if (firstConnectedNodes.length > 0) {
- firstConnectedNodes.forEach((node) => {
- if (dagreGraph.node(node.id)) {
- const nodePos = dagreGraph.node(node.id)
- avgFirstLayerY += nodePos.y
- firstLayerCount++
- minFirstLayerX = Math.min(minFirstLayerX, nodePos.x - nodePos.width / 2)
- }
- })
-
- if (firstLayerCount > 0) {
- avgFirstLayerY /= firstLayerCount
- startNodeY = avgFirstLayerY
- }
-
- const minRequiredX = startNodeX + startNodeSize.width + NODE_LAYOUT_MIN_DISTANCE
-
- if (minFirstLayerX < minRequiredX) {
- const shiftX = minRequiredX - minFirstLayerX
-
- nonStartNodes.forEach((node) => {
- if (dagreGraph.node(node.id)) {
- const nodePos = dagreGraph.node(node.id)
- dagreGraph.setNode(node.id, {
- x: nodePos.x + shiftX,
- y: nodePos.y,
- width: nodePos.width,
- height: nodePos.height,
- })
- }
- })
- }
- }
-
- dagreGraph.setNode(startNode.id, {
- x: startNodeX + startNodeSize.width / 2,
- y: startNodeY,
- width: startNodeSize.width,
- height: startNodeSize.height,
- })
-
- startNodeOutEdges.forEach((edge) => {
- dagreGraph.setEdge(edge.source, edge.target)
- })
-
- return dagreGraph
-}
diff --git a/web/app/components/workflow/utils/index.ts b/web/app/components/workflow/utils/index.ts
index ab59f513bc..e9ae2d1ef0 100644
--- a/web/app/components/workflow/utils/index.ts
+++ b/web/app/components/workflow/utils/index.ts
@@ -1,7 +1,7 @@
export * from './node'
export * from './edge'
export * from './workflow-init'
-export * from './dagre-layout'
+export * from './layout'
export * from './common'
export * from './tool'
export * from './workflow'
diff --git a/web/app/components/workflow/utils/layout.ts b/web/app/components/workflow/utils/layout.ts
new file mode 100644
index 0000000000..b3cf3b0d88
--- /dev/null
+++ b/web/app/components/workflow/utils/layout.ts
@@ -0,0 +1,529 @@
+import ELK from 'elkjs/lib/elk.bundled.js'
+import type { ElkNode, LayoutOptions } from 'elkjs/lib/elk-api'
+import { cloneDeep } from 'lodash-es'
+import type {
+ Edge,
+ Node,
+} from '../types'
+import {
+ BlockEnum,
+} from '../types'
+import {
+ CUSTOM_NODE,
+ NODE_LAYOUT_HORIZONTAL_PADDING,
+ NODE_LAYOUT_VERTICAL_PADDING,
+} from '../constants'
+import { CUSTOM_ITERATION_START_NODE } from '../nodes/iteration-start/constants'
+import { CUSTOM_LOOP_START_NODE } from '../nodes/loop-start/constants'
+import type { CaseItem, IfElseNodeType } from '../nodes/if-else/types'
+
+// Although the file name refers to Dagre, the implementation now relies on ELK's layered algorithm.
+// Keep the export signatures unchanged to minimise the blast radius while we migrate the layout stack.
+
+const elk = new ELK()
+
+const DEFAULT_NODE_WIDTH = 244
+const DEFAULT_NODE_HEIGHT = 100
+
+const ROOT_LAYOUT_OPTIONS = {
+ 'elk.algorithm': 'layered',
+ 'elk.direction': 'RIGHT',
+
+ // === Spacing - Maximum spacing to prevent any overlap ===
+ 'elk.layered.spacing.nodeNodeBetweenLayers': '100',
+ 'elk.spacing.nodeNode': '80',
+ 'elk.spacing.edgeNode': '50',
+ 'elk.spacing.edgeEdge': '30',
+ 'elk.spacing.edgeLabel': '10',
+ 'elk.spacing.portPort': '20',
+
+ // === Port Configuration ===
+ 'elk.portConstraints': 'FIXED_ORDER',
+ 'elk.layered.considerModelOrder.strategy': 'PREFER_EDGES',
+ 'elk.port.side': 'SOUTH',
+
+ // === Node Placement - Best quality ===
+ 'elk.layered.nodePlacement.strategy': 'NETWORK_SIMPLEX',
+ 'elk.layered.nodePlacement.favorStraightEdges': 'true',
+ 'elk.layered.nodePlacement.linearSegments.deflectionDampening': '0.5',
+ 'elk.layered.nodePlacement.networkSimplex.nodeFlexibility': 'NODE_SIZE',
+
+ // === Edge Routing - Maximum quality ===
+ 'elk.edgeRouting': 'SPLINES',
+ 'elk.layered.edgeRouting.selfLoopPlacement': 'NORTH',
+ 'elk.layered.edgeRouting.sloppySplineRouting': 'false',
+ 'elk.layered.edgeRouting.splines.mode': 'CONSERVATIVE',
+ 'elk.layered.edgeRouting.splines.sloppy.layerSpacingFactor': '1.2',
+
+ // === Crossing Minimization - Most aggressive ===
+ 'elk.layered.crossingMinimization.strategy': 'LAYER_SWEEP',
+ 'elk.layered.crossingMinimization.greedySwitch.type': 'TWO_SIDED',
+ 'elk.layered.crossingMinimization.greedySwitchHierarchical.type': 'TWO_SIDED',
+ 'elk.layered.crossingMinimization.semiInteractive': 'true',
+ 'elk.layered.crossingMinimization.hierarchicalSweepiness': '0.9',
+
+ // === Layering Strategy - Best quality ===
+ 'elk.layered.layering.strategy': 'NETWORK_SIMPLEX',
+ 'elk.layered.layering.networkSimplex.nodeFlexibility': 'NODE_SIZE',
+ 'elk.layered.layering.layerConstraint': 'NONE',
+ 'elk.layered.layering.minWidth.upperBoundOnWidth': '4',
+
+ // === Cycle Breaking ===
+ 'elk.layered.cycleBreaking.strategy': 'DEPTH_FIRST',
+
+ // === Connected Components ===
+ 'elk.separateConnectedComponents': 'true',
+ 'elk.spacing.componentComponent': '100',
+
+ // === Node Size Constraints ===
+ 'elk.nodeSize.constraints': 'NODE_LABELS',
+ 'elk.nodeSize.options': 'DEFAULT_MINIMUM_SIZE MINIMUM_SIZE_ACCOUNTS_FOR_PADDING',
+
+ // === Edge Label Placement ===
+ 'elk.edgeLabels.placement': 'CENTER',
+ 'elk.edgeLabels.inline': 'true',
+
+ // === Compaction ===
+ 'elk.layered.compaction.postCompaction.strategy': 'EDGE_LENGTH',
+ 'elk.layered.compaction.postCompaction.constraints': 'EDGE_LENGTH',
+
+ // === High-Quality Mode ===
+ 'elk.layered.thoroughness': '10',
+ 'elk.layered.wrapping.strategy': 'OFF',
+ 'elk.hierarchyHandling': 'INCLUDE_CHILDREN',
+
+ // === Additional Optimizations ===
+ 'elk.layered.feedbackEdges': 'true',
+ 'elk.layered.mergeEdges': 'false',
+ 'elk.layered.mergeHierarchyEdges': 'false',
+ 'elk.layered.allowNonFlowPortsToSwitchSides': 'false',
+ 'elk.layered.northOrSouthPort': 'false',
+ 'elk.partitioning.activate': 'false',
+ 'elk.junctionPoints': 'true',
+
+ // === Content Alignment ===
+ 'elk.contentAlignment': 'V_TOP H_LEFT',
+ 'elk.alignment': 'AUTOMATIC',
+}
+
+const CHILD_LAYOUT_OPTIONS = {
+ 'elk.algorithm': 'layered',
+ 'elk.direction': 'RIGHT',
+
+ // === Spacing - High quality for child nodes ===
+ 'elk.layered.spacing.nodeNodeBetweenLayers': '80',
+ 'elk.spacing.nodeNode': '60',
+ 'elk.spacing.edgeNode': '40',
+ 'elk.spacing.edgeEdge': '25',
+ 'elk.spacing.edgeLabel': '8',
+ 'elk.spacing.portPort': '15',
+
+ // === Node Placement - Best quality ===
+ 'elk.layered.nodePlacement.strategy': 'NETWORK_SIMPLEX',
+ 'elk.layered.nodePlacement.favorStraightEdges': 'true',
+ 'elk.layered.nodePlacement.linearSegments.deflectionDampening': '0.5',
+ 'elk.layered.nodePlacement.networkSimplex.nodeFlexibility': 'NODE_SIZE',
+
+ // === Edge Routing - Maximum quality ===
+ 'elk.edgeRouting': 'SPLINES',
+ 'elk.layered.edgeRouting.sloppySplineRouting': 'false',
+ 'elk.layered.edgeRouting.splines.mode': 'CONSERVATIVE',
+
+ // === Crossing Minimization - Aggressive ===
+ 'elk.layered.crossingMinimization.strategy': 'LAYER_SWEEP',
+ 'elk.layered.crossingMinimization.greedySwitch.type': 'TWO_SIDED',
+ 'elk.layered.crossingMinimization.semiInteractive': 'true',
+
+ // === Layering Strategy ===
+ 'elk.layered.layering.strategy': 'NETWORK_SIMPLEX',
+ 'elk.layered.layering.networkSimplex.nodeFlexibility': 'NODE_SIZE',
+
+ // === Cycle Breaking ===
+ 'elk.layered.cycleBreaking.strategy': 'DEPTH_FIRST',
+
+ // === Node Size ===
+ 'elk.nodeSize.constraints': 'NODE_LABELS',
+
+ // === Compaction ===
+ 'elk.layered.compaction.postCompaction.strategy': 'EDGE_LENGTH',
+
+ // === High-Quality Mode ===
+ 'elk.layered.thoroughness': '10',
+ 'elk.hierarchyHandling': 'INCLUDE_CHILDREN',
+
+ // === Additional Optimizations ===
+ 'elk.layered.feedbackEdges': 'true',
+ 'elk.layered.mergeEdges': 'false',
+ 'elk.junctionPoints': 'true',
+}
+
+type LayoutInfo = {
+ x: number
+ y: number
+ width: number
+ height: number
+ layer?: number
+}
+
+type LayoutBounds = {
+ minX: number
+ minY: number
+ maxX: number
+ maxY: number
+}
+
+export type LayoutResult = {
+ nodes: Map
+ bounds: LayoutBounds
+}
+
+// ELK Port definition for native port support
+type ElkPortShape = {
+ id: string
+ layoutOptions?: LayoutOptions
+}
+
+type ElkNodeShape = {
+ id: string
+ width: number
+ height: number
+ ports?: ElkPortShape[]
+ layoutOptions?: LayoutOptions
+ children?: ElkNodeShape[]
+}
+
+type ElkEdgeShape = {
+ id: string
+ sources: string[]
+ targets: string[]
+ sourcePort?: string
+ targetPort?: string
+}
+
+const toElkNode = (node: Node): ElkNodeShape => ({
+ id: node.id,
+ width: node.width ?? DEFAULT_NODE_WIDTH,
+ height: node.height ?? DEFAULT_NODE_HEIGHT,
+})
+
+let edgeCounter = 0
+const nextEdgeId = () => `elk-edge-${edgeCounter++}`
+
+const createEdge = (
+ source: string,
+ target: string,
+ sourcePort?: string,
+ targetPort?: string,
+): ElkEdgeShape => ({
+ id: nextEdgeId(),
+ sources: [source],
+ targets: [target],
+ sourcePort,
+ targetPort,
+})
+
+const collectLayout = (graph: ElkNode, predicate: (id: string) => boolean): LayoutResult => {
+ const result = new Map()
+ let minX = Infinity
+ let minY = Infinity
+ let maxX = -Infinity
+ let maxY = -Infinity
+
+ const visit = (node: ElkNode) => {
+ node.children?.forEach((child: ElkNode) => {
+ if (predicate(child.id)) {
+ const x = child.x ?? 0
+ const y = child.y ?? 0
+ const width = child.width ?? DEFAULT_NODE_WIDTH
+ const height = child.height ?? DEFAULT_NODE_HEIGHT
+ const layer = child?.layoutOptions?.['org.eclipse.elk.layered.layerIndex']
+
+ result.set(child.id, {
+ x,
+ y,
+ width,
+ height,
+ layer: layer ? Number.parseInt(layer) : undefined,
+ })
+
+ minX = Math.min(minX, x)
+ minY = Math.min(minY, y)
+ maxX = Math.max(maxX, x + width)
+ maxY = Math.max(maxY, y + height)
+ }
+
+ if (child.children?.length)
+ visit(child)
+ })
+ }
+
+ visit(graph)
+
+ if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
+ minX = 0
+ minY = 0
+ maxX = 0
+ maxY = 0
+ }
+
+ return {
+ nodes: result,
+ bounds: {
+ minX,
+ minY,
+ maxX,
+ maxY,
+ },
+ }
+}
+
+/**
+ * Build If/Else node with ELK native Ports instead of dummy nodes
+ * This is the recommended approach for handling multiple branches
+ */
+const buildIfElseWithPorts = (
+ ifElseNode: Node,
+ edges: Edge[],
+): { node: ElkNodeShape; portMap: Map } | null => {
+ const childEdges = edges.filter(edge => edge.source === ifElseNode.id)
+
+ if (childEdges.length <= 1)
+ return null
+
+ // Sort child edges according to case order
+ const sortedChildEdges = [...childEdges].sort((edgeA, edgeB) => {
+ const handleA = edgeA.sourceHandle
+ const handleB = edgeB.sourceHandle
+
+ if (handleA && handleB) {
+ const cases = (ifElseNode.data as IfElseNodeType).cases || []
+ const isAElse = handleA === 'false'
+ const isBElse = handleB === 'false'
+
+ if (isAElse)
+ return 1
+ if (isBElse)
+ return -1
+
+ const indexA = cases.findIndex((c: CaseItem) => c.case_id === handleA)
+ const indexB = cases.findIndex((c: CaseItem) => c.case_id === handleB)
+
+ if (indexA !== -1 && indexB !== -1)
+ return indexA - indexB
+ }
+
+ return 0
+ })
+
+ // Create ELK ports for each branch
+ const ports: ElkPortShape[] = sortedChildEdges.map((edge, index) => ({
+ id: `${ifElseNode.id}-port-${edge.sourceHandle || index}`,
+ layoutOptions: {
+ 'port.side': 'EAST', // Ports on the right side (matching 'RIGHT' direction)
+ 'port.index': String(index),
+ },
+ }))
+
+ // Build port mapping: sourceHandle -> portId
+ const portMap = new Map()
+ sortedChildEdges.forEach((edge, index) => {
+ const portId = `${ifElseNode.id}-port-${edge.sourceHandle || index}`
+ portMap.set(edge.id, portId)
+ })
+
+ return {
+ node: {
+ id: ifElseNode.id,
+ width: ifElseNode.width ?? DEFAULT_NODE_WIDTH,
+ height: ifElseNode.height ?? DEFAULT_NODE_HEIGHT,
+ ports,
+ layoutOptions: {
+ 'elk.portConstraints': 'FIXED_ORDER',
+ },
+ },
+ portMap,
+ }
+}
+
+const normaliseBounds = (layout: LayoutResult): LayoutResult => {
+ const {
+ nodes,
+ bounds,
+ } = layout
+
+ if (nodes.size === 0)
+ return layout
+
+ const offsetX = bounds.minX
+ const offsetY = bounds.minY
+
+ const adjustedNodes = new Map()
+ nodes.forEach((info, id) => {
+ adjustedNodes.set(id, {
+ ...info,
+ x: info.x - offsetX,
+ y: info.y - offsetY,
+ })
+ })
+
+ return {
+ nodes: adjustedNodes,
+ bounds: {
+ minX: 0,
+ minY: 0,
+ maxX: bounds.maxX - offsetX,
+ maxY: bounds.maxY - offsetY,
+ },
+ }
+}
+
+export const getLayoutByDagre = async (originNodes: Node[], originEdges: Edge[]): Promise => {
+ edgeCounter = 0
+ const nodes = cloneDeep(originNodes).filter(node => !node.parentId && node.type === CUSTOM_NODE)
+ const edges = cloneDeep(originEdges).filter(edge => (!edge.data?.isInIteration && !edge.data?.isInLoop))
+
+ const elkNodes: ElkNodeShape[] = []
+ const elkEdges: ElkEdgeShape[] = []
+
+ // Track which edges have been processed for If/Else nodes with ports
+ const edgeToPortMap = new Map()
+
+ // Build nodes with ports for If/Else nodes
+ nodes.forEach((node) => {
+ if (node.data.type === BlockEnum.IfElse) {
+ const portsResult = buildIfElseWithPorts(node, edges)
+ if (portsResult) {
+ // Use node with ports
+ elkNodes.push(portsResult.node)
+ // Store port mappings for edges
+ portsResult.portMap.forEach((portId, edgeId) => {
+ edgeToPortMap.set(edgeId, portId)
+ })
+ }
+ else {
+ // No multiple branches, use normal node
+ elkNodes.push(toElkNode(node))
+ }
+ }
+ else {
+ elkNodes.push(toElkNode(node))
+ }
+ })
+
+ // Build edges with port connections
+ edges.forEach((edge) => {
+ const sourcePort = edgeToPortMap.get(edge.id)
+ elkEdges.push(createEdge(edge.source, edge.target, sourcePort))
+ })
+
+ const graph = {
+ id: 'workflow-root',
+ layoutOptions: ROOT_LAYOUT_OPTIONS,
+ children: elkNodes,
+ edges: elkEdges,
+ }
+
+ const layoutedGraph = await elk.layout(graph)
+ // No need to filter dummy nodes anymore, as we're using ports
+ const layout = collectLayout(layoutedGraph, () => true)
+ return normaliseBounds(layout)
+}
+
+const normaliseChildLayout = (
+ layout: LayoutResult,
+ nodes: Node[],
+): LayoutResult => {
+ const result = new Map()
+ layout.nodes.forEach((info, id) => {
+ result.set(id, info)
+ })
+
+ // Ensure iteration / loop start nodes do not collapse into the children.
+ const startNode = nodes.find(node =>
+ node.type === CUSTOM_ITERATION_START_NODE
+ || node.type === CUSTOM_LOOP_START_NODE
+ || node.data?.type === BlockEnum.LoopStart
+ || node.data?.type === BlockEnum.IterationStart,
+ )
+
+ if (startNode) {
+ const startLayout = result.get(startNode.id)
+
+ if (startLayout) {
+ const desiredMinX = NODE_LAYOUT_HORIZONTAL_PADDING / 1.5
+ if (startLayout.x > desiredMinX) {
+ const shiftX = startLayout.x - desiredMinX
+ result.forEach((value, key) => {
+ result.set(key, {
+ ...value,
+ x: value.x - shiftX,
+ })
+ })
+ }
+
+ const desiredMinY = startLayout.y
+ const deltaY = NODE_LAYOUT_VERTICAL_PADDING / 2
+ result.forEach((value, key) => {
+ result.set(key, {
+ ...value,
+ y: value.y - desiredMinY + deltaY,
+ })
+ })
+ }
+ }
+
+ let minX = Infinity
+ let minY = Infinity
+ let maxX = -Infinity
+ let maxY = -Infinity
+
+ result.forEach((value) => {
+ minX = Math.min(minX, value.x)
+ minY = Math.min(minY, value.y)
+ maxX = Math.max(maxX, value.x + value.width)
+ maxY = Math.max(maxY, value.y + value.height)
+ })
+
+ if (!Number.isFinite(minX) || !Number.isFinite(minY))
+ return layout
+
+ return normaliseBounds({
+ nodes: result,
+ bounds: {
+ minX,
+ minY,
+ maxX,
+ maxY,
+ },
+ })
+}
+
+export const getLayoutForChildNodes = async (
+ parentNodeId: string,
+ originNodes: Node[],
+ originEdges: Edge[],
+): Promise => {
+ edgeCounter = 0
+ const nodes = cloneDeep(originNodes).filter(node => node.parentId === parentNodeId)
+ if (!nodes.length)
+ return null
+
+ const edges = cloneDeep(originEdges).filter(edge =>
+ (edge.data?.isInIteration && edge.data?.iteration_id === parentNodeId)
+ || (edge.data?.isInLoop && edge.data?.loop_id === parentNodeId),
+ )
+
+ const elkNodes: ElkNodeShape[] = nodes.map(toElkNode)
+ const elkEdges: ElkEdgeShape[] = edges.map(edge => createEdge(edge.source, edge.target))
+
+ const graph = {
+ id: parentNodeId,
+ layoutOptions: CHILD_LAYOUT_OPTIONS,
+ children: elkNodes,
+ edges: elkEdges,
+ }
+
+ const layoutedGraph = await elk.layout(graph)
+ const layout = collectLayout(layoutedGraph, () => true)
+ return normaliseChildLayout(layout, nodes)
+}
diff --git a/web/app/components/workflow/workflow-history-store.tsx b/web/app/components/workflow/workflow-history-store.tsx
index c250708177..96e87f4fd4 100644
--- a/web/app/components/workflow/workflow-history-store.tsx
+++ b/web/app/components/workflow/workflow-history-store.tsx
@@ -3,7 +3,7 @@ import { type StoreApi, create } from 'zustand'
import { type TemporalState, temporal } from 'zundo'
import isDeepEqual from 'fast-deep-equal'
import type { Edge, Node } from './types'
-import type { WorkflowHistoryEvent } from './hooks'
+import type { WorkflowHistoryEventT } from './hooks'
import { noop } from 'lodash-es'
export const WorkflowHistoryStoreContext = createContext({ store: null, shortcutsEnabled: true, setShortcutsEnabled: noop })
@@ -98,7 +98,7 @@ function createStore({
export type WorkflowHistoryStore = {
nodes: Node[]
edges: Edge[]
- workflowHistoryEvent: WorkflowHistoryEvent | undefined
+ workflowHistoryEvent: WorkflowHistoryEventT | undefined
workflowHistoryEventMeta?: WorkflowHistoryEventMeta
}
diff --git a/web/app/components/workflow/workflow-preview/components/nodes/loop/node.tsx b/web/app/components/workflow/workflow-preview/components/nodes/loop/node.tsx
index 4c0080ec70..f41fa120a6 100644
--- a/web/app/components/workflow/workflow-preview/components/nodes/loop/node.tsx
+++ b/web/app/components/workflow/workflow-preview/components/nodes/loop/node.tsx
@@ -15,7 +15,7 @@ import { useNodeLoopInteractions } from './hooks'
const Node: FC> = ({
id,
- data,
+ data: _data,
}) => {
const { zoom } = useViewport()
const nodesInitialized = useNodesInitialized()
diff --git a/web/app/signin/components/mail-and-password-auth.tsx b/web/app/signin/components/mail-and-password-auth.tsx
index aaadc0b197..5214b73ee0 100644
--- a/web/app/signin/components/mail-and-password-auth.tsx
+++ b/web/app/signin/components/mail-and-password-auth.tsx
@@ -19,7 +19,7 @@ type MailAndPasswordAuthProps = {
allowRegistration: boolean
}
-export default function MailAndPasswordAuth({ isInvite, isEmailSetup, allowRegistration }: MailAndPasswordAuthProps) {
+export default function MailAndPasswordAuth({ isInvite, isEmailSetup, allowRegistration: _allowRegistration }: MailAndPasswordAuthProps) {
const { t } = useTranslation()
const { locale } = useContext(I18NContext)
const router = useRouter()
diff --git a/web/i18n-config/index.ts b/web/i18n-config/index.ts
index fdb31c49b4..b2b83fa76a 100644
--- a/web/i18n-config/index.ts
+++ b/web/i18n-config/index.ts
@@ -14,7 +14,8 @@ export type Locale = typeof i18n['locales'][number]
export const setLocaleOnClient = async (locale: Locale, reloadPage = true) => {
Cookies.set(LOCALE_COOKIE_NAME, locale, { expires: 365 })
await changeLanguage(locale)
- reloadPage && location.reload()
+ if (reloadPage)
+ location.reload()
}
export const getLocaleOnClient = (): Locale => {
diff --git a/web/i18n/de-DE/billing.ts b/web/i18n/de-DE/billing.ts
index 98d4488fab..fc45f3889c 100644
--- a/web/i18n/de-DE/billing.ts
+++ b/web/i18n/de-DE/billing.ts
@@ -94,6 +94,8 @@ const translation = {
teamMember_one: '{{count,number}} Teammitglied',
documentsRequestQuotaTooltip: 'Gibt die Gesamtzahl der Aktionen an, die ein Arbeitsbereich pro Minute innerhalb der Wissensbasis ausführen kann, einschließlich der Erstellung, Löschung, Aktualisierung von Datensätzen, des Hochladens von Dokumenten, von Änderungen, der Archivierung und von Abfragen in der Wissensbasis. Diese Kennzahl wird verwendet, um die Leistung von Anfragen an die Wissensbasis zu bewerten. Wenn ein Sandbox-Nutzer beispielsweise in einer Minute 10 aufeinanderfolgende Testdurchläufe durchführt, wird sein Arbeitsbereich für die nächste Minute vorübergehend daran gehindert, die folgenden Aktionen auszuführen: Erstellung, Löschung, Aktualisierung von Datensätzen sowie das Hochladen oder Ändern von Dokumenten.',
startBuilding: 'Beginnen Sie mit der Entwicklung',
+ taxTipSecond: 'Wenn in Ihrer Region keine relevanten Steuervorschriften gelten, wird an der Kasse keine Steuer angezeigt und Ihnen werden während der gesamten Abonnementlaufzeit keine zusätzlichen Gebühren berechnet.',
+ taxTip: 'Alle Abonnementspreise (monatlich/jährlich) verstehen sich zuzüglich der geltenden Steuern (z. B. MwSt., Umsatzsteuer).',
},
plans: {
sandbox: {
diff --git a/web/i18n/de-DE/common.ts b/web/i18n/de-DE/common.ts
index 9431fbbf6a..d64b295121 100644
--- a/web/i18n/de-DE/common.ts
+++ b/web/i18n/de-DE/common.ts
@@ -61,6 +61,10 @@ const translation = {
selectAll: 'Alles auswählen',
deSelectAll: 'Alle abwählen',
config: 'Konfiguration',
+ yes: 'Ja',
+ deleteConfirmTitle: 'Löschen?',
+ no: 'Nein',
+ confirmAction: 'Bitte bestätigen Sie Ihre Aktion.',
},
placeholder: {
input: 'Bitte eingeben',
diff --git a/web/i18n/de-DE/workflow.ts b/web/i18n/de-DE/workflow.ts
index 71000897ca..ff1b9d76f9 100644
--- a/web/i18n/de-DE/workflow.ts
+++ b/web/i18n/de-DE/workflow.ts
@@ -444,6 +444,7 @@ const translation = {
},
outputVars: {
output: 'Generierter Inhalt',
+ reasoning_content: 'Reasoning-Inhalt',
usage: 'Nutzungsinformationen des Modells',
},
singleRun: {
diff --git a/web/i18n/en-US/billing.ts b/web/i18n/en-US/billing.ts
index 72cf9a3fca..9169631281 100644
--- a/web/i18n/en-US/billing.ts
+++ b/web/i18n/en-US/billing.ts
@@ -37,6 +37,8 @@ const translation = {
save: 'Save ',
free: 'Free',
annualBilling: 'Bill Annually Save {{percent}}%',
+ taxTip: 'All subscription prices (monthly/annual) exclude applicable taxes (e.g., VAT, sales tax).',
+ taxTipSecond: 'If your region has no applicable tax requirements, no tax will appear in your checkout, and you won’t be charged any additional fees for the entire subscription term.',
comparePlanAndFeatures: 'Compare plans & features',
priceTip: 'per workspace/',
currentPlan: 'Current Plan',
diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts
index b9d315388f..4f177ca0c3 100644
--- a/web/i18n/en-US/common.ts
+++ b/web/i18n/en-US/common.ts
@@ -18,6 +18,10 @@ const translation = {
cancel: 'Cancel',
clear: 'Clear',
save: 'Save',
+ yes: 'Yes',
+ no: 'No',
+ deleteConfirmTitle: 'Delete?',
+ confirmAction: 'Please confirm your action.',
saveAndEnable: 'Save & Enable',
edit: 'Edit',
add: 'Add',
diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts
index 3f1654b2e7..a5862bef86 100644
--- a/web/i18n/en-US/workflow.ts
+++ b/web/i18n/en-US/workflow.ts
@@ -456,6 +456,7 @@ const translation = {
},
outputVars: {
output: 'Generate content',
+ reasoning_content: 'Reasoning Content',
usage: 'Model Usage Information',
},
singleRun: {
diff --git a/web/i18n/es-ES/billing.ts b/web/i18n/es-ES/billing.ts
index c5d4ef95b9..a8180e2d07 100644
--- a/web/i18n/es-ES/billing.ts
+++ b/web/i18n/es-ES/billing.ts
@@ -94,6 +94,8 @@ const translation = {
apiRateLimitTooltip: 'El límite de tasa de la API se aplica a todas las solicitudes realizadas a través de la API de Dify, incluidos la generación de texto, las conversaciones de chat, las ejecuciones de flujo de trabajo y el procesamiento de documentos.',
documentsRequestQuotaTooltip: 'Especifica el número total de acciones que un espacio de trabajo puede realizar por minuto dentro de la base de conocimientos, incluyendo la creación, eliminación, actualización de conjuntos de datos, carga de documentos, modificaciones, archivo y consultas a la base de conocimientos. Esta métrica se utiliza para evaluar el rendimiento de las solicitudes a la base de conocimientos. Por ejemplo, si un usuario de Sandbox realiza 10 pruebas consecutivas en un minuto, su espacio de trabajo será temporalmente restringido de realizar las siguientes acciones durante el siguiente minuto: creación de conjuntos de datos, eliminación, actualizaciones y carga o modificaciones de documentos.',
startBuilding: 'Empezar a construir',
+ taxTip: 'Todos los precios de suscripción (mensuales/anuales) excluyen los impuestos aplicables (por ejemplo, IVA, impuesto sobre ventas).',
+ taxTipSecond: 'Si su región no tiene requisitos fiscales aplicables, no se mostrará ningún impuesto en su pago y no se le cobrará ninguna tarifa adicional durante todo el período de suscripción.',
},
plans: {
sandbox: {
diff --git a/web/i18n/es-ES/common.ts b/web/i18n/es-ES/common.ts
index 74af4a03b6..55c2c5e474 100644
--- a/web/i18n/es-ES/common.ts
+++ b/web/i18n/es-ES/common.ts
@@ -61,6 +61,10 @@ const translation = {
deSelectAll: 'Deseleccionar todo',
selectAll: 'Seleccionar todo',
config: 'Config',
+ confirmAction: 'Por favor, confirme su acción.',
+ deleteConfirmTitle: '¿Eliminar?',
+ yes: 'Sí',
+ no: 'No',
},
errorMsg: {
fieldRequired: '{{field}} es requerido',
diff --git a/web/i18n/es-ES/workflow.ts b/web/i18n/es-ES/workflow.ts
index 822b226e71..cc1a19bfbf 100644
--- a/web/i18n/es-ES/workflow.ts
+++ b/web/i18n/es-ES/workflow.ts
@@ -444,6 +444,7 @@ const translation = {
},
outputVars: {
output: 'Generar contenido',
+ reasoning_content: 'Contenido de razonamiento',
usage: 'Información de uso del modelo',
},
singleRun: {
diff --git a/web/i18n/fa-IR/billing.ts b/web/i18n/fa-IR/billing.ts
index 5634692dc2..3749036f3c 100644
--- a/web/i18n/fa-IR/billing.ts
+++ b/web/i18n/fa-IR/billing.ts
@@ -94,6 +94,8 @@ const translation = {
apiRateLimitTooltip: 'محدودیت نرخ API برای همه درخواستهای انجام شده از طریق API Dify اعمال میشود، از جمله تولید متن، محاورههای چت، اجرای گردشهای کار و پردازش اسناد.',
documentsRequestQuotaTooltip: 'تعیین میکند که تعداد کلی اقداماتی که یک فضای کاری میتواند در هر دقیقه در داخل پایگاه دانش انجام دهد، شامل ایجاد مجموعه داده، حذف، بهروزرسانی، بارگذاری مستندات، تغییرات، بایگانی و پرسش از پایگاه دانش است. این معیار برای ارزیابی عملکرد درخواستهای پایگاه دانش استفاده میشود. به عنوان مثال، اگر یک کاربر Sandbox در طی یک دقیقه 10 آزمایش متوالی انجام دهد، فضای کاری او به طور موقت از انجام اقدامات زیر در دقیقه بعدی محدود خواهد شد: ایجاد مجموعه داده، حذف، بهروزرسانی و بارگذاری یا تغییر مستندات.',
startBuilding: 'شروع به ساخت کنید',
+ taxTip: 'تمام قیمتهای اشتراک (ماهانه/سالانه) شامل مالیاتهای مربوطه (مثلاً مالیات بر ارزش افزوده، مالیات فروش) نمیشوند.',
+ taxTipSecond: 'اگر منطقه شما هیچ الزامات مالیاتی قابل اجرا نداشته باشد، هیچ مالیاتی در هنگام پرداخت نشان داده نمیشود و برای کل مدت اشتراک هیچ هزینه اضافی از شما دریافت نخواهد شد.',
},
plans: {
sandbox: {
diff --git a/web/i18n/fa-IR/common.ts b/web/i18n/fa-IR/common.ts
index dc6620ce2e..4d7482b143 100644
--- a/web/i18n/fa-IR/common.ts
+++ b/web/i18n/fa-IR/common.ts
@@ -61,6 +61,10 @@ const translation = {
selectAll: 'انتخاب همه',
deSelectAll: 'همه را انتخاب نکنید',
config: 'تنظیمات',
+ no: 'نه',
+ deleteConfirmTitle: 'حذف شود؟',
+ yes: 'بله',
+ confirmAction: 'لطفاً اقدام خود را تأیید کنید.',
},
errorMsg: {
fieldRequired: '{{field}} الزامی است',
diff --git a/web/i18n/fa-IR/workflow.ts b/web/i18n/fa-IR/workflow.ts
index d91e4498fe..c6e002a5f2 100644
--- a/web/i18n/fa-IR/workflow.ts
+++ b/web/i18n/fa-IR/workflow.ts
@@ -444,6 +444,7 @@ const translation = {
},
outputVars: {
output: 'تولید محتوا',
+ reasoning_content: 'محتوای استدلال',
usage: 'اطلاعات استفاده از مدل',
},
singleRun: {
diff --git a/web/i18n/fr-FR/billing.ts b/web/i18n/fr-FR/billing.ts
index 117d1c6654..a41eed7e23 100644
--- a/web/i18n/fr-FR/billing.ts
+++ b/web/i18n/fr-FR/billing.ts
@@ -94,6 +94,8 @@ const translation = {
documents: '{{count,number}} Documents de connaissance',
documentsRequestQuotaTooltip: 'Spécifie le nombre total d\'actions qu\'un espace de travail peut effectuer par minute dans la base de connaissances, y compris la création, la suppression, les mises à jour de jeux de données, le téléchargement de documents, les modifications, l\'archivage et les requêtes de la base de connaissances. Ce paramètre est utilisé pour évaluer les performances des requêtes de la base de connaissances. Par exemple, si un utilisateur de Sandbox effectue 10 tests de validité consécutifs en une minute, son espace de travail sera temporairement restreint dans l\'exécution des actions suivantes pendant la minute suivante : création, suppression, mises à jour de jeux de données, et téléchargements ou modifications de documents.',
startBuilding: 'Commencez à construire',
+ taxTip: 'Tous les prix des abonnements (mensuels/annuels) s\'entendent hors taxes applicables (par exemple, TVA, taxe de vente).',
+ taxTipSecond: 'Si votre région n\'a pas de exigences fiscales applicables, aucune taxe n\'apparaîtra lors de votre paiement et vous ne serez pas facturé de frais supplémentaires pendant toute la durée de l\'abonnement.',
},
plans: {
sandbox: {
diff --git a/web/i18n/fr-FR/common.ts b/web/i18n/fr-FR/common.ts
index f1e8ad007c..d2f1b6287a 100644
--- a/web/i18n/fr-FR/common.ts
+++ b/web/i18n/fr-FR/common.ts
@@ -61,6 +61,10 @@ const translation = {
deSelectAll: 'Désélectionner tout',
selectAll: 'Sélectionner tout',
config: 'Config',
+ no: 'Non',
+ confirmAction: 'Veuillez confirmer votre action.',
+ deleteConfirmTitle: 'Supprimer ?',
+ yes: 'Oui',
},
placeholder: {
input: 'Veuillez entrer',
diff --git a/web/i18n/fr-FR/workflow.ts b/web/i18n/fr-FR/workflow.ts
index 270cd1b7e6..f5d76e7579 100644
--- a/web/i18n/fr-FR/workflow.ts
+++ b/web/i18n/fr-FR/workflow.ts
@@ -444,6 +444,7 @@ const translation = {
},
outputVars: {
output: 'Contenu généré',
+ reasoning_content: 'Contenu de raisonnement',
usage: 'Informations sur l\'utilisation du modèle',
},
singleRun: {
diff --git a/web/i18n/hi-IN/billing.ts b/web/i18n/hi-IN/billing.ts
index 749ab804ab..fbc6dffc7c 100644
--- a/web/i18n/hi-IN/billing.ts
+++ b/web/i18n/hi-IN/billing.ts
@@ -102,6 +102,8 @@ const translation = {
teamMember_one: '{{count,number}} टीम सदस्य',
documentsRequestQuotaTooltip: 'यह ज्ञान आधार में एक कार्यक्षेत्र द्वारा प्रति मिनट किए जा सकने वाले कुल कार्यों की संख्या को निर्दिष्ट करता है, जिसमें डेटासेट बनाना, हटाना, अपडेट करना, दस्तावेज़ अपलोड करना, संशोधन करना, संग्रहित करना और ज्ञान आधार अनुरोध शामिल हैं। इस मीट्रिक का उपयोग ज्ञान आधार अनुरोधों के प्रदर्शन का मूल्यांकन करने के लिए किया जाता है। उदाहरण के लिए, यदि एक सैंडबॉक्स उपयोगकर्ता एक मिनट के भीतर 10 लगातार हिट परीक्षण करता है, तो उनके कार्यक्षेत्र को अगले मिनट के लिए निम्नलिखित कार्यों को करने से अस्थायी रूप से प्रतिबंधित किया जाएगा: डेटासेट बनाना, हटाना, अपडेट करना और दस्तावेज़ अपलोड या संशोधन करना।',
startBuilding: 'बनाना शुरू करें',
+ taxTip: 'सभी सदस्यता मूल्य (मासिक/वार्षिक) लागू करों (जैसे, VAT, बिक्री कर) को शामिल नहीं करते हैं।',
+ taxTipSecond: 'यदि आपके क्षेत्र में कोई लागू कर आवश्यकताएँ नहीं हैं, तो आपकी चेकआउट में कोई कर नहीं दिखाई देगा, और पूरे सदस्यता अवधि के लिए आपसे कोई अतिरिक्त शुल्क नहीं लिया जाएगा।',
},
plans: {
sandbox: {
diff --git a/web/i18n/hi-IN/common.ts b/web/i18n/hi-IN/common.ts
index d882b00929..acab7e9172 100644
--- a/web/i18n/hi-IN/common.ts
+++ b/web/i18n/hi-IN/common.ts
@@ -61,6 +61,10 @@ const translation = {
selectAll: 'सभी चुनें',
deSelectAll: 'सभी चयन हटाएँ',
config: 'कॉन्फ़िगरेशन',
+ no: 'नहीं',
+ yes: 'हाँ',
+ deleteConfirmTitle: 'हटाएं?',
+ confirmAction: 'कृपया अपनी क्रिया की पुष्टि करें।',
},
errorMsg: {
fieldRequired: '{{field}} आवश्यक है',
diff --git a/web/i18n/hi-IN/workflow.ts b/web/i18n/hi-IN/workflow.ts
index d94c9f102e..382a570a17 100644
--- a/web/i18n/hi-IN/workflow.ts
+++ b/web/i18n/hi-IN/workflow.ts
@@ -457,6 +457,7 @@ const translation = {
},
outputVars: {
output: 'सामग्री उत्पन्न करें',
+ reasoning_content: 'तर्क सामग्री',
usage: 'मॉडल उपयोग जानकारी',
},
singleRun: {
diff --git a/web/i18n/id-ID/billing.ts b/web/i18n/id-ID/billing.ts
index 11419c3b16..c6c718d15b 100644
--- a/web/i18n/id-ID/billing.ts
+++ b/web/i18n/id-ID/billing.ts
@@ -87,6 +87,8 @@ const translation = {
modelProviders: 'Mendukung OpenAI/Anthropic/Llama2/Azure OpenAI/Hugging Face/Replite',
member: 'Anggota',
startBuilding: 'Mulai Membangun',
+ taxTip: 'Semua harga langganan (bulanan/tahunan) belum termasuk pajak yang berlaku (misalnya, PPN, pajak penjualan).',
+ taxTipSecond: 'Jika wilayah Anda tidak memiliki persyaratan pajak yang berlaku, tidak akan ada pajak yang muncul saat checkout, dan Anda tidak akan dikenakan biaya tambahan apa pun selama masa langganan.',
},
plans: {
sandbox: {
diff --git a/web/i18n/id-ID/common.ts b/web/i18n/id-ID/common.ts
index b224f153f6..e57b9b3641 100644
--- a/web/i18n/id-ID/common.ts
+++ b/web/i18n/id-ID/common.ts
@@ -67,6 +67,10 @@ const translation = {
sure: 'Saya yakin',
imageCopied: 'Gambar yang disalin',
config: 'Konfigurasi',
+ deleteConfirmTitle: 'Hapus?',
+ confirmAction: 'Silakan konfirmasi tindakan Anda.',
+ yes: 'Ya',
+ no: 'Tidak',
},
errorMsg: {
urlError: 'URL harus dimulai dengan http:// atau https://',
diff --git a/web/i18n/id-ID/workflow.ts b/web/i18n/id-ID/workflow.ts
index 4bfbe934f7..969b7bb8b0 100644
--- a/web/i18n/id-ID/workflow.ts
+++ b/web/i18n/id-ID/workflow.ts
@@ -427,6 +427,7 @@ const translation = {
},
outputVars: {
output: 'Hasilkan konten',
+ reasoning_content: 'Konten penalaran',
usage: 'Informasi Penggunaan Model',
},
singleRun: {
diff --git a/web/i18n/it-IT/billing.ts b/web/i18n/it-IT/billing.ts
index f89502ee5b..ef6b1943e3 100644
--- a/web/i18n/it-IT/billing.ts
+++ b/web/i18n/it-IT/billing.ts
@@ -102,6 +102,8 @@ const translation = {
annualBilling: 'Fatturazione annuale',
documentsRequestQuotaTooltip: 'Specifica il numero totale di azioni che un\'area di lavoro può eseguire al minuto all\'interno della base di conoscenza, compresi la creazione, l\'eliminazione, gli aggiornamenti dei dataset, il caricamento di documenti, le modifiche, l\'archiviazione e le query sulla base di conoscenza. Questa metrica viene utilizzata per valutare le prestazioni delle richieste alla base di conoscenza. Ad esempio, se un utente di Sandbox esegue 10 test consecutivi in un minuto, la sua area di lavoro sarà temporaneamente limitata dall\'eseguire le seguenti azioni per il minuto successivo: creazione, eliminazione, aggiornamenti dei dataset e caricamento o modifica di documenti.',
startBuilding: 'Inizia a costruire',
+ taxTip: 'Tutti i prezzi degli abbonamenti (mensili/annuali) non includono le tasse applicabili (ad esempio, IVA, imposta sulle vendite).',
+ taxTipSecond: 'Se nella tua regione non ci sono requisiti fiscali applicabili, nessuna tassa apparirà al momento del pagamento e non ti verranno addebitate spese aggiuntive per l\'intera durata dell\'abbonamento.',
},
plans: {
sandbox: {
diff --git a/web/i18n/it-IT/common.ts b/web/i18n/it-IT/common.ts
index 4ba4f34240..14f705301a 100644
--- a/web/i18n/it-IT/common.ts
+++ b/web/i18n/it-IT/common.ts
@@ -61,6 +61,10 @@ const translation = {
selectAll: 'Seleziona tutto',
deSelectAll: 'Deseleziona tutto',
config: 'Config',
+ no: 'No',
+ yes: 'Sì',
+ confirmAction: 'Per favore conferma la tua azione.',
+ deleteConfirmTitle: 'Eliminare?',
},
errorMsg: {
fieldRequired: '{{field}} è obbligatorio',
diff --git a/web/i18n/it-IT/workflow.ts b/web/i18n/it-IT/workflow.ts
index 7322599abf..b032f1a59b 100644
--- a/web/i18n/it-IT/workflow.ts
+++ b/web/i18n/it-IT/workflow.ts
@@ -461,6 +461,7 @@ const translation = {
},
outputVars: {
output: 'Genera contenuto',
+ reasoning_content: 'Contenuto del ragionamento',
usage: 'Informazioni sull\'utilizzo del modello',
},
singleRun: {
diff --git a/web/i18n/ja-JP/billing.ts b/web/i18n/ja-JP/billing.ts
index 426687da6c..6dbff60d5a 100644
--- a/web/i18n/ja-JP/billing.ts
+++ b/web/i18n/ja-JP/billing.ts
@@ -36,6 +36,8 @@ const translation = {
save: '節約 ',
free: '無料',
annualBilling: '年次請求',
+ taxTip: 'すべてのサブスクリプション料金(月額/年額)は、適用される税金(例:消費税、付加価値税)を含みません。',
+ taxTipSecond: 'お客様の地域に適用税がない場合、チェックアウト時に税金は表示されず、サブスクリプション期間中に追加料金が請求されることもありません。',
comparePlanAndFeatures: 'プランと機能を比較する',
priceTip: 'ワークスペース/',
currentPlan: '現在のプラン',
diff --git a/web/i18n/ja-JP/common.ts b/web/i18n/ja-JP/common.ts
index 5526ac0441..52545c460b 100644
--- a/web/i18n/ja-JP/common.ts
+++ b/web/i18n/ja-JP/common.ts
@@ -67,6 +67,10 @@ const translation = {
selectAll: 'すべて選択',
deSelectAll: 'すべて選択解除',
config: 'コンフィグ',
+ yes: 'はい',
+ no: 'いいえ',
+ deleteConfirmTitle: '削除しますか?',
+ confirmAction: '操作を確認してください。',
},
errorMsg: {
fieldRequired: '{{field}}は必要です',
diff --git a/web/i18n/ja-JP/workflow.ts b/web/i18n/ja-JP/workflow.ts
index e85dcd305e..a4ffc95f68 100644
--- a/web/i18n/ja-JP/workflow.ts
+++ b/web/i18n/ja-JP/workflow.ts
@@ -456,6 +456,7 @@ const translation = {
},
outputVars: {
output: '生成内容',
+ reasoning_content: '推論内容',
usage: 'モデル使用量',
},
singleRun: {
diff --git a/web/i18n/ko-KR/billing.ts b/web/i18n/ko-KR/billing.ts
index ff0dd189e4..c5f081d41b 100644
--- a/web/i18n/ko-KR/billing.ts
+++ b/web/i18n/ko-KR/billing.ts
@@ -103,6 +103,8 @@ const translation = {
documentsRequestQuotaTooltip:
'지식 기반 내에서 작업 공간이 분당 수행할 수 있는 총 작업 수를 지정합니다. 여기에는 데이터 세트 생성, 삭제, 업데이트, 문서 업로드, 수정, 보관 및 지식 기반 쿼리가 포함됩니다. 이 지표는 지식 기반 요청의 성능을 평가하는 데 사용됩니다. 예를 들어, 샌드박스 사용자가 1 분 이내에 10 회의 연속 히트 테스트를 수행하면, 해당 작업 공간은 다음 1 분 동안 데이터 세트 생성, 삭제, 업데이트 및 문서 업로드 또는 수정과 같은 작업을 수행하는 것이 일시적으로 제한됩니다.',
startBuilding: '구축 시작',
+ taxTip: '모든 구독 요금(월간/연간)에는 해당 세금(예: 부가가치세, 판매세)이 포함되어 있지 않습니다.',
+ taxTipSecond: '귀하의 지역에 적용 가능한 세금 요구 사항이 없는 경우, 결제 시 세금이 표시되지 않으며 전체 구독 기간 동안 추가 요금이 부과되지 않습니다.',
},
plans: {
sandbox: {
diff --git a/web/i18n/ko-KR/common.ts b/web/i18n/ko-KR/common.ts
index 9d2948c594..617c0851c8 100644
--- a/web/i18n/ko-KR/common.ts
+++ b/web/i18n/ko-KR/common.ts
@@ -61,6 +61,10 @@ const translation = {
selectAll: '모두 선택',
deSelectAll: '모두 선택 해제',
config: '구성',
+ no: '아니요',
+ yes: '네',
+ deleteConfirmTitle: '삭제하시겠습니까?',
+ confirmAction: '귀하의 행동을 확인해 주세요.',
},
placeholder: {
input: '입력해주세요',
diff --git a/web/i18n/ko-KR/workflow.ts b/web/i18n/ko-KR/workflow.ts
index 7e3775c1f8..75c607a004 100644
--- a/web/i18n/ko-KR/workflow.ts
+++ b/web/i18n/ko-KR/workflow.ts
@@ -469,6 +469,7 @@ const translation = {
},
outputVars: {
output: '생성된 내용',
+ reasoning_content: '추론 내용',
usage: '모델 사용 정보',
},
singleRun: {
diff --git a/web/i18n/pl-PL/billing.ts b/web/i18n/pl-PL/billing.ts
index 3bf0867877..cf0859468b 100644
--- a/web/i18n/pl-PL/billing.ts
+++ b/web/i18n/pl-PL/billing.ts
@@ -101,6 +101,8 @@ const translation = {
documentsRequestQuota: '{{count,number}}/min Limit wiedzy na żądanie',
documentsRequestQuotaTooltip: 'Określa całkowitą liczbę działań, jakie przestrzeń robocza może wykonać na minutę w ramach bazy wiedzy, w tym tworzenie zbiorów danych, usuwanie, aktualizacje, przesyłanie dokumentów, modyfikacje, archiwizowanie i zapytania do bazy wiedzy. Ta metryka jest używana do oceny wydajności zapytań do bazy wiedzy. Na przykład, jeśli użytkownik Sandbox wykona 10 kolejnych testów w ciągu jednej minuty, jego przestrzeń robocza zostanie tymczasowo ograniczona w wykonywaniu następujących działań przez następną minutę: tworzenie zbiorów danych, usuwanie, aktualizacje oraz przesyłanie lub modyfikacje dokumentów.',
startBuilding: 'Zacznij budować',
+ taxTip: 'Wszystkie ceny subskrypcji (miesięczne/roczne) nie obejmują obowiązujących podatków (np. VAT, podatek od sprzedaży).',
+ taxTipSecond: 'Jeśli w Twoim regionie nie ma obowiązujących przepisów podatkowych, podatek nie pojawi się podczas realizacji zamówienia i nie zostaną naliczone żadne dodatkowe opłaty przez cały okres subskrypcji.',
},
plans: {
sandbox: {
diff --git a/web/i18n/pl-PL/common.ts b/web/i18n/pl-PL/common.ts
index 3f820e14e0..5fa05d3ce4 100644
--- a/web/i18n/pl-PL/common.ts
+++ b/web/i18n/pl-PL/common.ts
@@ -61,6 +61,10 @@ const translation = {
deSelectAll: 'Odznacz wszystkie',
selectAll: 'Zaznacz wszystkie',
config: 'Konfiguracja',
+ yes: 'Tak',
+ no: 'Nie',
+ deleteConfirmTitle: 'Usunąć?',
+ confirmAction: 'Proszę potwierdzić swoją akcję.',
},
placeholder: {
input: 'Proszę wprowadzić',
diff --git a/web/i18n/pl-PL/workflow.ts b/web/i18n/pl-PL/workflow.ts
index 87c96c758f..1d2a892941 100644
--- a/web/i18n/pl-PL/workflow.ts
+++ b/web/i18n/pl-PL/workflow.ts
@@ -444,6 +444,7 @@ const translation = {
},
outputVars: {
output: 'Generowana treść',
+ reasoning_content: 'Treść rozumowania',
usage: 'Informacje o użyciu modelu',
},
singleRun: {
diff --git a/web/i18n/pt-BR/billing.ts b/web/i18n/pt-BR/billing.ts
index 91ccaa7794..e4ca0a064a 100644
--- a/web/i18n/pt-BR/billing.ts
+++ b/web/i18n/pt-BR/billing.ts
@@ -94,6 +94,8 @@ const translation = {
apiRateLimitTooltip: 'O limite da taxa da API se aplica a todas as solicitações feitas através da API Dify, incluindo geração de texto, conversas de chat, execuções de fluxo de trabalho e processamento de documentos.',
documentsRequestQuotaTooltip: 'Especifica o número total de ações que um espaço de trabalho pode realizar por minuto dentro da base de conhecimento, incluindo criação, exclusão, atualizações de conjuntos de dados, uploads de documentos, modificações, arquivamento e consultas à base de conhecimento. Esse métrica é utilizada para avaliar o desempenho das solicitações à base de conhecimento. Por exemplo, se um usuário do Sandbox realizar 10 testes de impacto consecutivos dentro de um minuto, seu espaço de trabalho ficará temporariamente restrito de realizar as seguintes ações no minuto seguinte: criação, exclusão, atualizações de conjuntos de dados e uploads ou modificações de documentos.',
startBuilding: 'Comece a construir',
+ taxTip: 'Todos os preços de assinatura (mensal/anual) não incluem os impostos aplicáveis (por exemplo, IVA, imposto sobre vendas).',
+ taxTipSecond: 'Se a sua região não tiver requisitos fiscais aplicáveis, nenhum imposto aparecerá no seu checkout e você não será cobrado por taxas adicionais durante todo o período da assinatura.',
},
plans: {
sandbox: {
diff --git a/web/i18n/pt-BR/common.ts b/web/i18n/pt-BR/common.ts
index 3f5f353fb6..918bc24bf8 100644
--- a/web/i18n/pt-BR/common.ts
+++ b/web/i18n/pt-BR/common.ts
@@ -61,6 +61,10 @@ const translation = {
deSelectAll: 'Desmarcar tudo',
selectAll: 'Selecionar tudo',
config: 'Configuração',
+ no: 'Não',
+ yes: 'Sim',
+ deleteConfirmTitle: 'Excluir?',
+ confirmAction: 'Por favor, confirme sua ação.',
},
placeholder: {
input: 'Por favor, insira',
diff --git a/web/i18n/pt-BR/workflow.ts b/web/i18n/pt-BR/workflow.ts
index 9657ef8e7f..5610cacc13 100644
--- a/web/i18n/pt-BR/workflow.ts
+++ b/web/i18n/pt-BR/workflow.ts
@@ -444,6 +444,7 @@ const translation = {
},
outputVars: {
output: 'Conteúdo gerado',
+ reasoning_content: 'Conteúdo de raciocínio',
usage: 'Informações de uso do modelo',
},
singleRun: {
diff --git a/web/i18n/ro-RO/billing.ts b/web/i18n/ro-RO/billing.ts
index 550ff3e677..3f5577dc32 100644
--- a/web/i18n/ro-RO/billing.ts
+++ b/web/i18n/ro-RO/billing.ts
@@ -94,6 +94,8 @@ const translation = {
documentsRequestQuotaTooltip: 'Specificați numărul total de acțiuni pe care un spațiu de lucru le poate efectua pe minut în cadrul bazei de cunoștințe, inclusiv crearea, ștergerea, actualizările setului de date, încărcările de documente, modificările, arhivarea și interogările bazei de cunoștințe. Acest metric este utilizat pentru a evalua performanța cererilor din baza de cunoștințe. De exemplu, dacă un utilizator Sandbox efectuează 10 teste consecutive de hituri într-un minut, spațiul său de lucru va fi restricționat temporar de la efectuarea următoarelor acțiuni pentru minutul următor: crearea setului de date, ștergerea, actualizările și încărcările sau modificările documentelor.',
apiRateLimitTooltip: 'Limita de rată API se aplică tuturor cererilor efectuate prin API-ul Dify, inclusiv generarea de texte, conversațiile de chat, execuțiile fluxului de lucru și procesarea documentelor.',
startBuilding: 'Începeți să construiți',
+ taxTip: 'Toate prețurile abonamentelor (lunare/anuale) nu includ taxele aplicabile (de exemplu, TVA, taxa pe vânzări).',
+ taxTipSecond: 'Dacă regiunea dumneavoastră nu are cerințe fiscale aplicabile, niciun impozit nu va apărea la finalizarea comenzii și nu vi se vor percepe taxe suplimentare pe întreaga durată a abonamentului.',
},
plans: {
sandbox: {
diff --git a/web/i18n/ro-RO/common.ts b/web/i18n/ro-RO/common.ts
index 2e36e487fb..1a2f7f98c3 100644
--- a/web/i18n/ro-RO/common.ts
+++ b/web/i18n/ro-RO/common.ts
@@ -61,6 +61,10 @@ const translation = {
deSelectAll: 'Deselectați tot',
selectAll: 'Selectați tot',
config: 'Configurație',
+ yes: 'Da',
+ deleteConfirmTitle: 'Ștergere?',
+ no: 'Nu',
+ confirmAction: 'Vă rugăm să confirmați acțiunea dumneavoastră.',
},
placeholder: {
input: 'Vă rugăm să introduceți',
diff --git a/web/i18n/ro-RO/workflow.ts b/web/i18n/ro-RO/workflow.ts
index 94d01ec1ba..d2239e7979 100644
--- a/web/i18n/ro-RO/workflow.ts
+++ b/web/i18n/ro-RO/workflow.ts
@@ -444,6 +444,7 @@ const translation = {
},
outputVars: {
output: 'Conținut generat',
+ reasoning_content: 'Conținut de raționament',
usage: 'Informații de utilizare a modelului',
},
singleRun: {
diff --git a/web/i18n/ru-RU/billing.ts b/web/i18n/ru-RU/billing.ts
index 27f5c71685..7017f90cc2 100644
--- a/web/i18n/ru-RU/billing.ts
+++ b/web/i18n/ru-RU/billing.ts
@@ -94,6 +94,8 @@ const translation = {
priceTip: 'по рабочему месту/',
documentsTooltip: 'Квота на количество документов, импортируемых из источника знаний.',
startBuilding: 'Начать строительство',
+ taxTip: 'Все цены на подписку (ежемесячную/годовую) не включают применимые налоги (например, НДС, налог с продаж).',
+ taxTipSecond: 'Если в вашем регионе нет применимых налоговых требований, налоги не будут отображаться при оформлении заказа, и с вас не будут взиматься дополнительные сборы за весь срок подписки.',
},
plans: {
sandbox: {
diff --git a/web/i18n/ru-RU/common.ts b/web/i18n/ru-RU/common.ts
index 8f1fb3a51b..de5ff494a6 100644
--- a/web/i18n/ru-RU/common.ts
+++ b/web/i18n/ru-RU/common.ts
@@ -61,6 +61,10 @@ const translation = {
selectAll: 'Выбрать все',
deSelectAll: 'Снять выделение со всех',
config: 'Конфигурация',
+ yes: 'Да',
+ no: 'Нет',
+ deleteConfirmTitle: 'Удалить?',
+ confirmAction: 'Пожалуйста, подтвердите ваше действие.',
},
errorMsg: {
fieldRequired: '{{field}} обязательно',
diff --git a/web/i18n/ru-RU/workflow.ts b/web/i18n/ru-RU/workflow.ts
index 1e0ecf1276..2345f3447b 100644
--- a/web/i18n/ru-RU/workflow.ts
+++ b/web/i18n/ru-RU/workflow.ts
@@ -444,6 +444,7 @@ const translation = {
},
outputVars: {
output: 'Создать контент',
+ reasoning_content: 'Содержимое рассуждений',
usage: 'Информация об использовании модели',
},
singleRun: {
diff --git a/web/i18n/sl-SI/billing.ts b/web/i18n/sl-SI/billing.ts
index 4481100dd8..fb9d9ec435 100644
--- a/web/i18n/sl-SI/billing.ts
+++ b/web/i18n/sl-SI/billing.ts
@@ -94,6 +94,8 @@ const translation = {
getStarted: 'Začnite',
documentsRequestQuotaTooltip: 'Določa skupno število dejanj, ki jih lahko delovno mesto opravi na minuto znotraj znanja baze, vključno s kreiranjem, brisanjem, posodobitvami, nalaganjem dokumentov, spremembami, arhiviranjem in poizvedbami po znanju bazi. Ta meritev se uporablja za ocenjevanje uspešnosti poizvedb v bazi znanja. Na primer, če uporabnik Sandbox izvede 10 zaporednih testov udarca v eni minuti, bo njegovo delovno mesto začasno omejeno pri izvajanju naslednjih dejanj v naslednji minuti: kreiranje podatkovnih nizov, brisanje, posodobitve in nalaganje ali spremembe dokumentov.',
startBuilding: 'Začnite graditi',
+ taxTip: 'Vse cene naročnin (mesečne/letne) ne vključujejo veljavnih davkov (npr. DDV, davek na promet).',
+ taxTipSecond: 'Če vaša regija nima veljavnih davčnih zahtev, se v vaši košarici ne bo prikazal noben davek in za celotno obdobje naročnine vam ne bodo zaračunani nobeni dodatni stroški.',
},
plans: {
sandbox: {
diff --git a/web/i18n/sl-SI/common.ts b/web/i18n/sl-SI/common.ts
index 2efd6f8de6..169d0997f6 100644
--- a/web/i18n/sl-SI/common.ts
+++ b/web/i18n/sl-SI/common.ts
@@ -61,6 +61,10 @@ const translation = {
selectAll: 'Izberi vse',
deSelectAll: 'Odberi vse',
config: 'Konfiguracija',
+ no: 'Ne',
+ confirmAction: 'Prosimo, potrdite svoje dejanje.',
+ deleteConfirmTitle: 'Izbrisati?',
+ yes: 'Da',
},
errorMsg: {
fieldRequired: '{{field}} je obvezno',
diff --git a/web/i18n/sl-SI/workflow.ts b/web/i18n/sl-SI/workflow.ts
index baeff90ee6..7a167c236f 100644
--- a/web/i18n/sl-SI/workflow.ts
+++ b/web/i18n/sl-SI/workflow.ts
@@ -442,6 +442,7 @@ const translation = {
},
outputVars: {
output: 'Ustvari vsebino',
+ reasoning_content: 'Vsebina razmišljanja',
usage: 'Informacije o uporabi modela',
},
singleRun: {
diff --git a/web/i18n/th-TH/billing.ts b/web/i18n/th-TH/billing.ts
index 55a01449eb..461e4a8240 100644
--- a/web/i18n/th-TH/billing.ts
+++ b/web/i18n/th-TH/billing.ts
@@ -94,6 +94,8 @@ const translation = {
annualBilling: 'การเรียกเก็บเงินประจำปี',
documentsRequestQuotaTooltip: 'ระบุจำนวนรวมของการกระทำที่เวิร์กสเปซสามารถดำเนินการต่อหนึ่งนาทีภายในฐานความรู้ รวมถึงการสร้างชุดข้อมูล การลบ การอัปเดต การอัปโหลดเอกสาร การปรับเปลี่ยน การเก็บถาวร และการสอบถามฐานความรู้ เมตริกนี้ถูกใช้ในการประเมินประสิทธิภาพของคำขอฐานความรู้ ตัวอย่างเช่น หากผู้ใช้ Sandbox ทำการทดสอบการตี 10 ครั้งต่อเนื่องภายในหนึ่งนาที เวิร์กสเปซของพวกเขาจะถูกจำกัดชั่วคราวในการดำเนินการต่อไปนี้ในนาทีถัดไป: การสร้างชุดข้อมูล การลบ การอัปเดต หรือการอัปโหลดหรือปรับเปลี่ยนเอกสาร.',
startBuilding: 'เริ่มสร้าง',
+ taxTip: 'ราคาการสมัครสมาชิกทั้งหมด (รายเดือน/รายปี) ไม่รวมภาษีที่ใช้บังคับ (เช่น ภาษีมูลค่าเพิ่ม, ภาษีการขาย)',
+ taxTipSecond: 'หากภูมิภาคของคุณไม่มีข้อกำหนดเกี่ยวกับภาษีที่ใช้ได้ จะไม่มีการคิดภาษีในขั้นตอนการชำระเงินของคุณ และคุณจะไม่ถูกเรียกเก็บค่าธรรมเนียมเพิ่มเติมใด ๆ ตลอดระยะเวลาสมาชิกทั้งหมด',
},
plans: {
sandbox: {
diff --git a/web/i18n/th-TH/common.ts b/web/i18n/th-TH/common.ts
index a673629d3e..4149843371 100644
--- a/web/i18n/th-TH/common.ts
+++ b/web/i18n/th-TH/common.ts
@@ -61,6 +61,10 @@ const translation = {
selectAll: 'เลือกทั้งหมด',
deSelectAll: 'ยกเลิกการเลือกทั้งหมด',
config: 'การตั้งค่า',
+ no: 'ไม่',
+ deleteConfirmTitle: 'ลบหรือไม่?',
+ confirmAction: 'กรุณายืนยันการกระทำของคุณ',
+ yes: 'ใช่',
},
errorMsg: {
fieldRequired: '{{field}} เป็นสิ่งจําเป็น',
diff --git a/web/i18n/th-TH/workflow.ts b/web/i18n/th-TH/workflow.ts
index e2db4ceb4a..1cea01690a 100644
--- a/web/i18n/th-TH/workflow.ts
+++ b/web/i18n/th-TH/workflow.ts
@@ -444,6 +444,7 @@ const translation = {
},
outputVars: {
output: 'สร้างเนื้อหา',
+ reasoning_content: 'เนื้อหาการให้เหตุผล',
usage: 'ข้อมูลการใช้งานรุ่น',
},
singleRun: {
diff --git a/web/i18n/tr-TR/billing.ts b/web/i18n/tr-TR/billing.ts
index 62d6e0a07e..6d01d9dd32 100644
--- a/web/i18n/tr-TR/billing.ts
+++ b/web/i18n/tr-TR/billing.ts
@@ -94,6 +94,8 @@ const translation = {
teamWorkspace: '{{count,number}} Takım Çalışma Alanı',
documentsRequestQuotaTooltip: 'Bir çalışma alanının bilgi tabanında, veri seti oluşturma, silme, güncellemeler, belge yüklemeleri, değişiklikler, arşivleme ve bilgi tabanı sorguları dahil olmak üzere, dakikada gerçekleştirebileceği toplam işlem sayısını belirtir. Bu ölçüt, bilgi tabanı taleplerinin performansını değerlendirmek için kullanılır. Örneğin, bir Sandbox kullanıcısı bir dakika içinde ardışık 10 vurma testi gerçekleştirirse, çalışma alanı bir sonraki dakika için aşağıdaki işlemleri gerçekleştirmesi geçici olarak kısıtlanacaktır: veri seti oluşturma, silme, güncellemeler ve belge yüklemeleri veya değişiklikler.',
startBuilding: 'İnşa Etmeye Başlayın',
+ taxTip: 'Tüm abonelik fiyatları (aylık/yıllık) geçerli vergiler (ör. KDV, satış vergisi) hariçtir.',
+ taxTipSecond: 'Bölgenizde geçerli vergi gereksinimleri yoksa, ödeme sayfanızda herhangi bir vergi görünmeyecek ve tüm abonelik süresi boyunca ek bir ücret tahsil edilmeyecektir.',
},
plans: {
sandbox: {
diff --git a/web/i18n/tr-TR/common.ts b/web/i18n/tr-TR/common.ts
index b198bd5d63..14b4689419 100644
--- a/web/i18n/tr-TR/common.ts
+++ b/web/i18n/tr-TR/common.ts
@@ -61,6 +61,10 @@ const translation = {
selectAll: 'Hepsini Seç',
deSelectAll: 'Hepsini Seçme',
config: 'Konfigürasyon',
+ no: 'Hayır',
+ yes: 'Evet',
+ deleteConfirmTitle: 'Silinsin mi?',
+ confirmAction: 'Lütfen işleminizi onaylayın.',
},
errorMsg: {
fieldRequired: '{{field}} gereklidir',
diff --git a/web/i18n/tr-TR/workflow.ts b/web/i18n/tr-TR/workflow.ts
index 68f3d5c0c2..dfab5c2c0c 100644
--- a/web/i18n/tr-TR/workflow.ts
+++ b/web/i18n/tr-TR/workflow.ts
@@ -444,6 +444,7 @@ const translation = {
},
outputVars: {
output: 'İçerik Üret',
+ reasoning_content: 'Akıl yürütme içeriği',
usage: 'Model Kullanım Bilgileri',
},
singleRun: {
diff --git a/web/i18n/uk-UA/billing.ts b/web/i18n/uk-UA/billing.ts
index 10dafedb24..03b743e4fe 100644
--- a/web/i18n/uk-UA/billing.ts
+++ b/web/i18n/uk-UA/billing.ts
@@ -94,6 +94,8 @@ const translation = {
apiRateLimitTooltip: 'Обмеження частоти запитів застосовується до всіх запитів, зроблених через API Dify, включаючи генерацію тексту, чат-розмови, виконання робочих процесів та обробку документів.',
documentsRequestQuotaTooltip: 'Вказує загальну кількість дій, які робоча область може виконувати за хвилину в межах бази знань, включаючи створення, видалення, оновлення наборів даних, завантаження документів, модифікації, архівування та запити до бази знань. Цей показник використовується для оцінки ефективності запитів до бази знань. Наприклад, якщо користувач Sandbox виконує 10 послідовних тестів за один хвилину, його робочій області буде тимчасово заборонено виконувати наступні дії протягом наступної хвилини: створення наборів даних, видалення, оновлення, а також завантаження чи модифікацію документів.',
startBuilding: 'Почніть будувати',
+ taxTip: 'Всі ціни на підписку (щомісячна/щорічна) не включають відповідні податки (наприклад, ПДВ, податок з продажу).',
+ taxTipSecond: 'Якщо для вашого регіону немає відповідних податкових вимог, податок не відображатиметься на вашому чек-ауті, і з вас не стягуватимуть додаткові збори протягом усього терміну підписки.',
},
plans: {
sandbox: {
diff --git a/web/i18n/uk-UA/common.ts b/web/i18n/uk-UA/common.ts
index 69af3cc2db..ef9bccd7ff 100644
--- a/web/i18n/uk-UA/common.ts
+++ b/web/i18n/uk-UA/common.ts
@@ -61,6 +61,10 @@ const translation = {
deSelectAll: 'Вимкнути все',
selectAll: 'Вибрати все',
config: 'Конфігурація',
+ yes: 'Так',
+ no: 'Ні',
+ deleteConfirmTitle: 'Видалити?',
+ confirmAction: 'Будь ласка, підтвердіть свої дії.',
},
placeholder: {
input: 'Будь ласка, введіть текст',
diff --git a/web/i18n/uk-UA/workflow.ts b/web/i18n/uk-UA/workflow.ts
index 56715c5e37..09f2b71eea 100644
--- a/web/i18n/uk-UA/workflow.ts
+++ b/web/i18n/uk-UA/workflow.ts
@@ -444,6 +444,7 @@ const translation = {
},
outputVars: {
output: 'Генерований вміст',
+ reasoning_content: 'Зміст міркування',
usage: 'Інформація про використання моделі',
},
singleRun: {
diff --git a/web/i18n/vi-VN/billing.ts b/web/i18n/vi-VN/billing.ts
index 68e662425f..0166185e45 100644
--- a/web/i18n/vi-VN/billing.ts
+++ b/web/i18n/vi-VN/billing.ts
@@ -94,6 +94,8 @@ const translation = {
freeTrialTipSuffix: 'Không cần thẻ tín dụng',
documentsRequestQuotaTooltip: 'Chỉ định tổng số hành động mà một không gian làm việc có thể thực hiện mỗi phút trong cơ sở tri thức, bao gồm tạo mới tập dữ liệu, xóa, cập nhật, tải tài liệu lên, thay đổi, lưu trữ và truy vấn cơ sở tri thức. Chỉ số này được sử dụng để đánh giá hiệu suất của các yêu cầu cơ sở tri thức. Ví dụ, nếu một người dùng Sandbox thực hiện 10 lần kiểm tra liên tiếp trong một phút, không gian làm việc của họ sẽ bị hạn chế tạm thời không thực hiện các hành động sau trong phút tiếp theo: tạo mới tập dữ liệu, xóa, cập nhật và tải tài liệu lên hoặc thay đổi.',
startBuilding: 'Bắt đầu xây dựng',
+ taxTipSecond: 'Nếu khu vực của bạn không có yêu cầu thuế áp dụng, sẽ không có thuế xuất hiện trong quá trình thanh toán của bạn và bạn sẽ không bị tính bất kỳ khoản phí bổ sung nào trong suốt thời gian đăng ký.',
+ taxTip: 'Tất cả giá đăng ký (hàng tháng/hàng năm) chưa bao gồm các loại thuế áp dụng (ví dụ: VAT, thuế bán hàng).',
},
plans: {
sandbox: {
diff --git a/web/i18n/vi-VN/common.ts b/web/i18n/vi-VN/common.ts
index 216a2e2ed2..e9e1f17f56 100644
--- a/web/i18n/vi-VN/common.ts
+++ b/web/i18n/vi-VN/common.ts
@@ -61,6 +61,10 @@ const translation = {
deSelectAll: 'Bỏ chọn tất cả',
selectAll: 'Chọn Tất Cả',
config: 'Cấu hình',
+ no: 'Không',
+ yes: 'Vâng',
+ deleteConfirmTitle: 'Xóa?',
+ confirmAction: 'Vui lòng xác nhận hành động của bạn.',
},
placeholder: {
input: 'Vui lòng nhập',
diff --git a/web/i18n/vi-VN/workflow.ts b/web/i18n/vi-VN/workflow.ts
index 3016d79a23..27d19a37f4 100644
--- a/web/i18n/vi-VN/workflow.ts
+++ b/web/i18n/vi-VN/workflow.ts
@@ -444,6 +444,7 @@ const translation = {
},
outputVars: {
output: 'Nội dung được tạo',
+ reasoning_content: 'Nội dung lập luận',
usage: 'Thông tin sử dụng mô hình',
},
singleRun: {
diff --git a/web/i18n/zh-Hans/billing.ts b/web/i18n/zh-Hans/billing.ts
index 96ba7970c8..00a7dd909a 100644
--- a/web/i18n/zh-Hans/billing.ts
+++ b/web/i18n/zh-Hans/billing.ts
@@ -36,6 +36,8 @@ const translation = {
save: '节省',
free: '免费',
annualBilling: '按年计费节省 {{percent}}%',
+ taxTip: '所有订阅价格(按月/按年)均不含适用税费(如增值税、销售税)。',
+ taxTipSecond: '如果您所在地区无适用税费要求,结账时将不会显示税费,且在整个订阅周期内您都无需支付任何额外费用。',
comparePlanAndFeatures: '对比套餐 & 功能特性',
priceTip: '每个团队空间/',
currentPlan: '当前计划',
diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts
index 0ecdb20d5e..e73ac4cc6b 100644
--- a/web/i18n/zh-Hans/common.ts
+++ b/web/i18n/zh-Hans/common.ts
@@ -18,6 +18,10 @@ const translation = {
cancel: '取消',
clear: '清空',
save: '保存',
+ yes: '是',
+ no: '否',
+ deleteConfirmTitle: '删除?',
+ confirmAction: '请确认您的操作。',
saveAndEnable: '保存并启用',
edit: '编辑',
add: '添加',
diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts
index 77b7fe3597..a497bca56a 100644
--- a/web/i18n/zh-Hans/workflow.ts
+++ b/web/i18n/zh-Hans/workflow.ts
@@ -456,6 +456,7 @@ const translation = {
},
outputVars: {
output: '生成内容',
+ reasoning_content: '推理内容',
usage: '模型用量信息',
},
singleRun: {
diff --git a/web/i18n/zh-Hant/billing.ts b/web/i18n/zh-Hant/billing.ts
index f99b1ef2cf..1b0b1f5e1f 100644
--- a/web/i18n/zh-Hant/billing.ts
+++ b/web/i18n/zh-Hant/billing.ts
@@ -94,6 +94,8 @@ const translation = {
documentsTooltip: '從知識數據來源導入的文件數量配額。',
documentsRequestQuotaTooltip: '指定工作區在知識基礎中每分鐘可以執行的總操作次數,包括數據集的創建、刪除、更新、文檔上傳、修改、歸檔和知識基礎查詢。這個指標用於評估知識基礎請求的性能。例如,如果一個沙箱用戶在一分鐘內連續執行 10 次命中測試,他們的工作區將在接下來的一分鐘內暫時禁止執行以下操作:數據集的創建、刪除、更新以及文檔上傳或修改。',
startBuilding: '開始建造',
+ taxTip: '所有訂閱價格(月費/年費)不包含適用的稅費(例如增值稅、銷售稅)。',
+ taxTipSecond: '如果您的地區沒有適用的稅務要求,結帳時將不會顯示任何稅款,且在整個訂閱期間您也不會被收取任何額外費用。',
},
plans: {
sandbox: {
diff --git a/web/i18n/zh-Hant/common.ts b/web/i18n/zh-Hant/common.ts
index a5747ba300..273ecb010f 100644
--- a/web/i18n/zh-Hant/common.ts
+++ b/web/i18n/zh-Hant/common.ts
@@ -61,6 +61,10 @@ const translation = {
deSelectAll: '全不選',
selectAll: '全選',
config: '配置',
+ yes: '是',
+ confirmAction: '請確認您的操作。',
+ deleteConfirmTitle: '刪除?',
+ no: '不',
},
placeholder: {
input: '請輸入',
diff --git a/web/i18n/zh-Hant/workflow.ts b/web/i18n/zh-Hant/workflow.ts
index 809051c2be..6acace8e4a 100644
--- a/web/i18n/zh-Hant/workflow.ts
+++ b/web/i18n/zh-Hant/workflow.ts
@@ -444,6 +444,7 @@ const translation = {
},
outputVars: {
output: '生成內容',
+ reasoning_content: '推理內容',
usage: '模型用量信息',
},
singleRun: {
diff --git a/web/jest.config.ts b/web/jest.config.ts
index ebeb2f7d7e..6c2d88448c 100644
--- a/web/jest.config.ts
+++ b/web/jest.config.ts
@@ -160,7 +160,11 @@ const config: Config = {
testEnvironment: '@happy-dom/jest-environment',
// Options that will be passed to the testEnvironment
- // testEnvironmentOptions: {},
+ testEnvironmentOptions: {
+ // Match happy-dom's default to ensure Node.js environment resolution
+ // This prevents ESM packages like uuid from using browser exports
+ customExportConditions: ['node', 'node-addons'],
+ },
// Adds a location field to test results
// testLocationInResults: false,
@@ -189,10 +193,10 @@ const config: Config = {
// transform: undefined,
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
- // transformIgnorePatterns: [
- // "/node_modules/",
- // "\\.pnp\\.[^\\/]+$"
- // ],
+ // For pnpm: allow transforming uuid ESM package
+ transformIgnorePatterns: [
+ 'node_modules/(?!(.pnpm|uuid))',
+ ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
diff --git a/web/package.json b/web/package.json
index 4433d866d8..366dc99e6f 100644
--- a/web/package.json
+++ b/web/package.json
@@ -2,7 +2,7 @@
"name": "dify-web",
"version": "1.9.1",
"private": true,
- "packageManager": "pnpm@10.17.1",
+ "packageManager": "pnpm@10.18.2",
"engines": {
"node": ">=v22.11.0"
},
@@ -43,20 +43,19 @@
"knip": "knip"
},
"dependencies": {
- "@dagrejs/dagre": "^1.1.4",
"@emoji-mart/data": "^1.2.1",
"@floating-ui/react": "^0.26.25",
"@formatjs/intl-localematcher": "^0.5.6",
"@headlessui/react": "2.2.1",
"@heroicons/react": "^2.0.16",
"@hookform/resolvers": "^3.9.0",
- "@lexical/code": "^0.30.0",
- "@lexical/link": "^0.30.0",
- "@lexical/list": "^0.30.0",
- "@lexical/react": "^0.30.0",
- "@lexical/selection": "^0.30.0",
- "@lexical/text": "^0.35.0",
- "@lexical/utils": "^0.30.0",
+ "@lexical/code": "^0.36.2",
+ "@lexical/link": "^0.36.2",
+ "@lexical/list": "^0.36.2",
+ "@lexical/react": "^0.36.2",
+ "@lexical/selection": "^0.36.2",
+ "@lexical/text": "^0.36.2",
+ "@lexical/utils": "^0.37.0",
"@monaco-editor/react": "^4.6.0",
"@octokit/core": "^6.1.2",
"@octokit/request-error": "^6.1.5",
@@ -91,14 +90,14 @@
"katex": "^0.16.21",
"ky": "^1.7.2",
"lamejs": "^1.2.1",
- "lexical": "^0.30.0",
+ "lexical": "^0.36.2",
"line-clamp": "^1.0.0",
"lodash-es": "^4.17.21",
"mermaid": "11.10.0",
"mime": "^4.0.4",
"mitt": "^3.0.1",
"negotiator": "^1.0.0",
- "next": "15.5.0",
+ "next": "15.5.4",
"next-pwa": "^5.6.0",
"next-themes": "^0.4.3",
"pinyin-pro": "^3.25.0",
@@ -107,7 +106,7 @@
"react": "19.1.1",
"react-18-input-autosize": "^3.0.0",
"react-dom": "19.1.1",
- "react-easy-crop": "^5.1.0",
+ "react-easy-crop": "^5.5.3",
"react-hook-form": "^7.53.1",
"react-hotkeys-hook": "^4.6.1",
"react-i18next": "^15.1.0",
@@ -141,14 +140,15 @@
},
"devDependencies": {
"@antfu/eslint-config": "^5.0.0",
+ "@babel/core": "^7.28.3",
"@chromatic-com/storybook": "^3.1.0",
"@eslint-react/eslint-plugin": "^1.15.0",
- "@happy-dom/jest-environment": "^17.4.4",
+ "@happy-dom/jest-environment": "^20.0.0",
"@mdx-js/loader": "^3.1.0",
"@mdx-js/react": "^3.1.0",
- "@next/bundle-analyzer": "15.5.3",
- "@next/eslint-plugin-next": "15.5.0",
- "@next/mdx": "15.5.0",
+ "@next/bundle-analyzer": "15.5.4",
+ "@next/eslint-plugin-next": "15.5.4",
+ "@next/mdx": "15.5.4",
"@rgrove/parse-xml": "^4.1.0",
"@storybook/addon-essentials": "8.5.0",
"@storybook/addon-interactions": "8.5.0",
@@ -161,8 +161,6 @@
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.0.1",
- "@babel/core": "^7.28.3",
- "@types/dagre": "^0.7.52",
"@types/jest": "^29.5.13",
"@types/js-cookie": "^3.0.6",
"@types/lodash-es": "^4.17.12",
@@ -178,6 +176,7 @@
"@types/sortablejs": "^1.15.1",
"@types/uuid": "^10.0.0",
"autoprefixer": "^10.4.20",
+ "babel-loader": "^10.0.0",
"bing-translate-api": "^4.0.2",
"code-inspector-plugin": "1.2.9",
"cross-env": "^7.0.3",
@@ -191,7 +190,7 @@
"globals": "^15.11.0",
"husky": "^9.1.6",
"jest": "^29.7.0",
- "knip": "^5.64.1",
+ "knip": "^5.64.3",
"lint-staged": "^15.2.10",
"lodash": "^4.17.21",
"magicast": "^0.3.4",
@@ -200,8 +199,7 @@
"storybook": "8.5.0",
"tailwindcss": "^3.4.14",
"typescript": "^5.8.3",
- "uglify-js": "^3.19.3",
- "babel-loader": "^9.2.1"
+ "uglify-js": "^3.19.3"
},
"resolutions": {
"@types/react": "19.1.11",
diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml
index 8c2e869c91..28758f1142 100644
--- a/web/pnpm-lock.yaml
+++ b/web/pnpm-lock.yaml
@@ -49,9 +49,6 @@ importers:
.:
dependencies:
- '@dagrejs/dagre':
- specifier: ^1.1.4
- version: 1.1.5
'@emoji-mart/data':
specifier: ^1.2.1
version: 1.2.1
@@ -71,26 +68,26 @@ importers:
specifier: ^3.9.0
version: 3.10.0(react-hook-form@7.60.0(react@19.1.1))
'@lexical/code':
- specifier: ^0.30.0
- version: 0.30.0
+ specifier: ^0.36.2
+ version: 0.36.2
'@lexical/link':
- specifier: ^0.30.0
- version: 0.30.0
+ specifier: ^0.36.2
+ version: 0.36.2
'@lexical/list':
- specifier: ^0.30.0
- version: 0.30.0
+ specifier: ^0.36.2
+ version: 0.36.2
'@lexical/react':
- specifier: ^0.30.0
- version: 0.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(yjs@13.6.27)
+ specifier: ^0.36.2
+ version: 0.36.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(yjs@13.6.27)
'@lexical/selection':
- specifier: ^0.30.0
- version: 0.30.0
+ specifier: ^0.36.2
+ version: 0.36.2
'@lexical/text':
- specifier: ^0.35.0
- version: 0.35.0
+ specifier: ^0.36.2
+ version: 0.36.2
'@lexical/utils':
- specifier: ^0.30.0
- version: 0.30.0
+ specifier: ^0.37.0
+ version: 0.37.0
'@monaco-editor/react':
specifier: ^4.6.0
version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -194,8 +191,8 @@ importers:
specifier: ^1.2.1
version: 1.2.1
lexical:
- specifier: ^0.30.0
- version: 0.30.0
+ specifier: ^0.36.2
+ version: 0.36.2
line-clamp:
specifier: ^1.0.0
version: 1.0.0
@@ -215,11 +212,11 @@ importers:
specifier: ^1.0.0
version: 1.0.0
next:
- specifier: 15.5.0
- version: 15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1)
+ specifier: 15.5.4
+ version: 15.5.4(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1)
next-pwa:
specifier: ^5.6.0
- version: 5.6.0(@babel/core@7.28.3)(@types/babel__core@7.20.5)(esbuild@0.25.0)(next@15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1))(uglify-js@3.19.3)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
+ version: 5.6.0(@babel/core@7.28.3)(@types/babel__core@7.20.5)(esbuild@0.25.0)(next@15.5.4(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1))(uglify-js@3.19.3)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
next-themes:
specifier: ^0.4.3
version: 0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -242,8 +239,8 @@ importers:
specifier: 19.1.1
version: 19.1.1(react@19.1.1)
react-easy-crop:
- specifier: ^5.1.0
- version: 5.5.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ specifier: ^5.5.3
+ version: 5.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react-hook-form:
specifier: ^7.53.1
version: 7.60.0(react@19.1.1)
@@ -337,7 +334,7 @@ importers:
devDependencies:
'@antfu/eslint-config':
specifier: ^5.0.0
- version: 5.0.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.35.0(jiti@2.6.0))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@next/eslint-plugin-next@15.5.0)(@vue/compiler-sfc@3.5.17)(eslint-plugin-react-hooks@5.2.0(eslint@9.35.0(jiti@2.6.0)))(eslint-plugin-react-refresh@0.4.20(eslint@9.35.0(jiti@2.6.0)))(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ version: 5.0.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.35.0(jiti@2.6.1))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@next/eslint-plugin-next@15.5.4)(@vue/compiler-sfc@3.5.17)(eslint-plugin-react-hooks@5.2.0(eslint@9.35.0(jiti@2.6.1)))(eslint-plugin-react-refresh@0.4.20(eslint@9.35.0(jiti@2.6.1)))(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@babel/core':
specifier: ^7.28.3
version: 7.28.3
@@ -346,10 +343,10 @@ importers:
version: 3.2.7(react@19.1.1)(storybook@8.5.0)
'@eslint-react/eslint-plugin':
specifier: ^1.15.0
- version: 1.52.3(eslint@9.35.0(jiti@2.6.0))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3)
+ version: 1.52.3(eslint@9.35.0(jiti@2.6.1))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3)
'@happy-dom/jest-environment':
- specifier: ^17.4.4
- version: 17.6.3
+ specifier: ^20.0.0
+ version: 20.0.0(@jest/environment@29.7.0)(@jest/fake-timers@29.7.0)(@jest/types@29.6.3)(jest-mock@29.7.0)(jest-util@29.7.0)
'@mdx-js/loader':
specifier: ^3.1.0
version: 3.1.0(acorn@8.15.0)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
@@ -357,14 +354,14 @@ importers:
specifier: ^3.1.0
version: 3.1.0(@types/react@19.1.11)(react@19.1.1)
'@next/bundle-analyzer':
- specifier: 15.5.3
- version: 15.5.3
+ specifier: 15.5.4
+ version: 15.5.4
'@next/eslint-plugin-next':
- specifier: 15.5.0
- version: 15.5.0
+ specifier: 15.5.4
+ version: 15.5.4
'@next/mdx':
- specifier: 15.5.0
- version: 15.5.0(@mdx-js/loader@3.1.0(acorn@8.15.0)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3)))(@mdx-js/react@3.1.0(@types/react@19.1.11)(react@19.1.1))
+ specifier: 15.5.4
+ version: 15.5.4(@mdx-js/loader@3.1.0(acorn@8.15.0)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3)))(@mdx-js/react@3.1.0(@types/react@19.1.11)(react@19.1.1))
'@rgrove/parse-xml':
specifier: ^4.1.0
version: 4.2.0
@@ -385,7 +382,7 @@ importers:
version: 8.5.0(storybook@8.5.0)
'@storybook/nextjs':
specifier: 8.5.0
- version: 8.5.0(esbuild@0.25.0)(next@15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1)(storybook@8.5.0)(type-fest@2.19.0)(typescript@5.8.3)(uglify-js@3.19.3)(webpack-hot-middleware@2.26.1)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
+ version: 8.5.0(esbuild@0.25.0)(next@15.5.4(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1)(storybook@8.5.0)(type-fest@2.19.0)(typescript@5.8.3)(uglify-js@3.19.3)(webpack-hot-middleware@2.26.1)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
'@storybook/react':
specifier: 8.5.0
version: 8.5.0(@storybook/test@8.5.0(storybook@8.5.0))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@8.5.0)(typescript@5.8.3)
@@ -401,9 +398,6 @@ importers:
'@testing-library/react':
specifier: ^16.0.1
version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.7(@types/react@19.1.11))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- '@types/dagre':
- specifier: ^0.7.52
- version: 0.7.53
'@types/jest':
specifier: ^29.5.13
version: 29.5.14
@@ -450,8 +444,8 @@ importers:
specifier: ^10.4.20
version: 10.4.21(postcss@8.5.6)
babel-loader:
- specifier: ^9.2.1
- version: 9.2.1(@babel/core@7.28.3)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
+ specifier: ^10.0.0
+ version: 10.0.0(@babel/core@7.28.3)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
bing-translate-api:
specifier: ^4.0.2
version: 4.1.0
@@ -463,22 +457,22 @@ importers:
version: 7.0.3
eslint:
specifier: ^9.35.0
- version: 9.35.0(jiti@2.6.0)
+ version: 9.35.0(jiti@2.6.1)
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.35.0(jiti@2.6.0))
+ version: 5.2.0(eslint@9.35.0(jiti@2.6.1))
eslint-plugin-react-refresh:
specifier: ^0.4.19
- version: 0.4.20(eslint@9.35.0(jiti@2.6.0))
+ version: 0.4.20(eslint@9.35.0(jiti@2.6.1))
eslint-plugin-sonarjs:
specifier: ^3.0.2
- version: 3.0.4(eslint@9.35.0(jiti@2.6.0))
+ version: 3.0.4(eslint@9.35.0(jiti@2.6.1))
eslint-plugin-storybook:
specifier: ^9.0.7
- version: 9.0.7(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ version: 9.0.7(eslint@9.35.0(jiti@2.6.1))(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)))
@@ -492,8 +486,8 @@ importers:
specifier: ^29.7.0
version: 29.7.0(@types/node@18.15.0)(ts-node@10.9.2(@types/node@18.15.0)(typescript@5.8.3))
knip:
- specifier: ^5.64.1
- version: 5.64.1(@types/node@18.15.0)(typescript@5.8.3)
+ specifier: ^5.64.3
+ version: 5.64.3(@types/node@18.15.0)(typescript@5.8.3)
lint-staged:
specifier: ^15.2.10
version: 15.5.2
@@ -1246,6 +1240,10 @@ packages:
resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==}
engines: {node: '>=6.9.0'}
+ '@babel/runtime@7.28.4':
+ resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
+ engines: {node: '>=6.9.0'}
+
'@babel/template@7.27.2':
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
engines: {node: '>=6.9.0'}
@@ -1325,13 +1323,6 @@ packages:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
- '@dagrejs/dagre@1.1.5':
- resolution: {integrity: sha512-Ghgrh08s12DCL5SeiR6AoyE80mQELTWhJBRmXfFoqDiFkR458vPEdgTbbjA0T+9ETNxUblnD0QW55tfdvi5pjQ==}
-
- '@dagrejs/graphlib@2.2.4':
- resolution: {integrity: sha512-mepCf/e9+SKYy1d02/UkvSy6+6MoyXhVxP8lLDfA7BPE1X1d4dR0sZznmbM8/XVJ1GPM+Svnx7Xj6ZweByWUkw==}
- engines: {node: '>17.0.0'}
-
'@discoveryjs/json-ext@0.5.7':
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
engines: {node: '>=10.0.0'}
@@ -1339,9 +1330,6 @@ packages:
'@emnapi/core@1.5.0':
resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==}
- '@emnapi/runtime@1.4.4':
- resolution: {integrity: sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg==}
-
'@emnapi/runtime@1.5.0':
resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==}
@@ -1617,30 +1605,54 @@ packages:
'@floating-ui/core@1.7.2':
resolution: {integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==}
+ '@floating-ui/core@1.7.3':
+ resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
+
'@floating-ui/dom@1.7.2':
resolution: {integrity: sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==}
+ '@floating-ui/dom@1.7.4':
+ resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==}
+
'@floating-ui/react-dom@2.1.4':
resolution: {integrity: sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
+ '@floating-ui/react-dom@2.1.6':
+ resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
'@floating-ui/react@0.26.28':
resolution: {integrity: sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
+ '@floating-ui/react@0.27.16':
+ resolution: {integrity: sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g==}
+ peerDependencies:
+ react: '>=17.0.0'
+ react-dom: '>=17.0.0'
+
'@floating-ui/utils@0.2.10':
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
'@formatjs/intl-localematcher@0.5.10':
resolution: {integrity: sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==}
- '@happy-dom/jest-environment@17.6.3':
- resolution: {integrity: sha512-HXuHKvpHLo9/GQ/yKMmKFyS1AYL2t9pL67+GfpYZfOAb29qD80EMozi50zRZk82KmNRBcA2A0/ErjpOwUxJrNg==}
+ '@happy-dom/jest-environment@20.0.0':
+ resolution: {integrity: sha512-dUyMDNJzPDFopSDyzKdbeYs8z9B4jLj9kXnru8TjYdGeLsQKf+6r0lq/9T2XVcu04QFxXMykt64A+KjsaJTaNA==}
engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@jest/environment': '>=25.0.0'
+ '@jest/fake-timers': '>=25.0.0'
+ '@jest/types': '>=25.0.0'
+ jest-mock: '>=25.0.0'
+ jest-util: '>=25.0.0'
'@headlessui/react@2.2.1':
resolution: {integrity: sha512-daiUqVLae8CKVjEVT19P/izW0aGK0GNhMSAeMlrDebKmoVZHcRRwbxzgtnEadUVDXyBsWo9/UH4KHeniO+0tMg==}
@@ -1685,14 +1697,18 @@ packages:
'@iconify/utils@2.3.0':
resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==}
+ '@img/colour@1.0.0':
+ resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
+ engines: {node: '>=18'}
+
'@img/sharp-darwin-arm64@0.33.5':
resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [darwin]
- '@img/sharp-darwin-arm64@0.34.3':
- resolution: {integrity: sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==}
+ '@img/sharp-darwin-arm64@0.34.4':
+ resolution: {integrity: sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [darwin]
@@ -1703,8 +1719,8 @@ packages:
cpu: [x64]
os: [darwin]
- '@img/sharp-darwin-x64@0.34.3':
- resolution: {integrity: sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==}
+ '@img/sharp-darwin-x64@0.34.4':
+ resolution: {integrity: sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [darwin]
@@ -1714,8 +1730,8 @@ packages:
cpu: [arm64]
os: [darwin]
- '@img/sharp-libvips-darwin-arm64@1.2.0':
- resolution: {integrity: sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==}
+ '@img/sharp-libvips-darwin-arm64@1.2.3':
+ resolution: {integrity: sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==}
cpu: [arm64]
os: [darwin]
@@ -1724,8 +1740,8 @@ packages:
cpu: [x64]
os: [darwin]
- '@img/sharp-libvips-darwin-x64@1.2.0':
- resolution: {integrity: sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==}
+ '@img/sharp-libvips-darwin-x64@1.2.3':
+ resolution: {integrity: sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==}
cpu: [x64]
os: [darwin]
@@ -1734,8 +1750,8 @@ packages:
cpu: [arm64]
os: [linux]
- '@img/sharp-libvips-linux-arm64@1.2.0':
- resolution: {integrity: sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==}
+ '@img/sharp-libvips-linux-arm64@1.2.3':
+ resolution: {integrity: sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==}
cpu: [arm64]
os: [linux]
@@ -1744,13 +1760,13 @@ packages:
cpu: [arm]
os: [linux]
- '@img/sharp-libvips-linux-arm@1.2.0':
- resolution: {integrity: sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==}
+ '@img/sharp-libvips-linux-arm@1.2.3':
+ resolution: {integrity: sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==}
cpu: [arm]
os: [linux]
- '@img/sharp-libvips-linux-ppc64@1.2.0':
- resolution: {integrity: sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==}
+ '@img/sharp-libvips-linux-ppc64@1.2.3':
+ resolution: {integrity: sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==}
cpu: [ppc64]
os: [linux]
@@ -1759,8 +1775,8 @@ packages:
cpu: [s390x]
os: [linux]
- '@img/sharp-libvips-linux-s390x@1.2.0':
- resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==}
+ '@img/sharp-libvips-linux-s390x@1.2.3':
+ resolution: {integrity: sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==}
cpu: [s390x]
os: [linux]
@@ -1769,8 +1785,8 @@ packages:
cpu: [x64]
os: [linux]
- '@img/sharp-libvips-linux-x64@1.2.0':
- resolution: {integrity: sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==}
+ '@img/sharp-libvips-linux-x64@1.2.3':
+ resolution: {integrity: sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==}
cpu: [x64]
os: [linux]
@@ -1779,8 +1795,8 @@ packages:
cpu: [arm64]
os: [linux]
- '@img/sharp-libvips-linuxmusl-arm64@1.2.0':
- resolution: {integrity: sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==}
+ '@img/sharp-libvips-linuxmusl-arm64@1.2.3':
+ resolution: {integrity: sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==}
cpu: [arm64]
os: [linux]
@@ -1789,8 +1805,8 @@ packages:
cpu: [x64]
os: [linux]
- '@img/sharp-libvips-linuxmusl-x64@1.2.0':
- resolution: {integrity: sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==}
+ '@img/sharp-libvips-linuxmusl-x64@1.2.3':
+ resolution: {integrity: sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==}
cpu: [x64]
os: [linux]
@@ -1800,8 +1816,8 @@ packages:
cpu: [arm64]
os: [linux]
- '@img/sharp-linux-arm64@0.34.3':
- resolution: {integrity: sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==}
+ '@img/sharp-linux-arm64@0.34.4':
+ resolution: {integrity: sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
@@ -1812,14 +1828,14 @@ packages:
cpu: [arm]
os: [linux]
- '@img/sharp-linux-arm@0.34.3':
- resolution: {integrity: sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==}
+ '@img/sharp-linux-arm@0.34.4':
+ resolution: {integrity: sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm]
os: [linux]
- '@img/sharp-linux-ppc64@0.34.3':
- resolution: {integrity: sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==}
+ '@img/sharp-linux-ppc64@0.34.4':
+ resolution: {integrity: sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ppc64]
os: [linux]
@@ -1830,8 +1846,8 @@ packages:
cpu: [s390x]
os: [linux]
- '@img/sharp-linux-s390x@0.34.3':
- resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==}
+ '@img/sharp-linux-s390x@0.34.4':
+ resolution: {integrity: sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x]
os: [linux]
@@ -1842,8 +1858,8 @@ packages:
cpu: [x64]
os: [linux]
- '@img/sharp-linux-x64@0.34.3':
- resolution: {integrity: sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==}
+ '@img/sharp-linux-x64@0.34.4':
+ resolution: {integrity: sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
@@ -1854,8 +1870,8 @@ packages:
cpu: [arm64]
os: [linux]
- '@img/sharp-linuxmusl-arm64@0.34.3':
- resolution: {integrity: sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==}
+ '@img/sharp-linuxmusl-arm64@0.34.4':
+ resolution: {integrity: sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
@@ -1866,8 +1882,8 @@ packages:
cpu: [x64]
os: [linux]
- '@img/sharp-linuxmusl-x64@0.34.3':
- resolution: {integrity: sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==}
+ '@img/sharp-linuxmusl-x64@0.34.4':
+ resolution: {integrity: sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
@@ -1877,13 +1893,13 @@ packages:
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [wasm32]
- '@img/sharp-wasm32@0.34.3':
- resolution: {integrity: sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==}
+ '@img/sharp-wasm32@0.34.4':
+ resolution: {integrity: sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [wasm32]
- '@img/sharp-win32-arm64@0.34.3':
- resolution: {integrity: sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==}
+ '@img/sharp-win32-arm64@0.34.4':
+ resolution: {integrity: sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [win32]
@@ -1894,8 +1910,8 @@ packages:
cpu: [ia32]
os: [win32]
- '@img/sharp-win32-ia32@0.34.3':
- resolution: {integrity: sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==}
+ '@img/sharp-win32-ia32@0.34.4':
+ resolution: {integrity: sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ia32]
os: [win32]
@@ -1906,8 +1922,8 @@ packages:
cpu: [x64]
os: [win32]
- '@img/sharp-win32-x64@0.34.3':
- resolution: {integrity: sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==}
+ '@img/sharp-win32-x64@0.34.4':
+ resolution: {integrity: sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [win32]
@@ -2020,77 +2036,98 @@ packages:
'@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
- '@lexical/clipboard@0.30.0':
- resolution: {integrity: sha512-taWQURtE6xF4Jy4I8teQw3+nVBVNO1r+9N9voXeivgwxSrAM40rjqQ/aZEKxWbwZtfkABDkCEArbVrqP0SkWcQ==}
+ '@lexical/clipboard@0.36.2':
+ resolution: {integrity: sha512-l7z52jltlMz1HmJRmG7ZdxySPjheRRxdV/75QEnzalMtqfLPgh4G5IpycISjbX+95PgEaC6rXbcjPix0CyHDJg==}
- '@lexical/code@0.30.0':
- resolution: {integrity: sha512-OmA6Bmp3w9SMV25Hae1dLXtPNOdCgnzo1xy84K19U+dPP5iqXagwFq5oY/9PVOOI2wgaQHrz3C+7B4phDb9xaA==}
+ '@lexical/clipboard@0.37.0':
+ resolution: {integrity: sha512-hRwASFX/ilaI5r8YOcZuQgONFshRgCPfdxfofNL7uruSFYAO6LkUhsjzZwUgf0DbmCJmbBADFw15FSthgCUhGA==}
- '@lexical/devtools-core@0.30.0':
- resolution: {integrity: sha512-6vKEEIUym8pQ+tWt4VfRMOGE/dtfyPr9e1zPrAAV7Y/EdzK0AJYPPlw2Dt5Uqq9rposcIriqF4MkuFvy4UcZiQ==}
+ '@lexical/code@0.36.2':
+ resolution: {integrity: sha512-dfS62rNo3uKwNAJQ39zC+8gYX0k8UAoW7u+JPIqx+K2VPukZlvpsPLNGft15pdWBkHc7Pv+o9gJlB6gGv+EBfA==}
+
+ '@lexical/devtools-core@0.36.2':
+ resolution: {integrity: sha512-G+XW7gR/SCx3YgX4FK9wAIn6AIOkC+j8zRPWrS3GQNZ15CE0QkwQl3IyQ7XW9KzWmdRMs6yTmTVnENFa1JLzXg==}
peerDependencies:
react: '>=17.x'
react-dom: '>=17.x'
- '@lexical/dragon@0.30.0':
- resolution: {integrity: sha512-eikVYw1pIcFIOojn2mGlps59YcyT9ATd6UMIx/ivuscakrZeU7SZM/F6c75QPJXNOu1b2koOo+4Bb1GT6jixGQ==}
+ '@lexical/dragon@0.36.2':
+ resolution: {integrity: sha512-VWNjYaH74uQ8MFKkl80pTofojpEnTYSX2sgHyZmo1Lk1cKLHK25pMnWgAxPAMLQD5/RW/2PtZcK+j0Kfoe5lSQ==}
- '@lexical/hashtag@0.30.0':
- resolution: {integrity: sha512-gB3DobSdAc0YZUhlTT7ZAUr+6RRREQ3UWVC1twdtFvXXw1vyTUXH2gWTDp/ParwBZ16Lnrg8mxET8Nu/qD1PSw==}
+ '@lexical/extension@0.36.2':
+ resolution: {integrity: sha512-NWxtqMFMzScq4Eemqp1ST2KREIfj57fUbn7qHv+mMnYgQZK4iIhrHKo5klonxi1oBURcxUZMIbdtH7MJ4BdisA==}
- '@lexical/history@0.30.0':
- resolution: {integrity: sha512-dxudthi94vSLQKXVq3LSwcOVkOmb2lvxoy7sCma513yJbrsn3fPLppR2Ynhl6aB9oPw675wSDrfsE6BG3U3+CA==}
+ '@lexical/extension@0.37.0':
+ resolution: {integrity: sha512-Z58f2tIdz9bn8gltUu5cVg37qROGha38dUZv20gI2GeNugXAkoPzJYEcxlI1D/26tkevJ/7VaFUr9PTk+iKmaA==}
- '@lexical/html@0.30.0':
- resolution: {integrity: sha512-GdegWO6RjJ7eE+yD3Z0X/OpT88SZjOs3DyQ0rgrZy3z7RPaFCbEEcq0M/NssJbKAB1XOFUsUFrnS7kZs1vJzGg==}
+ '@lexical/hashtag@0.36.2':
+ resolution: {integrity: sha512-WdmKtzXFcahQT3ShFDeHF6LCR5C8yvFCj3ImI09rZwICrYeonbMrzsBUxS1joBz0HQ+ufF9Tx+RxLvGWx6WxzQ==}
- '@lexical/link@0.30.0':
- resolution: {integrity: sha512-isD3PC0ywQIwbtekHYEvh7hDxcPz/cEr/AspYntYs08u5J0czhw3rpqnXWGauWaav5V9ExIkf1ZkGUFUI6bw5w==}
+ '@lexical/history@0.36.2':
+ resolution: {integrity: sha512-pnS36gyMWz1yq/3Z2jv0gUxjJfas5j0GZOM4rFTzDAHjRVc5q3Ua4ElwekdcLaPPGpUlcg3jghIGWa2pSeoPvA==}
- '@lexical/list@0.30.0':
- resolution: {integrity: sha512-WKnwH+Cg+j2I0EbaEyPHo8MPNyrqQV3W1NmH5Mf/iRxCq42z7NJxemhmRUxbqv8vsugACwBkh2RlkhekRXmUQQ==}
+ '@lexical/html@0.36.2':
+ resolution: {integrity: sha512-fgqALzgKnoy93G0yFyYD4C4qJTSMZyUt4JE5kj/POFwWNOnXThIqJhQGwBvH/ibImpIfOeds2TrSr8PbStlrNg==}
- '@lexical/mark@0.30.0':
- resolution: {integrity: sha512-dLFH6tJ2WQUSdo1Y2Jp81vRT8j48FjF75K5YLRsKD/UFxWEy+RFgRXsd0H/BuFkx/jPTXt6xe8CaIrZvek8mLg==}
+ '@lexical/html@0.37.0':
+ resolution: {integrity: sha512-oTsBc45eL8/lmF7fqGR+UCjrJYP04gumzf5nk4TczrxWL2pM4GIMLLKG1mpQI2H1MDiRLzq3T/xdI7Gh74z7Zw==}
- '@lexical/markdown@0.30.0':
- resolution: {integrity: sha512-GGddZs63k0wb3/fdL7JyBjiy8L1AIHuRKT68riWbKAcNL7rfMl3Uy5VnMkgV/5bN/2eUQijkGjxG+VxsR8RWbw==}
+ '@lexical/link@0.36.2':
+ resolution: {integrity: sha512-Zb+DeHA1po8VMiOAAXsBmAHhfWmQttsUkI5oiZUmOXJruRuQ2rVr01NoxHpoEpLwHOABVNzD3PMbwov+g3c7lg==}
- '@lexical/offset@0.30.0':
- resolution: {integrity: sha512-sZFbZt5dVdtrdoYk79i13xBDs8/MHXw6CqmZNht85L7UdwiuzVqA3KTyaMe60Vrg6mfsKIVjghbpMOhspcuCrw==}
+ '@lexical/list@0.36.2':
+ resolution: {integrity: sha512-JpaIaE0lgNUrAR7iaCaIoETcCKG9EvZjM3G71VxiexTs7PltmEMq36LUlO2goafWurP7knG2rUpVnTcuSbYYeA==}
- '@lexical/overflow@0.30.0':
- resolution: {integrity: sha512-fvjWnhtPZLMS3qJ6HC6tZTOMmcfNmeRUkgXTas9bvWT8Yul+WLJ/fWjzwvBcqpKlvPQjRFOcDcrW8T/Rp7KPrg==}
+ '@lexical/list@0.37.0':
+ resolution: {integrity: sha512-AOC6yAA3mfNvJKbwo+kvAbPJI+13yF2ISA65vbA578CugvJ08zIVgM+pSzxquGhD0ioJY3cXVW7+gdkCP1qu5g==}
- '@lexical/plain-text@0.30.0':
- resolution: {integrity: sha512-jvxMMxFO3Yuj7evWsc33IGWfigU5A1KrJaIf6zv6GmYj0a7ZRkR1x6vJyc7AlgUM70sld+dozLdoynguQIlmrQ==}
+ '@lexical/mark@0.36.2':
+ resolution: {integrity: sha512-n0MNXtGH+1i43hglgHjpQV0093HmIiFR7Budg2BJb8ZNzO1KZRqeXAHlA5ZzJ698FkAnS4R5bqG9tZ0JJHgAuA==}
- '@lexical/react@0.30.0':
- resolution: {integrity: sha512-fsb6voXzxHyP55lXdmnGhHMfxe6g/f+0NpmfPCkutOXYnY8UqKa86LLYl4Nrsi8HX8BRZfh1H0IjkzDG6EzVPw==}
+ '@lexical/markdown@0.36.2':
+ resolution: {integrity: sha512-jI4McaVKUo8ADOYNCB5LnYyxXDyOWBOofM05r42R9QIMyUxGryo43WNPMAYXzCgtHlkQv+FNles9OlQY0IlAag==}
+
+ '@lexical/offset@0.36.2':
+ resolution: {integrity: sha512-+QQNwzFW/joes3DhNINpGdEX6O5scUTs4n8pYDyM/3pWb+8oCHRaRtEmpUU9HStbdy/pK2kQ9XdztkrNvP/ilA==}
+
+ '@lexical/overflow@0.36.2':
+ resolution: {integrity: sha512-bLaEe93iZIJH5wDh6e/DTZVNz7xO7lMS5akcJW8CIwopr4I/Qv2uCvc4G1bMMHx2xM1gVxstn5rFgIUP8/Gqlg==}
+
+ '@lexical/plain-text@0.36.2':
+ resolution: {integrity: sha512-c9F/+WHl2QuXVhu+1bBVo6BIrSjCcixLe5ePKxoUpy+B7W72s3VCoAQZp+pmtPIyodDLmZAx78hZBBlzoIOeeg==}
+
+ '@lexical/react@0.36.2':
+ resolution: {integrity: sha512-mPVm1BmeuMsMpVyUplgc0btOI8+Vm9bZj4AftgfMSkvzkr8i6NkLn8LV5IlEnoRvxXkjOExwlwBwdQte5ZGvNw==}
peerDependencies:
react: '>=17.x'
react-dom: '>=17.x'
- '@lexical/rich-text@0.30.0':
- resolution: {integrity: sha512-oitOh5u68E5DBZt5VBZIaIeM/iNdt3mIDkGp2C259x81V/9KlSNB9c3rqdTKcs/A+Msw4j60FRhdmZcKQ9uYUA==}
+ '@lexical/rich-text@0.36.2':
+ resolution: {integrity: sha512-dZ7zAIv5NBrh1ApxIT9bayn96zfQHHdnT+oaqmR+q100Vo2uROeR/ZF5igeAuwYGM1Z3ZWDBvNxRKd1d6FWiZw==}
- '@lexical/selection@0.30.0':
- resolution: {integrity: sha512-Ys2XfSmIV/Irg6Xo663YtR4jozIv/7sDemArkEGHT0fxZn2py5qftowPF5IBqFYxKTigAdv5vVPwusBvAnLIEg==}
+ '@lexical/selection@0.36.2':
+ resolution: {integrity: sha512-n96joW3HCKBmPeESR172BxVE+m8V9SdidQm4kKb9jOZ1Ota+tnam2386TeI6795TWwgjDQJPK3HZNKcX6Gb+Bg==}
- '@lexical/table@0.30.0':
- resolution: {integrity: sha512-XPCIMIGnZLKTa5/4cP16bXbmzvMndPR273HNl7ZaF35ky7UjZxdj42HBbE7q9zw2zbRPDiO77EyhYA0p20cbdw==}
+ '@lexical/selection@0.37.0':
+ resolution: {integrity: sha512-Lix1s2r71jHfsTEs4q/YqK2s3uXKOnyA3fd1VDMWysO+bZzRwEO5+qyDvENZ0WrXSDCnlibNFV1HttWX9/zqyw==}
- '@lexical/text@0.30.0':
- resolution: {integrity: sha512-P0ptriFwwP/hoDpz/MoBbzHxrFHqh0kCGzASWUdRZ1zrU0yPvJ9vV/UNMhyolH7xx+eAGI1Yl+m74NlpGmXqTg==}
+ '@lexical/table@0.36.2':
+ resolution: {integrity: sha512-96rNNPiVbC65i+Jn1QzIsehCS7UVUc69ovrh9Bt4+pXDebZSdZai153Q7RUq8q3AQ5ocK4/SA2kLQfMu0grj3Q==}
- '@lexical/text@0.35.0':
- resolution: {integrity: sha512-uaMh46BkysV8hK8wQwp5g/ByZW+2hPDt8ahAErxtf8NuzQem1FHG/f5RTchmFqqUDVHO3qLNTv4AehEGmXv8MA==}
+ '@lexical/table@0.37.0':
+ resolution: {integrity: sha512-g7S8ml8kIujEDLWlzYKETgPCQ2U9oeWqdytRuHjHGi/rjAAGHSej5IRqTPIMxNP3VVQHnBoQ+Y9hBtjiuddhgQ==}
- '@lexical/utils@0.30.0':
- resolution: {integrity: sha512-VJlAUhupCZmnbYYX3zMWovd4viu2guR01sAqKGbbOMbP+4rlaymixFbinvNPaRKDBloOARi+fpiveQFxnyr/Ew==}
+ '@lexical/text@0.36.2':
+ resolution: {integrity: sha512-IbbqgRdMAD6Uk9b2+qSVoy+8RVcczrz6OgXvg39+EYD+XEC7Rbw7kDTWzuNSJJpP7vxSO8YDZSaIlP5gNH3qKA==}
- '@lexical/yjs@0.30.0':
- resolution: {integrity: sha512-mWGFAGpUPz4JoSV+Y0cZOzOZJoMLbVb/enldxEbV0xX71BBVzD0c0vjPxuaIJ9MtNkRZdK3eOubj+B45iOECtw==}
+ '@lexical/utils@0.36.2':
+ resolution: {integrity: sha512-P9+t2Ob10YNGYT/PWEER+1EqH8SAjCNRn+7SBvKbr0IdleGF2JvzbJwAWaRwZs1c18P11XdQZ779dGvWlfwBIw==}
+
+ '@lexical/utils@0.37.0':
+ resolution: {integrity: sha512-CFp4diY/kR5RqhzQSl/7SwsMod1sgLpI1FBifcOuJ6L/S6YywGpEB4B7aV5zqW21A/jU2T+2NZtxSUn6S+9gMg==}
+
+ '@lexical/yjs@0.36.2':
+ resolution: {integrity: sha512-gZ66Mw+uKXTO8KeX/hNKAinXbFg3gnNYraG76lBXCwb/Ka3q34upIY9FUeGOwGVaau3iIDQhE49I+6MugAX2FQ==}
peerDependencies:
yjs: '>=13.5.22'
@@ -2128,20 +2165,20 @@ packages:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
- '@napi-rs/wasm-runtime@1.0.5':
- resolution: {integrity: sha512-TBr9Cf9onSAS2LQ2+QHx6XcC6h9+RIzJgbqG3++9TUZSH204AwEy5jg3BTQ0VATsyoGj4ee49tN/y6rvaOOtcg==}
+ '@napi-rs/wasm-runtime@1.0.7':
+ resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==}
- '@next/bundle-analyzer@15.5.3':
- resolution: {integrity: sha512-l2NxnWHP2gWHbomAlz/wFnN2jNCx/dpr7P/XWeOLhULiyKkXSac8O8SjxRO/8FNhr2l4JNtWVKk82Uya4cZYTw==}
+ '@next/bundle-analyzer@15.5.4':
+ resolution: {integrity: sha512-wMtpIjEHi+B/wC34ZbEcacGIPgQTwTFjjp0+F742s9TxC6QwT0MwB/O0QEgalMe8s3SH/K09DO0gmTvUSJrLRA==}
- '@next/env@15.5.0':
- resolution: {integrity: sha512-sDaprBAfzCQiOgo2pO+LhnV0Wt2wBgartjrr+dpcTORYVnnXD0gwhHhiiyIih9hQbq+JnbqH4odgcFWhqCGidw==}
+ '@next/env@15.5.4':
+ resolution: {integrity: sha512-27SQhYp5QryzIT5uO8hq99C69eLQ7qkzkDPsk3N+GuS2XgOgoYEeOav7Pf8Tn4drECOVDsDg8oj+/DVy8qQL2A==}
- '@next/eslint-plugin-next@15.5.0':
- resolution: {integrity: sha512-+k83U/fST66eQBjTltX2T9qUYd43ntAe+NZ5qeZVTQyTiFiHvTLtkpLKug4AnZAtuI/lwz5tl/4QDJymjVkybg==}
+ '@next/eslint-plugin-next@15.5.4':
+ resolution: {integrity: sha512-SR1vhXNNg16T4zffhJ4TS7Xn7eq4NfKfcOsRwea7RIAHrjRpI9ALYbamqIJqkAhowLlERffiwk0FMvTLNdnVtw==}
- '@next/mdx@15.5.0':
- resolution: {integrity: sha512-TxfWpIDHx9Xy/GgZwegrl+HxjzeQml0bTclxX72SqJLi83IhJaFiglQbfMTotB2hDRbxCGKpPYh0X20+r1Trtw==}
+ '@next/mdx@15.5.4':
+ resolution: {integrity: sha512-QUc14KkswCau2/Lul13t13v8QYRiEh3aeyUMUix5mK/Zd8c/J9NQuVvLGhxS7fxGPU+fOcv0GaXqZshkvNaX7A==}
peerDependencies:
'@mdx-js/loader': '>=0.15.0'
'@mdx-js/react': '>=0.15.0'
@@ -2151,50 +2188,50 @@ packages:
'@mdx-js/react':
optional: true
- '@next/swc-darwin-arm64@15.5.0':
- resolution: {integrity: sha512-v7Jj9iqC6enxIRBIScD/o0lH7QKvSxq2LM8UTyqJi+S2w2QzhMYjven4vgu/RzgsdtdbpkyCxBTzHl/gN5rTRg==}
+ '@next/swc-darwin-arm64@15.5.4':
+ resolution: {integrity: sha512-nopqz+Ov6uvorej8ndRX6HlxCYWCO3AHLfKK2TYvxoSB2scETOcfm/HSS3piPqc3A+MUgyHoqE6je4wnkjfrOA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
- '@next/swc-darwin-x64@15.5.0':
- resolution: {integrity: sha512-s2Nk6ec+pmYmAb/utawuURy7uvyYKDk+TRE5aqLRsdnj3AhwC9IKUBmhfnLmY/+P+DnwqpeXEFIKe9tlG0p6CA==}
+ '@next/swc-darwin-x64@15.5.4':
+ resolution: {integrity: sha512-QOTCFq8b09ghfjRJKfb68kU9k2K+2wsC4A67psOiMn849K9ZXgCSRQr0oVHfmKnoqCbEmQWG1f2h1T2vtJJ9mA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
- '@next/swc-linux-arm64-gnu@15.5.0':
- resolution: {integrity: sha512-mGlPJMZReU4yP5fSHjOxiTYvZmwPSWn/eF/dcg21pwfmiUCKS1amFvf1F1RkLHPIMPfocxLViNWFvkvDB14Isg==}
+ '@next/swc-linux-arm64-gnu@15.5.4':
+ resolution: {integrity: sha512-eRD5zkts6jS3VfE/J0Kt1VxdFqTnMc3QgO5lFE5GKN3KDI/uUpSyK3CjQHmfEkYR4wCOl0R0XrsjpxfWEA++XA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
- '@next/swc-linux-arm64-musl@15.5.0':
- resolution: {integrity: sha512-biWqIOE17OW/6S34t1X8K/3vb1+svp5ji5QQT/IKR+VfM3B7GvlCwmz5XtlEan2ukOUf9tj2vJJBffaGH4fGRw==}
+ '@next/swc-linux-arm64-musl@15.5.4':
+ resolution: {integrity: sha512-TOK7iTxmXFc45UrtKqWdZ1shfxuL4tnVAOuuJK4S88rX3oyVV4ZkLjtMT85wQkfBrOOvU55aLty+MV8xmcJR8A==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
- '@next/swc-linux-x64-gnu@15.5.0':
- resolution: {integrity: sha512-zPisT+obYypM/l6EZ0yRkK3LEuoZqHaSoYKj+5jiD9ESHwdr6QhnabnNxYkdy34uCigNlWIaCbjFmQ8FY5AlxA==}
+ '@next/swc-linux-x64-gnu@15.5.4':
+ resolution: {integrity: sha512-7HKolaj+481FSW/5lL0BcTkA4Ueam9SPYWyN/ib/WGAFZf0DGAN8frNpNZYFHtM4ZstrHZS3LY3vrwlIQfsiMA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
- '@next/swc-linux-x64-musl@15.5.0':
- resolution: {integrity: sha512-+t3+7GoU9IYmk+N+FHKBNFdahaReoAktdOpXHFIPOU1ixxtdge26NgQEEkJkCw2dHT9UwwK5zw4mAsURw4E8jA==}
+ '@next/swc-linux-x64-musl@15.5.4':
+ resolution: {integrity: sha512-nlQQ6nfgN0nCO/KuyEUwwOdwQIGjOs4WNMjEUtpIQJPR2NUfmGpW2wkJln1d4nJ7oUzd1g4GivH5GoEPBgfsdw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
- '@next/swc-win32-arm64-msvc@15.5.0':
- resolution: {integrity: sha512-d8MrXKh0A+c9DLiy1BUFwtg3Hu90Lucj3k6iKTUdPOv42Ve2UiIG8HYi3UAb8kFVluXxEfdpCoPPCSODk5fDcw==}
+ '@next/swc-win32-arm64-msvc@15.5.4':
+ resolution: {integrity: sha512-PcR2bN7FlM32XM6eumklmyWLLbu2vs+D7nJX8OAIoWy69Kef8mfiN4e8TUv2KohprwifdpFKPzIP1njuCjD0YA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
- '@next/swc-win32-x64-msvc@15.5.0':
- resolution: {integrity: sha512-Fe1tGHxOWEyQjmygWkkXSwhFcTJuimrNu52JEuwItrKJVV4iRjbWp9I7zZjwqtiNnQmxoEvoisn8wueFLrNpvQ==}
+ '@next/swc-win32-x64-msvc@15.5.4':
+ resolution: {integrity: sha512-1ur2tSHZj8Px/KMAthmuI9FMp/YFusMMGoRNJaRZMOlSkgvLjzosSdQI0cJAKogdHl3qXUQKL9MGaYvKwA7DXg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
@@ -2323,98 +2360,98 @@ packages:
'@octokit/types@14.1.0':
resolution: {integrity: sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==}
- '@oxc-resolver/binding-android-arm-eabi@11.8.4':
- resolution: {integrity: sha512-6BjMji0TcvQfJ4EoSunOSyu/SiyHKficBD0V3Y0NxF0beaNnnZ7GYEi2lHmRNnRCuIPK8IuVqQ6XizYau+CkKw==}
+ '@oxc-resolver/binding-android-arm-eabi@11.9.0':
+ resolution: {integrity: sha512-4AxaG6TkSBQ2FiC5oGZEJQ35DjsSfAbW6/AJauebq4EzIPVOIgDJCF4de+PvX/Xi9BkNw6VtJuMXJdWW97iEAA==}
cpu: [arm]
os: [android]
- '@oxc-resolver/binding-android-arm64@11.8.4':
- resolution: {integrity: sha512-SxF4X6rzCBS9XNPXKZGoIHIABjfGmtQpEgRBDzpDHx5VTuLAUmwLTHXnVBAZoX5bmnhF79RiMElavzFdJ2cA1A==}
+ '@oxc-resolver/binding-android-arm64@11.9.0':
+ resolution: {integrity: sha512-oOEg7rUd2M6YlmRkvPcszJ6KO6TaLGN21oDdcs27gbTVYbQQtCWYbZz5jRW5zEBJu6dopoWVx+shJNGtG1qDFw==}
cpu: [arm64]
os: [android]
- '@oxc-resolver/binding-darwin-arm64@11.8.4':
- resolution: {integrity: sha512-8zWeERrzgscAniE6kh1TQ4E7GJyglYsvdoKrHYLBCbHWD+0/soffiwAYxZuckKEQSc2RXMSPjcu+JTCALaY0Dw==}
+ '@oxc-resolver/binding-darwin-arm64@11.9.0':
+ resolution: {integrity: sha512-fM6zE/j6o3C1UIkcZPV7C1f186R7w97guY2N4lyNLlhlgwwhd46acnOezLARvRNU5oyKNev4PvOJhGCCDnFMGg==}
cpu: [arm64]
os: [darwin]
- '@oxc-resolver/binding-darwin-x64@11.8.4':
- resolution: {integrity: sha512-BUwggKz8Hi5uEQ0AeVTSun1+sp4lzNcItn+L7fDsHu5Cx0Zueuo10BtVm+dIwmYVVPL5oGYOeD0fS7MKAazKiw==}
+ '@oxc-resolver/binding-darwin-x64@11.9.0':
+ resolution: {integrity: sha512-Bg3Orw7gAxbUqQlt64YPWvHDVo3bo2JfI26Qmzv6nKo7mIMTDhQKl7YmywtLNMYbX0IgUM4qu1V90euu+WCDOw==}
cpu: [x64]
os: [darwin]
- '@oxc-resolver/binding-freebsd-x64@11.8.4':
- resolution: {integrity: sha512-fPO5TQhnn8gA6yP4o49lc4Gn8KeDwAp9uYd4PlE3Q00JVqU6cY9WecDhYHrWtiFcyoZ8UVBlIxuhRqT/DP4Z4A==}
+ '@oxc-resolver/binding-freebsd-x64@11.9.0':
+ resolution: {integrity: sha512-eBqVZqTETH6miBfIZXvpzUe98WATz2+Sh+LEFwuRpGsTsKkIpTyb4p1kwylCLkxrd3Yx7wkxQku+L0AMEGBiAA==}
cpu: [x64]
os: [freebsd]
- '@oxc-resolver/binding-linux-arm-gnueabihf@11.8.4':
- resolution: {integrity: sha512-QuNbdUaVGiP0W0GrXsvCDZjqeL4lZGU7aXlx/S2tCvyTk3wh6skoiLJgqUf/eeqXfUPnzTfntYqyfolzCAyBYA==}
+ '@oxc-resolver/binding-linux-arm-gnueabihf@11.9.0':
+ resolution: {integrity: sha512-QgCk/IJnGBvpbc8rYTVgO+A3m3edJjH1zfv8Nvx7fmsxpbXwWH2l4b4tY3/SLMzasxsp7x7k87+HWt095bI5Lg==}
cpu: [arm]
os: [linux]
- '@oxc-resolver/binding-linux-arm-musleabihf@11.8.4':
- resolution: {integrity: sha512-p/zLMfza8OsC4BDKxqeZ9Qel+4eA/oiMSyKLRkMrTgt6OWQq1d5nHntjfG35Abcw4ev6Q9lRU3NOW5hj0xlUbw==}
+ '@oxc-resolver/binding-linux-arm-musleabihf@11.9.0':
+ resolution: {integrity: sha512-xkJH0jldIXD2GwoHpCDEF0ucJ7fvRETCL+iFLctM679o7qeDXvtzsO/E401EgFFXcWBJNKXWvH+ZfdYMKyowfA==}
cpu: [arm]
os: [linux]
- '@oxc-resolver/binding-linux-arm64-gnu@11.8.4':
- resolution: {integrity: sha512-bvJF9wWxF1+a5YZATlS5JojpOMC7OsnTatA6sXVHoOb7MIigjledYB5ZMAeRrnWWexRMiEX3YSaA46oSfOzmOg==}
+ '@oxc-resolver/binding-linux-arm64-gnu@11.9.0':
+ resolution: {integrity: sha512-TWq+y2psMzbMtZB9USAq2bSA7NV1TMmh9lhAFbMGQ8Yp2YV4BRC/HilD6qF++efQl6shueGBFOv0LVe9BUXaIA==}
cpu: [arm64]
os: [linux]
- '@oxc-resolver/binding-linux-arm64-musl@11.8.4':
- resolution: {integrity: sha512-gf4nwGBfu+EFwOn5p7/T7VF4jmIdfodwJS9MRkOBHvuAm3LQgCX7O6d3Y80mm0TV7ZMRD/trfW628rHfd5++vQ==}
+ '@oxc-resolver/binding-linux-arm64-musl@11.9.0':
+ resolution: {integrity: sha512-8WwGLfXk7yttc6rD6g53+RnYfX5B8xOot1ffthLn8oCXzVRO4cdChlmeHStxwLD/MWx8z8BGeyfyINNrsh9N2w==}
cpu: [arm64]
os: [linux]
- '@oxc-resolver/binding-linux-ppc64-gnu@11.8.4':
- resolution: {integrity: sha512-T120R5GIzRd41rYWWKCI6cSYrZjmRQzf3X4xeE1WX396Uabz5DX8KU7RnVHihSK+KDxccCVOFBxcH3ITd+IEpw==}
+ '@oxc-resolver/binding-linux-ppc64-gnu@11.9.0':
+ resolution: {integrity: sha512-ZWiAXfan6actlSzayaFS/kYO2zD6k1k0fmLb1opbujXYMKepEnjjVOvKdzCIYR/zKzudqI39dGc+ywqVdsPIpQ==}
cpu: [ppc64]
os: [linux]
- '@oxc-resolver/binding-linux-riscv64-gnu@11.8.4':
- resolution: {integrity: sha512-PVG7SxBFFjAaQ76p9O/0Xt5mTBlziRwpck+6cRNhy/hbWY/hSt8BFfPqw0EDSfnl40Uuh+NPsHFMnaWWyxbQEg==}
+ '@oxc-resolver/binding-linux-riscv64-gnu@11.9.0':
+ resolution: {integrity: sha512-p9mCSb+Bym+eycNo9k+81wQ5SAE31E+/rtfbDmF4/7krPotkEjPsEBSc3rqunRwO+FtsUn7H68JLY7hlai49eQ==}
cpu: [riscv64]
os: [linux]
- '@oxc-resolver/binding-linux-riscv64-musl@11.8.4':
- resolution: {integrity: sha512-L0OklUhM2qLGaKvPSyKmwWpoijfc++VJtPyVgz031ShOXyo0WjD0ZGzusyJMsA1a/gdulAmN6CQ/0Sf4LGXEcw==}
+ '@oxc-resolver/binding-linux-riscv64-musl@11.9.0':
+ resolution: {integrity: sha512-/SePuVxgFhLPciRwsJ8kLVltr+rxh0b6riGFuoPnFXBbHFclKnjNIt3TfqzUj0/vOnslXw3cVGPpmtkm2TgCgg==}
cpu: [riscv64]
os: [linux]
- '@oxc-resolver/binding-linux-s390x-gnu@11.8.4':
- resolution: {integrity: sha512-18Ajz5hqO4cRGuoHzLFUsIPod9GIaIRDiXFg2m6CS3NgVdHx7iCZscplYH7KtjdE42M8nGWYMyyq5BOk7QVgPw==}
+ '@oxc-resolver/binding-linux-s390x-gnu@11.9.0':
+ resolution: {integrity: sha512-zLuEjlYIzfnr1Ei2UZYQBbCTa/9deh+BEjO9rh1ai8BfEq4uj6RupTtNpgHfgAsEYdqOBVExw9EU1S6SW3RCAw==}
cpu: [s390x]
os: [linux]
- '@oxc-resolver/binding-linux-x64-gnu@11.8.4':
- resolution: {integrity: sha512-uHvH4RyYBdQ/lFGV9H+R1ScHg6EBnAhE3mnX+u+mO/btnalvg7j80okuHf8Qw0tLQiP5P1sEBoVeE6zviXY9IA==}
+ '@oxc-resolver/binding-linux-x64-gnu@11.9.0':
+ resolution: {integrity: sha512-cxdg73WG+aVlPu/k4lEQPRVOhWunYOUglW6OSzclZLJJAXZU0tSZ5ymKaqPRkfTsyNSAafj1cA1XYd+P9UxBgw==}
cpu: [x64]
os: [linux]
- '@oxc-resolver/binding-linux-x64-musl@11.8.4':
- resolution: {integrity: sha512-X5z44qh5DdJfVhcqXAQFTDFUpcxdpf6DT/lHL5CFcdQGIZxatjc7gFUy05IXPI9xwfq39RValjJBvFovUk9XBw==}
+ '@oxc-resolver/binding-linux-x64-musl@11.9.0':
+ resolution: {integrity: sha512-sy5nkVdMvNgqcx9sIY7G6U9TYZUZC4cmMGw/wKhJNuuD2/HFGtbje62ttXSwBAbVbmJ2GgZ4ZUo/S1OMyU+/OA==}
cpu: [x64]
os: [linux]
- '@oxc-resolver/binding-wasm32-wasi@11.8.4':
- resolution: {integrity: sha512-z3906y+cd8RRhBGNwHRrRAFxnKjXsBeL3+rdQjZpBrUyrhhsaV5iKD/ROx64FNJ9GjL/9mfon8A5xx/McYIqHA==}
+ '@oxc-resolver/binding-wasm32-wasi@11.9.0':
+ resolution: {integrity: sha512-dfi/a0Xh6o6nOLbJdaYuy7txncEcwkRHp9DGGZaAP7zxDiepkBZ6ewSJODQrWwhjVmMteXo+XFzEOMjsC7WUtQ==}
engines: {node: '>=14.0.0'}
cpu: [wasm32]
- '@oxc-resolver/binding-win32-arm64-msvc@11.8.4':
- resolution: {integrity: sha512-70vXFs74uA3X5iYOkpclbkWlQEF+MI325uAQ+Or2n8HJip2T0SEmuBlyw/sRL2E8zLC4oocb+1g25fmzlDVkmg==}
+ '@oxc-resolver/binding-win32-arm64-msvc@11.9.0':
+ resolution: {integrity: sha512-b1yKr+eFwyi8pZMjAQwW352rXpaHAmz7FLK03vFIxdyWzWiiL6S3UrfMu+nKQud38963zu4wNNLm7rdXQazgRA==}
cpu: [arm64]
os: [win32]
- '@oxc-resolver/binding-win32-ia32-msvc@11.8.4':
- resolution: {integrity: sha512-SEOUAzTvr+nyMia3nx1dMtD7YUxZwuhQ3QAPnxy21261Lj0yT3JY4EIfwWH54lAWWfMdRSRRMFuGeF/dq7XjEw==}
+ '@oxc-resolver/binding-win32-ia32-msvc@11.9.0':
+ resolution: {integrity: sha512-DxRT+1HjCpRH8qYCmGHzgsRCYiK+X14PUM9Fb+aD4TljplA7MdDQXqMISTb4zBZ70AuclvlXKTbW+K1GZop3xA==}
cpu: [ia32]
os: [win32]
- '@oxc-resolver/binding-win32-x64-msvc@11.8.4':
- resolution: {integrity: sha512-1gARIQsOPOU7LJ7jvMyPmZEVMapL/PymeG3J7naOdLZDrIZKX6CTvgawJmETYKt+8icP8M6KbBinrVkKVqFd+A==}
+ '@oxc-resolver/binding-win32-x64-msvc@11.9.0':
+ resolution: {integrity: sha512-gE3QJvhh0Yj9cSAkkHjRLKPmC7BTJeiaB5YyhVKVUwbnWQgTszV92lZ9pvZtNPEghP7jPbhEs4c6983A0ojQwA==}
cpu: [x64]
os: [win32]
@@ -2537,6 +2574,9 @@ packages:
'@polka/url@1.0.0-next.29':
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
+ '@preact/signals-core@1.12.1':
+ resolution: {integrity: sha512-BwbTXpj+9QutoZLQvbttRg5x3l5468qaV2kufh+51yha1c53ep5dY4kTuZR35+3pAZxpfQerGJiQqg34ZNZ6uA==}
+
'@radix-ui/primitive@1.1.2':
resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==}
@@ -3312,9 +3352,6 @@ packages:
'@types/d3@7.4.3':
resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==}
- '@types/dagre@0.7.53':
- resolution: {integrity: sha512-f4gkWqzPZvYmKhOsDnhq/R8mO4UMcKdxZo+i5SCkOU1wvGeHJeUXGIHeE9pnwGyPMDof1Vx5ZQo4nxpeg2TTVQ==}
-
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
@@ -3406,6 +3443,9 @@ packages:
'@types/node@18.15.0':
resolution: {integrity: sha512-z6nr0TTEOBGkzLGmbypWOGnpSpSIBorEhC4L+4HeQ2iezKCi4f77kyslRwvHeNitymGQ+oFyIWGP96l/DPSV9w==}
+ '@types/node@20.19.20':
+ resolution: {integrity: sha512-2Q7WS25j4pS1cS8yw3d6buNCVJukOTeQ39bAnwR6sOJbaxvyCGebzTMypDFN82CxBLnl+lSWVdCCWbRY6y9yZQ==}
+
'@types/papaparse@5.3.16':
resolution: {integrity: sha512-T3VuKMC2H0lgsjI9buTB3uuKj3EMD2eap1MOuEQuBQ44EnDx/IkGhU6EwiTf9zG3za4SKlmwKAImdDKdNnCsXg==}
@@ -3465,6 +3505,9 @@ packages:
'@types/uuid@9.0.8':
resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==}
+ '@types/whatwg-mimetype@3.0.2':
+ resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==}
+
'@types/yargs-parser@21.0.3':
resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==}
@@ -3910,6 +3953,13 @@ packages:
peerDependencies:
'@babel/core': ^7.8.0
+ babel-loader@10.0.0:
+ resolution: {integrity: sha512-z8jt+EdS61AMw22nSfoNJAZ0vrtmhPRVi6ghL3rCeRZI8cdNYFiV5xeV3HbE7rlZZNmGH8BVccwWt8/ED0QOHA==}
+ engines: {node: ^18.20.0 || ^20.10.0 || >=22.0.0}
+ peerDependencies:
+ '@babel/core': ^7.12.0
+ webpack: '>=5.61.0'
+
babel-loader@8.4.1:
resolution: {integrity: sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA==}
engines: {node: '>= 8.9'}
@@ -4095,6 +4145,9 @@ packages:
caniuse-lite@1.0.30001727:
resolution: {integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==}
+ caniuse-lite@1.0.30001746:
+ resolution: {integrity: sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==}
+
canvas@2.11.2:
resolution: {integrity: sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==}
engines: {node: '>=6'}
@@ -4715,6 +4768,10 @@ packages:
resolution: {integrity: sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==}
engines: {node: '>=8'}
+ detect-libc@2.1.1:
+ resolution: {integrity: sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==}
+ engines: {node: '>=8'}
+
detect-newline@3.1.0:
resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==}
engines: {node: '>=8'}
@@ -5518,8 +5575,8 @@ packages:
hachure-fill@0.5.2:
resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}
- happy-dom@17.6.3:
- resolution: {integrity: sha512-UVIHeVhxmxedbWPCfgS55Jg2rDfwf2BCKeylcPSqazLz5w3Kri7Q4xdBJubsr/+VUzFLh0VjIvh13RaDA2/Xug==}
+ happy-dom@20.0.0:
+ resolution: {integrity: sha512-GkWnwIFxVGCf2raNrxImLo397RdGhLapj5cT3R2PT7FwL62Ze1DROhzmYW7+J3p9105DYMVenEejEbnq5wA37w==}
engines: {node: '>=20.0.0'}
has-flag@4.0.0:
@@ -6046,8 +6103,8 @@ packages:
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
hasBin: true
- jiti@2.6.0:
- resolution: {integrity: sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==}
+ jiti@2.6.1:
+ resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
js-audio-recorder@1.0.7:
@@ -6140,8 +6197,8 @@ packages:
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
engines: {node: '>=6'}
- knip@5.64.1:
- resolution: {integrity: sha512-80XnLsyeXuyxj1F4+NBtQFHxaRH0xWRw8EKwfQ6EkVZZ0bSz/kqqan08k/Qg8ajWsFPhFq+0S2RbLCBGIQtuOg==}
+ knip@5.64.3:
+ resolution: {integrity: sha512-P9dZetEZfSBwNBFwj55CAnPAMdzVLTTscWx6rdB8eBmPqXPji8F3L+hhWi+Xp+u9O6Xp2ClRDq2JENSK8Z04Qg==}
engines: {node: '>=18.18.0'}
hasBin: true
peerDependencies:
@@ -6179,11 +6236,11 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
- lexical@0.30.0:
- resolution: {integrity: sha512-6gxYeXaJiAcreJD0whCofvO0MuJmnWoIgIl1w7L5FTigfhnEohuCx2SoI/oywzfzXE9gzZnyr3rVvZrMItPL8A==}
+ lexical@0.36.2:
+ resolution: {integrity: sha512-gIDJCmSAhtxD7h95WK17Nz19wCZu92Zn0p1/R45X01S/KAsLCwEtVJ2fTvIJNFTyx3QNJTuGcm5mYgRMUwq8rg==}
- lexical@0.35.0:
- resolution: {integrity: sha512-3VuV8xXhh5xJA6tzvfDvE0YBCMkIZUmxtRilJQDDdCgJCc+eut6qAv2qbN+pbqvarqcQqPN1UF+8YvsjmyOZpw==}
+ lexical@0.37.0:
+ resolution: {integrity: sha512-r5VJR2TioQPAsZATfktnJFrGIiy6gjQN8b/+0a2u1d7/QTH7lhbB7byhGSvcq1iaa1TV/xcf/pFV55a5V5hTDQ==}
lib0@0.2.114:
resolution: {integrity: sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==}
@@ -6652,11 +6709,6 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
- napi-postinstall@0.3.0:
- resolution: {integrity: sha512-M7NqKyhODKV1gRLdkwE7pDsZP2/SC2a2vHkOYh9MCpKMbWVfyVfUw5MaH83Fv6XMjxr5jryUp3IDDL9rlxsTeA==}
- engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
- hasBin: true
-
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
@@ -6682,8 +6734,8 @@ packages:
react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
- next@15.5.0:
- resolution: {integrity: sha512-N1lp9Hatw3a9XLt0307lGB4uTKsXDhyOKQo7uYMzX4i0nF/c27grcGXkLdb7VcT8QPYLBa8ouIyEoUQJ2OyeNQ==}
+ next@15.5.4:
+ resolution: {integrity: sha512-xH4Yjhb82sFYQfY3vbkJfgSDgXvBB6a8xPs9i35k6oZJRoQRihZH+4s9Yo2qsWpzBmZ3lPXaJ2KPXLfkvW4LnA==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
hasBin: true
peerDependencies:
@@ -6809,8 +6861,8 @@ packages:
os-browserify@0.3.0:
resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==}
- oxc-resolver@11.8.4:
- resolution: {integrity: sha512-qpimS3tHHEf+kgESMAme+q+rj7aCzMya00u9YdKOKyX2o7q4lozjPo6d7ZTTi979KHEcVOPWdNTueAKdeNq72w==}
+ oxc-resolver@11.9.0:
+ resolution: {integrity: sha512-u714L0DBBXpD0ERErCQlun2XwinuBfIGo2T8bA7xE8WLQ4uaJudO/VOEQCWslOmcDY2nEkS+UVir5PpyvSG23w==}
p-cancelable@2.1.1:
resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==}
@@ -7270,15 +7322,14 @@ packages:
react: '>= 16.3.0'
react-dom: '>= 16.3.0'
- react-easy-crop@5.5.0:
- resolution: {integrity: sha512-OZzU+yXMhe69vLkDex+5QxcfT94FdcgVCyW2dBUw35ZoC3Is42TUxUy04w8nH1mfMKaizVdC3rh/wUfNW1mK4w==}
+ react-easy-crop@5.5.3:
+ resolution: {integrity: sha512-iKwFTnAsq+IVuyF6N0Q3zjRx9DG1NMySkwWxVfM/xAOeHYH1vhvM+V2kFiq5HOIQGWouITjfltCx54mbDpMpmA==}
peerDependencies:
react: '>=16.4.0'
react-dom: '>=16.4.0'
- react-error-boundary@3.1.4:
- resolution: {integrity: sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==}
- engines: {node: '>=10', npm: '>=6'}
+ react-error-boundary@6.0.0:
+ resolution: {integrity: sha512-gdlJjD7NWr0IfkPlaREN2d9uUZUlksrfOx7SX62VRerwXbMY6ftGCIZua1VG1aXFNOimhISsTq+Owp725b9SiA==}
peerDependencies:
react: '>=16.13.1'
@@ -7721,8 +7772,8 @@ packages:
resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- sharp@0.34.3:
- resolution: {integrity: sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==}
+ sharp@0.34.4:
+ resolution: {integrity: sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
shebang-command@2.0.0:
@@ -7799,6 +7850,10 @@ packages:
resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}
engines: {node: '>= 8'}
+ source-map@0.7.6:
+ resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
+ engines: {node: '>= 12'}
+
source-map@0.8.0-beta.0:
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
engines: {node: '>= 8'}
@@ -8222,6 +8277,9 @@ packages:
engines: {node: '>=0.8.0'}
hasBin: true
+ undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
unicode-canonical-property-names-ecmascript@2.0.1:
resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==}
engines: {node: '>=4'}
@@ -8448,10 +8506,6 @@ packages:
webidl-conversions@4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
- webidl-conversions@7.0.0:
- resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
- engines: {node: '>=12'}
-
webpack-bundle-analyzer@4.10.1:
resolution: {integrity: sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==}
engines: {node: '>= 10.13.0'}
@@ -8669,11 +8723,8 @@ packages:
zod@3.25.76:
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
- zod@4.0.5:
- resolution: {integrity: sha512-/5UuuRPStvHXu7RS+gmvRf4NXrNxpSllGwDnCBcJZtQsKrviYXm54yDGV2KYNLT5kq0lHGcl7lqWJLgSaG+tgA==}
-
- zod@4.1.11:
- resolution: {integrity: sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==}
+ zod@4.1.12:
+ resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==}
zrender@5.6.1:
resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==}
@@ -8712,50 +8763,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.35.0(jiti@2.6.0))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@next/eslint-plugin-next@15.5.0)(@vue/compiler-sfc@3.5.17)(eslint-plugin-react-hooks@5.2.0(eslint@9.35.0(jiti@2.6.0)))(eslint-plugin-react-refresh@0.4.20(eslint@9.35.0(jiti@2.6.0)))(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)':
+ '@antfu/eslint-config@5.0.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.35.0(jiti@2.6.1))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@next/eslint-plugin-next@15.5.4)(@vue/compiler-sfc@3.5.17)(eslint-plugin-react-hooks@5.2.0(eslint@9.35.0(jiti@2.6.1)))(eslint-plugin-react-refresh@0.4.20(eslint@9.35.0(jiti@2.6.1)))(eslint@9.35.0(jiti@2.6.1))(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.35.0(jiti@2.6.0))
+ '@eslint-community/eslint-plugin-eslint-comments': 4.5.0(eslint@9.35.0(jiti@2.6.1))
'@eslint/markdown': 7.1.0
- '@stylistic/eslint-plugin': 5.2.2(eslint@9.35.0(jiti@2.6.0))
- '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3))(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@typescript-eslint/parser': 8.38.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@vitest/eslint-plugin': 1.3.4(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@stylistic/eslint-plugin': 5.2.2(eslint@9.35.0(jiti@2.6.1))
+ '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@typescript-eslint/parser': 8.38.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@vitest/eslint-plugin': 1.3.4(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
ansis: 4.1.0
cac: 6.7.14
- eslint: 9.35.0(jiti@2.6.0)
- eslint-config-flat-gitignore: 2.1.0(eslint@9.35.0(jiti@2.6.0))
+ eslint: 9.35.0(jiti@2.6.1)
+ eslint-config-flat-gitignore: 2.1.0(eslint@9.35.0(jiti@2.6.1))
eslint-flat-config-utils: 2.1.0
- eslint-merge-processors: 2.0.0(eslint@9.35.0(jiti@2.6.0))
- eslint-plugin-antfu: 3.1.1(eslint@9.35.0(jiti@2.6.0))
- eslint-plugin-command: 3.3.1(eslint@9.35.0(jiti@2.6.0))
- eslint-plugin-import-lite: 0.3.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- eslint-plugin-jsdoc: 51.4.1(eslint@9.35.0(jiti@2.6.0))
- eslint-plugin-jsonc: 2.20.1(eslint@9.35.0(jiti@2.6.0))
- eslint-plugin-n: 17.21.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ eslint-merge-processors: 2.0.0(eslint@9.35.0(jiti@2.6.1))
+ eslint-plugin-antfu: 3.1.1(eslint@9.35.0(jiti@2.6.1))
+ eslint-plugin-command: 3.3.1(eslint@9.35.0(jiti@2.6.1))
+ eslint-plugin-import-lite: 0.3.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ eslint-plugin-jsdoc: 51.4.1(eslint@9.35.0(jiti@2.6.1))
+ eslint-plugin-jsonc: 2.20.1(eslint@9.35.0(jiti@2.6.1))
+ eslint-plugin-n: 17.21.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
eslint-plugin-no-only-tests: 3.3.0
- eslint-plugin-perfectionist: 4.15.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- eslint-plugin-pnpm: 1.1.0(eslint@9.35.0(jiti@2.6.0))
- eslint-plugin-regexp: 2.9.0(eslint@9.35.0(jiti@2.6.0))
- eslint-plugin-toml: 0.12.0(eslint@9.35.0(jiti@2.6.0))
- eslint-plugin-unicorn: 60.0.0(eslint@9.35.0(jiti@2.6.0))
- eslint-plugin-unused-imports: 4.1.4(@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3))(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3))(eslint@9.35.0(jiti@2.6.0))
- eslint-plugin-vue: 10.3.0(@typescript-eslint/parser@8.38.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3))(eslint@9.35.0(jiti@2.6.0))(vue-eslint-parser@10.2.0(eslint@9.35.0(jiti@2.6.0)))
- eslint-plugin-yml: 1.18.0(eslint@9.35.0(jiti@2.6.0))
- eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.17)(eslint@9.35.0(jiti@2.6.0))
+ eslint-plugin-perfectionist: 4.15.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ eslint-plugin-pnpm: 1.1.0(eslint@9.35.0(jiti@2.6.1))
+ eslint-plugin-regexp: 2.9.0(eslint@9.35.0(jiti@2.6.1))
+ eslint-plugin-toml: 0.12.0(eslint@9.35.0(jiti@2.6.1))
+ eslint-plugin-unicorn: 60.0.0(eslint@9.35.0(jiti@2.6.1))
+ eslint-plugin-unused-imports: 4.1.4(@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.35.0(jiti@2.6.1))
+ eslint-plugin-vue: 10.3.0(@typescript-eslint/parser@8.38.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.35.0(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.35.0(jiti@2.6.1)))
+ eslint-plugin-yml: 1.18.0(eslint@9.35.0(jiti@2.6.1))
+ eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.17)(eslint@9.35.0(jiti@2.6.1))
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.35.0(jiti@2.6.0))
+ vue-eslint-parser: 10.2.0(eslint@9.35.0(jiti@2.6.1))
yaml-eslint-parser: 1.3.0
optionalDependencies:
- '@eslint-react/eslint-plugin': 1.52.3(eslint@9.35.0(jiti@2.6.0))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3)
- '@next/eslint-plugin-next': 15.5.0
- eslint-plugin-react-hooks: 5.2.0(eslint@9.35.0(jiti@2.6.0))
- eslint-plugin-react-refresh: 0.4.20(eslint@9.35.0(jiti@2.6.0))
+ '@eslint-react/eslint-plugin': 1.52.3(eslint@9.35.0(jiti@2.6.1))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3)
+ '@next/eslint-plugin-next': 15.5.4
+ eslint-plugin-react-hooks: 5.2.0(eslint@9.35.0(jiti@2.6.1))
+ eslint-plugin-react-refresh: 0.4.20(eslint@9.35.0(jiti@2.6.1))
transitivePeerDependencies:
- '@eslint/json'
- '@vue/compiler-sfc'
@@ -9606,6 +9657,8 @@ snapshots:
'@babel/runtime@7.27.6': {}
+ '@babel/runtime@7.28.4': {}
+
'@babel/template@7.27.2':
dependencies:
'@babel/code-frame': 7.27.1
@@ -9743,12 +9796,6 @@ snapshots:
'@jridgewell/trace-mapping': 0.3.9
optional: true
- '@dagrejs/dagre@1.1.5':
- dependencies:
- '@dagrejs/graphlib': 2.2.4
-
- '@dagrejs/graphlib@2.2.4': {}
-
'@discoveryjs/json-ext@0.5.7': {}
'@emnapi/core@1.5.0':
@@ -9757,11 +9804,6 @@ snapshots:
tslib: 2.8.1
optional: true
- '@emnapi/runtime@1.4.4':
- dependencies:
- tslib: 2.8.1
- optional: true
-
'@emnapi/runtime@1.5.0':
dependencies:
tslib: 2.8.1
@@ -9865,30 +9907,30 @@ snapshots:
'@esbuild/win32-x64@0.25.0':
optional: true
- '@eslint-community/eslint-plugin-eslint-comments@4.5.0(eslint@9.35.0(jiti@2.6.0))':
+ '@eslint-community/eslint-plugin-eslint-comments@4.5.0(eslint@9.35.0(jiti@2.6.1))':
dependencies:
escape-string-regexp: 4.0.0
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
ignore: 5.3.2
- '@eslint-community/eslint-utils@4.7.0(eslint@9.35.0(jiti@2.6.0))':
+ '@eslint-community/eslint-utils@4.7.0(eslint@9.35.0(jiti@2.6.1))':
dependencies:
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
eslint-visitor-keys: 3.4.3
- '@eslint-community/eslint-utils@4.9.0(eslint@9.35.0(jiti@2.6.0))':
+ '@eslint-community/eslint-utils@4.9.0(eslint@9.35.0(jiti@2.6.1))':
dependencies:
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.1': {}
- '@eslint-react/ast@1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)':
+ '@eslint-react/ast@1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)':
dependencies:
'@eslint-react/eff': 1.52.3
'@typescript-eslint/types': 8.37.0
'@typescript-eslint/typescript-estree': 8.44.0(typescript@5.8.3)
- '@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
string-ts: 2.2.1
ts-pattern: 5.7.1
transitivePeerDependencies:
@@ -9896,17 +9938,17 @@ snapshots:
- supports-color
- typescript
- '@eslint-react/core@1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)':
+ '@eslint-react/core@1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)':
dependencies:
- '@eslint-react/ast': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@eslint-react/ast': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@eslint-react/eff': 1.52.3
- '@eslint-react/kit': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/shared': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/var': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@eslint-react/kit': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/shared': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/var': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@typescript-eslint/scope-manager': 8.37.0
- '@typescript-eslint/type-utils': 8.37.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@typescript-eslint/type-utils': 8.37.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@typescript-eslint/types': 8.37.0
- '@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
birecord: 0.1.1
ts-pattern: 5.7.1
transitivePeerDependencies:
@@ -9916,58 +9958,58 @@ snapshots:
'@eslint-react/eff@1.52.3': {}
- '@eslint-react/eslint-plugin@1.52.3(eslint@9.35.0(jiti@2.6.0))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3)':
+ '@eslint-react/eslint-plugin@1.52.3(eslint@9.35.0(jiti@2.6.1))(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.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/shared': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@eslint-react/kit': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/shared': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@typescript-eslint/scope-manager': 8.37.0
- '@typescript-eslint/type-utils': 8.37.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@typescript-eslint/type-utils': 8.37.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@typescript-eslint/types': 8.37.0
- '@typescript-eslint/utils': 8.37.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- eslint: 9.35.0(jiti@2.6.0)
- eslint-plugin-react-debug: 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- eslint-plugin-react-dom: 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- eslint-plugin-react-hooks-extra: 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- eslint-plugin-react-naming-convention: 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- eslint-plugin-react-web-api: 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- eslint-plugin-react-x: 1.52.3(eslint@9.35.0(jiti@2.6.0))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3)
+ '@typescript-eslint/utils': 8.37.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ eslint: 9.35.0(jiti@2.6.1)
+ eslint-plugin-react-debug: 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ eslint-plugin-react-dom: 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ eslint-plugin-react-hooks-extra: 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ eslint-plugin-react-naming-convention: 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ eslint-plugin-react-web-api: 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ eslint-plugin-react-x: 1.52.3(eslint@9.35.0(jiti@2.6.1))(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.35.0(jiti@2.6.0))(typescript@5.8.3)':
+ '@eslint-react/kit@1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)':
dependencies:
'@eslint-react/eff': 1.52.3
- '@typescript-eslint/utils': 8.37.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@typescript-eslint/utils': 8.37.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
ts-pattern: 5.7.1
- zod: 4.0.5
+ zod: 4.1.12
transitivePeerDependencies:
- eslint
- supports-color
- typescript
- '@eslint-react/shared@1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)':
+ '@eslint-react/shared@1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)':
dependencies:
'@eslint-react/eff': 1.52.3
- '@eslint-react/kit': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@typescript-eslint/utils': 8.37.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@eslint-react/kit': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@typescript-eslint/utils': 8.37.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
ts-pattern: 5.7.1
- zod: 4.0.5
+ zod: 4.1.12
transitivePeerDependencies:
- eslint
- supports-color
- typescript
- '@eslint-react/var@1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)':
+ '@eslint-react/var@1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)':
dependencies:
- '@eslint-react/ast': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@eslint-react/ast': 1.52.3(eslint@9.35.0(jiti@2.6.1))(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.44.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
string-ts: 2.2.1
ts-pattern: 5.7.1
transitivePeerDependencies:
@@ -9975,9 +10017,9 @@ snapshots:
- supports-color
- typescript
- '@eslint/compat@1.3.1(eslint@9.35.0(jiti@2.6.0))':
+ '@eslint/compat@1.3.1(eslint@9.35.0(jiti@2.6.1))':
optionalDependencies:
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
'@eslint/config-array@0.21.0':
dependencies:
@@ -10042,17 +10084,32 @@ snapshots:
dependencies:
'@floating-ui/utils': 0.2.10
+ '@floating-ui/core@1.7.3':
+ dependencies:
+ '@floating-ui/utils': 0.2.10
+
'@floating-ui/dom@1.7.2':
dependencies:
'@floating-ui/core': 1.7.2
'@floating-ui/utils': 0.2.10
+ '@floating-ui/dom@1.7.4':
+ dependencies:
+ '@floating-ui/core': 1.7.3
+ '@floating-ui/utils': 0.2.10
+
'@floating-ui/react-dom@2.1.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@floating-ui/dom': 1.7.2
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
+ '@floating-ui/react-dom@2.1.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ dependencies:
+ '@floating-ui/dom': 1.7.4
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+
'@floating-ui/react@0.26.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@floating-ui/react-dom': 2.1.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -10061,18 +10118,26 @@ snapshots:
react-dom: 19.1.1(react@19.1.1)
tabbable: 6.2.0
+ '@floating-ui/react@0.27.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ dependencies:
+ '@floating-ui/react-dom': 2.1.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@floating-ui/utils': 0.2.10
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ tabbable: 6.2.0
+
'@floating-ui/utils@0.2.10': {}
'@formatjs/intl-localematcher@0.5.10':
dependencies:
tslib: 2.8.1
- '@happy-dom/jest-environment@17.6.3':
+ '@happy-dom/jest-environment@20.0.0(@jest/environment@29.7.0)(@jest/fake-timers@29.7.0)(@jest/types@29.6.3)(jest-mock@29.7.0)(jest-util@29.7.0)':
dependencies:
'@jest/environment': 29.7.0
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
- happy-dom: 17.6.3
+ happy-dom: 20.0.0
jest-mock: 29.7.0
jest-util: 29.7.0
@@ -10121,14 +10186,17 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@img/colour@1.0.0':
+ optional: true
+
'@img/sharp-darwin-arm64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.0.4
optional: true
- '@img/sharp-darwin-arm64@0.34.3':
+ '@img/sharp-darwin-arm64@0.34.4':
optionalDependencies:
- '@img/sharp-libvips-darwin-arm64': 1.2.0
+ '@img/sharp-libvips-darwin-arm64': 1.2.3
optional: true
'@img/sharp-darwin-x64@0.33.5':
@@ -10136,60 +10204,60 @@ snapshots:
'@img/sharp-libvips-darwin-x64': 1.0.4
optional: true
- '@img/sharp-darwin-x64@0.34.3':
+ '@img/sharp-darwin-x64@0.34.4':
optionalDependencies:
- '@img/sharp-libvips-darwin-x64': 1.2.0
+ '@img/sharp-libvips-darwin-x64': 1.2.3
optional: true
'@img/sharp-libvips-darwin-arm64@1.0.4':
optional: true
- '@img/sharp-libvips-darwin-arm64@1.2.0':
+ '@img/sharp-libvips-darwin-arm64@1.2.3':
optional: true
'@img/sharp-libvips-darwin-x64@1.0.4':
optional: true
- '@img/sharp-libvips-darwin-x64@1.2.0':
+ '@img/sharp-libvips-darwin-x64@1.2.3':
optional: true
'@img/sharp-libvips-linux-arm64@1.0.4':
optional: true
- '@img/sharp-libvips-linux-arm64@1.2.0':
+ '@img/sharp-libvips-linux-arm64@1.2.3':
optional: true
'@img/sharp-libvips-linux-arm@1.0.5':
optional: true
- '@img/sharp-libvips-linux-arm@1.2.0':
+ '@img/sharp-libvips-linux-arm@1.2.3':
optional: true
- '@img/sharp-libvips-linux-ppc64@1.2.0':
+ '@img/sharp-libvips-linux-ppc64@1.2.3':
optional: true
'@img/sharp-libvips-linux-s390x@1.0.4':
optional: true
- '@img/sharp-libvips-linux-s390x@1.2.0':
+ '@img/sharp-libvips-linux-s390x@1.2.3':
optional: true
'@img/sharp-libvips-linux-x64@1.0.4':
optional: true
- '@img/sharp-libvips-linux-x64@1.2.0':
+ '@img/sharp-libvips-linux-x64@1.2.3':
optional: true
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
optional: true
- '@img/sharp-libvips-linuxmusl-arm64@1.2.0':
+ '@img/sharp-libvips-linuxmusl-arm64@1.2.3':
optional: true
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
optional: true
- '@img/sharp-libvips-linuxmusl-x64@1.2.0':
+ '@img/sharp-libvips-linuxmusl-x64@1.2.3':
optional: true
'@img/sharp-linux-arm64@0.33.5':
@@ -10197,9 +10265,9 @@ snapshots:
'@img/sharp-libvips-linux-arm64': 1.0.4
optional: true
- '@img/sharp-linux-arm64@0.34.3':
+ '@img/sharp-linux-arm64@0.34.4':
optionalDependencies:
- '@img/sharp-libvips-linux-arm64': 1.2.0
+ '@img/sharp-libvips-linux-arm64': 1.2.3
optional: true
'@img/sharp-linux-arm@0.33.5':
@@ -10207,14 +10275,14 @@ snapshots:
'@img/sharp-libvips-linux-arm': 1.0.5
optional: true
- '@img/sharp-linux-arm@0.34.3':
+ '@img/sharp-linux-arm@0.34.4':
optionalDependencies:
- '@img/sharp-libvips-linux-arm': 1.2.0
+ '@img/sharp-libvips-linux-arm': 1.2.3
optional: true
- '@img/sharp-linux-ppc64@0.34.3':
+ '@img/sharp-linux-ppc64@0.34.4':
optionalDependencies:
- '@img/sharp-libvips-linux-ppc64': 1.2.0
+ '@img/sharp-libvips-linux-ppc64': 1.2.3
optional: true
'@img/sharp-linux-s390x@0.33.5':
@@ -10222,9 +10290,9 @@ snapshots:
'@img/sharp-libvips-linux-s390x': 1.0.4
optional: true
- '@img/sharp-linux-s390x@0.34.3':
+ '@img/sharp-linux-s390x@0.34.4':
optionalDependencies:
- '@img/sharp-libvips-linux-s390x': 1.2.0
+ '@img/sharp-libvips-linux-s390x': 1.2.3
optional: true
'@img/sharp-linux-x64@0.33.5':
@@ -10232,9 +10300,9 @@ snapshots:
'@img/sharp-libvips-linux-x64': 1.0.4
optional: true
- '@img/sharp-linux-x64@0.34.3':
+ '@img/sharp-linux-x64@0.34.4':
optionalDependencies:
- '@img/sharp-libvips-linux-x64': 1.2.0
+ '@img/sharp-libvips-linux-x64': 1.2.3
optional: true
'@img/sharp-linuxmusl-arm64@0.33.5':
@@ -10242,9 +10310,9 @@ snapshots:
'@img/sharp-libvips-linuxmusl-arm64': 1.0.4
optional: true
- '@img/sharp-linuxmusl-arm64@0.34.3':
+ '@img/sharp-linuxmusl-arm64@0.34.4':
optionalDependencies:
- '@img/sharp-libvips-linuxmusl-arm64': 1.2.0
+ '@img/sharp-libvips-linuxmusl-arm64': 1.2.3
optional: true
'@img/sharp-linuxmusl-x64@0.33.5':
@@ -10252,9 +10320,9 @@ snapshots:
'@img/sharp-libvips-linuxmusl-x64': 1.0.4
optional: true
- '@img/sharp-linuxmusl-x64@0.34.3':
+ '@img/sharp-linuxmusl-x64@0.34.4':
optionalDependencies:
- '@img/sharp-libvips-linuxmusl-x64': 1.2.0
+ '@img/sharp-libvips-linuxmusl-x64': 1.2.3
optional: true
'@img/sharp-wasm32@0.33.5':
@@ -10262,24 +10330,24 @@ snapshots:
'@emnapi/runtime': 1.5.0
optional: true
- '@img/sharp-wasm32@0.34.3':
+ '@img/sharp-wasm32@0.34.4':
dependencies:
- '@emnapi/runtime': 1.4.4
+ '@emnapi/runtime': 1.5.0
optional: true
- '@img/sharp-win32-arm64@0.34.3':
+ '@img/sharp-win32-arm64@0.34.4':
optional: true
'@img/sharp-win32-ia32@0.33.5':
optional: true
- '@img/sharp-win32-ia32@0.34.3':
+ '@img/sharp-win32-ia32@0.34.4':
optional: true
'@img/sharp-win32-x64@0.33.5':
optional: true
- '@img/sharp-win32-x64@0.34.3':
+ '@img/sharp-win32-x64@0.34.4':
optional: true
'@isaacs/balanced-match@4.0.1': {}
@@ -10496,153 +10564,210 @@ snapshots:
'@jridgewell/sourcemap-codec': 1.5.5
optional: true
- '@lexical/clipboard@0.30.0':
+ '@lexical/clipboard@0.36.2':
dependencies:
- '@lexical/html': 0.30.0
- '@lexical/list': 0.30.0
- '@lexical/selection': 0.30.0
- '@lexical/utils': 0.30.0
- lexical: 0.30.0
+ '@lexical/html': 0.36.2
+ '@lexical/list': 0.36.2
+ '@lexical/selection': 0.36.2
+ '@lexical/utils': 0.36.2
+ lexical: 0.36.2
- '@lexical/code@0.30.0':
+ '@lexical/clipboard@0.37.0':
dependencies:
- '@lexical/utils': 0.30.0
- lexical: 0.30.0
+ '@lexical/html': 0.37.0
+ '@lexical/list': 0.37.0
+ '@lexical/selection': 0.37.0
+ '@lexical/utils': 0.37.0
+ lexical: 0.37.0
+
+ '@lexical/code@0.36.2':
+ dependencies:
+ '@lexical/utils': 0.36.2
+ lexical: 0.36.2
prismjs: 1.30.0
- '@lexical/devtools-core@0.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ '@lexical/devtools-core@0.36.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
- '@lexical/html': 0.30.0
- '@lexical/link': 0.30.0
- '@lexical/mark': 0.30.0
- '@lexical/table': 0.30.0
- '@lexical/utils': 0.30.0
- lexical: 0.30.0
+ '@lexical/html': 0.36.2
+ '@lexical/link': 0.36.2
+ '@lexical/mark': 0.36.2
+ '@lexical/table': 0.36.2
+ '@lexical/utils': 0.36.2
+ lexical: 0.36.2
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
- '@lexical/dragon@0.30.0':
+ '@lexical/dragon@0.36.2':
dependencies:
- lexical: 0.30.0
+ '@lexical/extension': 0.36.2
+ lexical: 0.36.2
- '@lexical/hashtag@0.30.0':
+ '@lexical/extension@0.36.2':
dependencies:
- '@lexical/utils': 0.30.0
- lexical: 0.30.0
+ '@lexical/utils': 0.36.2
+ '@preact/signals-core': 1.12.1
+ lexical: 0.36.2
- '@lexical/history@0.30.0':
+ '@lexical/extension@0.37.0':
dependencies:
- '@lexical/utils': 0.30.0
- lexical: 0.30.0
+ '@lexical/utils': 0.37.0
+ '@preact/signals-core': 1.12.1
+ lexical: 0.37.0
- '@lexical/html@0.30.0':
+ '@lexical/hashtag@0.36.2':
dependencies:
- '@lexical/selection': 0.30.0
- '@lexical/utils': 0.30.0
- lexical: 0.30.0
+ '@lexical/text': 0.36.2
+ '@lexical/utils': 0.36.2
+ lexical: 0.36.2
- '@lexical/link@0.30.0':
+ '@lexical/history@0.36.2':
dependencies:
- '@lexical/utils': 0.30.0
- lexical: 0.30.0
+ '@lexical/extension': 0.36.2
+ '@lexical/utils': 0.36.2
+ lexical: 0.36.2
- '@lexical/list@0.30.0':
+ '@lexical/html@0.36.2':
dependencies:
- '@lexical/selection': 0.30.0
- '@lexical/utils': 0.30.0
- lexical: 0.30.0
+ '@lexical/selection': 0.36.2
+ '@lexical/utils': 0.36.2
+ lexical: 0.36.2
- '@lexical/mark@0.30.0':
+ '@lexical/html@0.37.0':
dependencies:
- '@lexical/utils': 0.30.0
- lexical: 0.30.0
+ '@lexical/selection': 0.37.0
+ '@lexical/utils': 0.37.0
+ lexical: 0.37.0
- '@lexical/markdown@0.30.0':
+ '@lexical/link@0.36.2':
dependencies:
- '@lexical/code': 0.30.0
- '@lexical/link': 0.30.0
- '@lexical/list': 0.30.0
- '@lexical/rich-text': 0.30.0
- '@lexical/text': 0.30.0
- '@lexical/utils': 0.30.0
- lexical: 0.30.0
+ '@lexical/extension': 0.36.2
+ '@lexical/utils': 0.36.2
+ lexical: 0.36.2
- '@lexical/offset@0.30.0':
+ '@lexical/list@0.36.2':
dependencies:
- lexical: 0.30.0
+ '@lexical/extension': 0.36.2
+ '@lexical/selection': 0.36.2
+ '@lexical/utils': 0.36.2
+ lexical: 0.36.2
- '@lexical/overflow@0.30.0':
+ '@lexical/list@0.37.0':
dependencies:
- lexical: 0.30.0
+ '@lexical/extension': 0.37.0
+ '@lexical/selection': 0.37.0
+ '@lexical/utils': 0.37.0
+ lexical: 0.37.0
- '@lexical/plain-text@0.30.0':
+ '@lexical/mark@0.36.2':
dependencies:
- '@lexical/clipboard': 0.30.0
- '@lexical/selection': 0.30.0
- '@lexical/utils': 0.30.0
- lexical: 0.30.0
+ '@lexical/utils': 0.36.2
+ lexical: 0.36.2
- '@lexical/react@0.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(yjs@13.6.27)':
+ '@lexical/markdown@0.36.2':
dependencies:
- '@lexical/devtools-core': 0.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- '@lexical/dragon': 0.30.0
- '@lexical/hashtag': 0.30.0
- '@lexical/history': 0.30.0
- '@lexical/link': 0.30.0
- '@lexical/list': 0.30.0
- '@lexical/mark': 0.30.0
- '@lexical/markdown': 0.30.0
- '@lexical/overflow': 0.30.0
- '@lexical/plain-text': 0.30.0
- '@lexical/rich-text': 0.30.0
- '@lexical/table': 0.30.0
- '@lexical/text': 0.30.0
- '@lexical/utils': 0.30.0
- '@lexical/yjs': 0.30.0(yjs@13.6.27)
- lexical: 0.30.0
+ '@lexical/code': 0.36.2
+ '@lexical/link': 0.36.2
+ '@lexical/list': 0.36.2
+ '@lexical/rich-text': 0.36.2
+ '@lexical/text': 0.36.2
+ '@lexical/utils': 0.36.2
+ lexical: 0.36.2
+
+ '@lexical/offset@0.36.2':
+ dependencies:
+ lexical: 0.36.2
+
+ '@lexical/overflow@0.36.2':
+ dependencies:
+ lexical: 0.36.2
+
+ '@lexical/plain-text@0.36.2':
+ dependencies:
+ '@lexical/clipboard': 0.36.2
+ '@lexical/dragon': 0.36.2
+ '@lexical/selection': 0.36.2
+ '@lexical/utils': 0.36.2
+ lexical: 0.36.2
+
+ '@lexical/react@0.36.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(yjs@13.6.27)':
+ dependencies:
+ '@floating-ui/react': 0.27.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@lexical/devtools-core': 0.36.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@lexical/dragon': 0.36.2
+ '@lexical/extension': 0.36.2
+ '@lexical/hashtag': 0.36.2
+ '@lexical/history': 0.36.2
+ '@lexical/link': 0.36.2
+ '@lexical/list': 0.36.2
+ '@lexical/mark': 0.36.2
+ '@lexical/markdown': 0.36.2
+ '@lexical/overflow': 0.36.2
+ '@lexical/plain-text': 0.36.2
+ '@lexical/rich-text': 0.36.2
+ '@lexical/table': 0.36.2
+ '@lexical/text': 0.36.2
+ '@lexical/utils': 0.36.2
+ '@lexical/yjs': 0.36.2(yjs@13.6.27)
+ lexical: 0.36.2
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
- react-error-boundary: 3.1.4(react@19.1.1)
+ react-error-boundary: 6.0.0(react@19.1.1)
transitivePeerDependencies:
- yjs
- '@lexical/rich-text@0.30.0':
+ '@lexical/rich-text@0.36.2':
dependencies:
- '@lexical/clipboard': 0.30.0
- '@lexical/selection': 0.30.0
- '@lexical/utils': 0.30.0
- lexical: 0.30.0
+ '@lexical/clipboard': 0.36.2
+ '@lexical/dragon': 0.36.2
+ '@lexical/selection': 0.36.2
+ '@lexical/utils': 0.36.2
+ lexical: 0.36.2
- '@lexical/selection@0.30.0':
+ '@lexical/selection@0.36.2':
dependencies:
- lexical: 0.30.0
+ lexical: 0.36.2
- '@lexical/table@0.30.0':
+ '@lexical/selection@0.37.0':
dependencies:
- '@lexical/clipboard': 0.30.0
- '@lexical/utils': 0.30.0
- lexical: 0.30.0
+ lexical: 0.37.0
- '@lexical/text@0.30.0':
+ '@lexical/table@0.36.2':
dependencies:
- lexical: 0.30.0
+ '@lexical/clipboard': 0.36.2
+ '@lexical/extension': 0.36.2
+ '@lexical/utils': 0.36.2
+ lexical: 0.36.2
- '@lexical/text@0.35.0':
+ '@lexical/table@0.37.0':
dependencies:
- lexical: 0.35.0
+ '@lexical/clipboard': 0.37.0
+ '@lexical/extension': 0.37.0
+ '@lexical/utils': 0.37.0
+ lexical: 0.37.0
- '@lexical/utils@0.30.0':
+ '@lexical/text@0.36.2':
dependencies:
- '@lexical/list': 0.30.0
- '@lexical/selection': 0.30.0
- '@lexical/table': 0.30.0
- lexical: 0.30.0
+ lexical: 0.36.2
- '@lexical/yjs@0.30.0(yjs@13.6.27)':
+ '@lexical/utils@0.36.2':
dependencies:
- '@lexical/offset': 0.30.0
- '@lexical/selection': 0.30.0
- lexical: 0.30.0
+ '@lexical/list': 0.36.2
+ '@lexical/selection': 0.36.2
+ '@lexical/table': 0.36.2
+ lexical: 0.36.2
+
+ '@lexical/utils@0.37.0':
+ dependencies:
+ '@lexical/list': 0.37.0
+ '@lexical/selection': 0.37.0
+ '@lexical/table': 0.37.0
+ lexical: 0.37.0
+
+ '@lexical/yjs@0.36.2(yjs@13.6.27)':
+ dependencies:
+ '@lexical/offset': 0.36.2
+ '@lexical/selection': 0.36.2
+ lexical: 0.36.2
yjs: 13.6.27
'@mapbox/node-pre-gyp@1.0.11':
@@ -10728,55 +10853,55 @@ snapshots:
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
- '@napi-rs/wasm-runtime@1.0.5':
+ '@napi-rs/wasm-runtime@1.0.7':
dependencies:
'@emnapi/core': 1.5.0
'@emnapi/runtime': 1.5.0
'@tybys/wasm-util': 0.10.1
optional: true
- '@next/bundle-analyzer@15.5.3':
+ '@next/bundle-analyzer@15.5.4':
dependencies:
webpack-bundle-analyzer: 4.10.1
transitivePeerDependencies:
- bufferutil
- utf-8-validate
- '@next/env@15.5.0': {}
+ '@next/env@15.5.4': {}
- '@next/eslint-plugin-next@15.5.0':
+ '@next/eslint-plugin-next@15.5.4':
dependencies:
fast-glob: 3.3.1
- '@next/mdx@15.5.0(@mdx-js/loader@3.1.0(acorn@8.15.0)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3)))(@mdx-js/react@3.1.0(@types/react@19.1.11)(react@19.1.1))':
+ '@next/mdx@15.5.4(@mdx-js/loader@3.1.0(acorn@8.15.0)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3)))(@mdx-js/react@3.1.0(@types/react@19.1.11)(react@19.1.1))':
dependencies:
- source-map: 0.7.4
+ source-map: 0.7.6
optionalDependencies:
'@mdx-js/loader': 3.1.0(acorn@8.15.0)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
'@mdx-js/react': 3.1.0(@types/react@19.1.11)(react@19.1.1)
- '@next/swc-darwin-arm64@15.5.0':
+ '@next/swc-darwin-arm64@15.5.4':
optional: true
- '@next/swc-darwin-x64@15.5.0':
+ '@next/swc-darwin-x64@15.5.4':
optional: true
- '@next/swc-linux-arm64-gnu@15.5.0':
+ '@next/swc-linux-arm64-gnu@15.5.4':
optional: true
- '@next/swc-linux-arm64-musl@15.5.0':
+ '@next/swc-linux-arm64-musl@15.5.4':
optional: true
- '@next/swc-linux-x64-gnu@15.5.0':
+ '@next/swc-linux-x64-gnu@15.5.4':
optional: true
- '@next/swc-linux-x64-musl@15.5.0':
+ '@next/swc-linux-x64-musl@15.5.4':
optional: true
- '@next/swc-win32-arm64-msvc@15.5.0':
+ '@next/swc-win32-arm64-msvc@15.5.4':
optional: true
- '@next/swc-win32-x64-msvc@15.5.0':
+ '@next/swc-win32-x64-msvc@15.5.4':
optional: true
'@nodelib/fs.scandir@2.1.5':
@@ -10900,63 +11025,63 @@ snapshots:
dependencies:
'@octokit/openapi-types': 25.1.0
- '@oxc-resolver/binding-android-arm-eabi@11.8.4':
+ '@oxc-resolver/binding-android-arm-eabi@11.9.0':
optional: true
- '@oxc-resolver/binding-android-arm64@11.8.4':
+ '@oxc-resolver/binding-android-arm64@11.9.0':
optional: true
- '@oxc-resolver/binding-darwin-arm64@11.8.4':
+ '@oxc-resolver/binding-darwin-arm64@11.9.0':
optional: true
- '@oxc-resolver/binding-darwin-x64@11.8.4':
+ '@oxc-resolver/binding-darwin-x64@11.9.0':
optional: true
- '@oxc-resolver/binding-freebsd-x64@11.8.4':
+ '@oxc-resolver/binding-freebsd-x64@11.9.0':
optional: true
- '@oxc-resolver/binding-linux-arm-gnueabihf@11.8.4':
+ '@oxc-resolver/binding-linux-arm-gnueabihf@11.9.0':
optional: true
- '@oxc-resolver/binding-linux-arm-musleabihf@11.8.4':
+ '@oxc-resolver/binding-linux-arm-musleabihf@11.9.0':
optional: true
- '@oxc-resolver/binding-linux-arm64-gnu@11.8.4':
+ '@oxc-resolver/binding-linux-arm64-gnu@11.9.0':
optional: true
- '@oxc-resolver/binding-linux-arm64-musl@11.8.4':
+ '@oxc-resolver/binding-linux-arm64-musl@11.9.0':
optional: true
- '@oxc-resolver/binding-linux-ppc64-gnu@11.8.4':
+ '@oxc-resolver/binding-linux-ppc64-gnu@11.9.0':
optional: true
- '@oxc-resolver/binding-linux-riscv64-gnu@11.8.4':
+ '@oxc-resolver/binding-linux-riscv64-gnu@11.9.0':
optional: true
- '@oxc-resolver/binding-linux-riscv64-musl@11.8.4':
+ '@oxc-resolver/binding-linux-riscv64-musl@11.9.0':
optional: true
- '@oxc-resolver/binding-linux-s390x-gnu@11.8.4':
+ '@oxc-resolver/binding-linux-s390x-gnu@11.9.0':
optional: true
- '@oxc-resolver/binding-linux-x64-gnu@11.8.4':
+ '@oxc-resolver/binding-linux-x64-gnu@11.9.0':
optional: true
- '@oxc-resolver/binding-linux-x64-musl@11.8.4':
+ '@oxc-resolver/binding-linux-x64-musl@11.9.0':
optional: true
- '@oxc-resolver/binding-wasm32-wasi@11.8.4':
+ '@oxc-resolver/binding-wasm32-wasi@11.9.0':
dependencies:
- '@napi-rs/wasm-runtime': 1.0.5
+ '@napi-rs/wasm-runtime': 1.0.7
optional: true
- '@oxc-resolver/binding-win32-arm64-msvc@11.8.4':
+ '@oxc-resolver/binding-win32-arm64-msvc@11.9.0':
optional: true
- '@oxc-resolver/binding-win32-ia32-msvc@11.8.4':
+ '@oxc-resolver/binding-win32-ia32-msvc@11.9.0':
optional: true
- '@oxc-resolver/binding-win32-x64-msvc@11.8.4':
+ '@oxc-resolver/binding-win32-x64-msvc@11.9.0':
optional: true
'@parcel/watcher-android-arm64@2.5.1':
@@ -11042,6 +11167,8 @@ snapshots:
'@polka/url@1.0.0-next.29': {}
+ '@preact/signals-core@1.12.1': {}
+
'@radix-ui/primitive@1.1.2': {}
'@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.11)(react@19.1.1)':
@@ -11607,7 +11734,7 @@ snapshots:
dependencies:
storybook: 8.5.0
- '@storybook/nextjs@8.5.0(esbuild@0.25.0)(next@15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1)(storybook@8.5.0)(type-fest@2.19.0)(typescript@5.8.3)(uglify-js@3.19.3)(webpack-hot-middleware@2.26.1)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))':
+ '@storybook/nextjs@8.5.0(esbuild@0.25.0)(next@15.5.4(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1)(storybook@8.5.0)(type-fest@2.19.0)(typescript@5.8.3)(uglify-js@3.19.3)(webpack-hot-middleware@2.26.1)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))':
dependencies:
'@babel/core': 7.28.3
'@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.3)
@@ -11633,7 +11760,7 @@ snapshots:
find-up: 5.0.0
image-size: 1.2.1
loader-utils: 3.3.1
- next: 15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1)
+ next: 15.5.4(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1)
node-polyfill-webpack-plugin: 2.0.1(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
pnp-webpack-plugin: 1.7.0(typescript@5.8.3)
postcss: 8.5.6
@@ -11759,11 +11886,11 @@ snapshots:
dependencies:
storybook: 8.5.0
- '@stylistic/eslint-plugin@5.2.2(eslint@9.35.0(jiti@2.6.0))':
+ '@stylistic/eslint-plugin@5.2.2(eslint@9.35.0(jiti@2.6.1))':
dependencies:
- '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.6.0))
+ '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.6.1))
'@typescript-eslint/types': 8.38.0
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
eslint-visitor-keys: 4.2.1
espree: 10.4.0
estraverse: 5.3.0
@@ -12052,8 +12179,6 @@ snapshots:
'@types/d3-transition': 3.0.9
'@types/d3-zoom': 3.0.8
- '@types/dagre@0.7.53': {}
-
'@types/debug@4.1.12':
dependencies:
'@types/ms': 2.1.0
@@ -12148,6 +12273,10 @@ snapshots:
'@types/node@18.15.0': {}
+ '@types/node@20.19.20':
+ dependencies:
+ undici-types: 6.21.0
+
'@types/papaparse@5.3.16':
dependencies:
'@types/node': 18.15.0
@@ -12202,21 +12331,23 @@ snapshots:
'@types/uuid@9.0.8': {}
+ '@types/whatwg-mimetype@3.0.2': {}
+
'@types/yargs-parser@21.0.3': {}
'@types/yargs@17.0.33':
dependencies:
'@types/yargs-parser': 21.0.3
- '@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3))(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)':
+ '@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)':
dependencies:
'@eslint-community/regexpp': 4.12.1
- '@typescript-eslint/parser': 8.38.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@typescript-eslint/parser': 8.38.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@typescript-eslint/scope-manager': 8.38.0
- '@typescript-eslint/type-utils': 8.38.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@typescript-eslint/utils': 8.38.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@typescript-eslint/type-utils': 8.38.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@typescript-eslint/utils': 8.38.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@typescript-eslint/visitor-keys': 8.38.0
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
graphemer: 1.4.0
ignore: 7.0.5
natural-compare: 1.4.0
@@ -12225,14 +12356,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@8.38.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)':
+ '@typescript-eslint/parser@8.38.0(eslint@9.35.0(jiti@2.6.1))(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.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
@@ -12291,25 +12422,25 @@ snapshots:
dependencies:
typescript: 5.8.3
- '@typescript-eslint/type-utils@8.37.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)':
+ '@typescript-eslint/type-utils@8.37.0(eslint@9.35.0(jiti@2.6.1))(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.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@typescript-eslint/utils': 8.37.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
debug: 4.4.1
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
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.35.0(jiti@2.6.0))(typescript@5.8.3)':
+ '@typescript-eslint/type-utils@8.38.0(eslint@9.35.0(jiti@2.6.1))(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.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@typescript-eslint/utils': 8.38.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
debug: 4.4.1
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
ts-api-utils: 2.1.0(typescript@5.8.3)
typescript: 5.8.3
transitivePeerDependencies:
@@ -12369,35 +12500,35 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.37.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)':
+ '@typescript-eslint/utils@8.37.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)':
dependencies:
- '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.6.0))
+ '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.6.1))
'@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.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.38.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)':
+ '@typescript-eslint/utils@8.38.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)':
dependencies:
- '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.6.0))
+ '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.6.1))
'@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.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.44.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)':
+ '@typescript-eslint/utils@8.44.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)':
dependencies:
- '@eslint-community/eslint-utils': 4.9.0(eslint@9.35.0(jiti@2.6.0))
+ '@eslint-community/eslint-utils': 4.9.0(eslint@9.35.0(jiti@2.6.1))
'@typescript-eslint/scope-manager': 8.44.0
'@typescript-eslint/types': 8.44.0
'@typescript-eslint/typescript-estree': 8.44.0(typescript@5.8.3)
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
@@ -12419,10 +12550,10 @@ snapshots:
'@ungap/structured-clone@1.3.0': {}
- '@vitest/eslint-plugin@1.3.4(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)':
+ '@vitest/eslint-plugin@1.3.4(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)':
dependencies:
- '@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- eslint: 9.35.0(jiti@2.6.0)
+ '@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ eslint: 9.35.0(jiti@2.6.1)
optionalDependencies:
typescript: 5.8.3
transitivePeerDependencies:
@@ -12762,6 +12893,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ babel-loader@10.0.0(@babel/core@7.28.3)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3)):
+ dependencies:
+ '@babel/core': 7.28.3
+ find-up: 5.0.0
+ webpack: 5.100.2(esbuild@0.25.0)(uglify-js@3.19.3)
+
babel-loader@8.4.1(@babel/core@7.28.3)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3)):
dependencies:
'@babel/core': 7.28.3
@@ -12986,6 +13123,8 @@ snapshots:
caniuse-lite@1.0.30001727: {}
+ caniuse-lite@1.0.30001746: {}
+
canvas@2.11.2:
dependencies:
'@mapbox/node-pre-gyp': 1.0.11
@@ -13627,6 +13766,9 @@ snapshots:
detect-libc@2.1.0: {}
+ detect-libc@2.1.1:
+ optional: true
+
detect-newline@3.1.0: {}
detect-node-es@1.1.0: {}
@@ -13833,67 +13975,67 @@ snapshots:
escape-string-regexp@5.0.0: {}
- eslint-compat-utils@0.5.1(eslint@9.35.0(jiti@2.6.0)):
+ eslint-compat-utils@0.5.1(eslint@9.35.0(jiti@2.6.1)):
dependencies:
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
semver: 7.7.2
- eslint-compat-utils@0.6.5(eslint@9.35.0(jiti@2.6.0)):
+ eslint-compat-utils@0.6.5(eslint@9.35.0(jiti@2.6.1)):
dependencies:
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
semver: 7.7.2
- eslint-config-flat-gitignore@2.1.0(eslint@9.35.0(jiti@2.6.0)):
+ eslint-config-flat-gitignore@2.1.0(eslint@9.35.0(jiti@2.6.1)):
dependencies:
- '@eslint/compat': 1.3.1(eslint@9.35.0(jiti@2.6.0))
- eslint: 9.35.0(jiti@2.6.0)
+ '@eslint/compat': 1.3.1(eslint@9.35.0(jiti@2.6.1))
+ eslint: 9.35.0(jiti@2.6.1)
eslint-flat-config-utils@2.1.0:
dependencies:
pathe: 2.0.3
- eslint-json-compat-utils@0.2.1(eslint@9.35.0(jiti@2.6.0))(jsonc-eslint-parser@2.4.0):
+ eslint-json-compat-utils@0.2.1(eslint@9.35.0(jiti@2.6.1))(jsonc-eslint-parser@2.4.0):
dependencies:
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
esquery: 1.6.0
jsonc-eslint-parser: 2.4.0
- eslint-merge-processors@2.0.0(eslint@9.35.0(jiti@2.6.0)):
+ eslint-merge-processors@2.0.0(eslint@9.35.0(jiti@2.6.1)):
dependencies:
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
- eslint-plugin-antfu@3.1.1(eslint@9.35.0(jiti@2.6.0)):
+ eslint-plugin-antfu@3.1.1(eslint@9.35.0(jiti@2.6.1)):
dependencies:
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
- eslint-plugin-command@3.3.1(eslint@9.35.0(jiti@2.6.0)):
+ eslint-plugin-command@3.3.1(eslint@9.35.0(jiti@2.6.1)):
dependencies:
'@es-joy/jsdoccomment': 0.50.2
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
- eslint-plugin-es-x@7.8.0(eslint@9.35.0(jiti@2.6.0)):
+ eslint-plugin-es-x@7.8.0(eslint@9.35.0(jiti@2.6.1)):
dependencies:
- '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.6.0))
+ '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.6.1))
'@eslint-community/regexpp': 4.12.1
- eslint: 9.35.0(jiti@2.6.0)
- eslint-compat-utils: 0.5.1(eslint@9.35.0(jiti@2.6.0))
+ eslint: 9.35.0(jiti@2.6.1)
+ eslint-compat-utils: 0.5.1(eslint@9.35.0(jiti@2.6.1))
- eslint-plugin-import-lite@0.3.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3):
+ eslint-plugin-import-lite@0.3.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3):
dependencies:
- '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.6.0))
+ '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.6.1))
'@typescript-eslint/types': 8.38.0
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
optionalDependencies:
typescript: 5.8.3
- eslint-plugin-jsdoc@51.4.1(eslint@9.35.0(jiti@2.6.0)):
+ eslint-plugin-jsdoc@51.4.1(eslint@9.35.0(jiti@2.6.1)):
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.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
espree: 10.4.0
esquery: 1.6.0
parse-imports-exports: 0.2.4
@@ -13902,12 +14044,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-jsonc@2.20.1(eslint@9.35.0(jiti@2.6.0)):
+ eslint-plugin-jsonc@2.20.1(eslint@9.35.0(jiti@2.6.1)):
dependencies:
- '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.6.0))
- eslint: 9.35.0(jiti@2.6.0)
- eslint-compat-utils: 0.6.5(eslint@9.35.0(jiti@2.6.0))
- eslint-json-compat-utils: 0.2.1(eslint@9.35.0(jiti@2.6.0))(jsonc-eslint-parser@2.4.0)
+ '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.6.1))
+ eslint: 9.35.0(jiti@2.6.1)
+ eslint-compat-utils: 0.6.5(eslint@9.35.0(jiti@2.6.1))
+ eslint-json-compat-utils: 0.2.1(eslint@9.35.0(jiti@2.6.1))(jsonc-eslint-parser@2.4.0)
espree: 10.4.0
graphemer: 1.4.0
jsonc-eslint-parser: 2.4.0
@@ -13916,12 +14058,12 @@ snapshots:
transitivePeerDependencies:
- '@eslint/json'
- eslint-plugin-n@17.21.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3):
+ eslint-plugin-n@17.21.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3):
dependencies:
- '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.6.0))
+ '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.6.1))
enhanced-resolve: 5.18.2
- eslint: 9.35.0(jiti@2.6.0)
- eslint-plugin-es-x: 7.8.0(eslint@9.35.0(jiti@2.6.0))
+ eslint: 9.35.0(jiti@2.6.1)
+ eslint-plugin-es-x: 7.8.0(eslint@9.35.0(jiti@2.6.1))
get-tsconfig: 4.10.1
globals: 15.15.0
ignore: 5.3.2
@@ -13937,19 +14079,19 @@ snapshots:
dependencies:
jsonc-parser: 3.3.1
- eslint-plugin-perfectionist@4.15.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3):
+ eslint-plugin-perfectionist@4.15.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3):
dependencies:
'@typescript-eslint/types': 8.38.0
- '@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- eslint: 9.35.0(jiti@2.6.0)
+ '@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ eslint: 9.35.0(jiti@2.6.1)
natural-orderby: 5.0.0
transitivePeerDependencies:
- supports-color
- typescript
- eslint-plugin-pnpm@1.1.0(eslint@9.35.0(jiti@2.6.0)):
+ eslint-plugin-pnpm@1.1.0(eslint@9.35.0(jiti@2.6.1)):
dependencies:
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
find-up-simple: 1.0.1
jsonc-eslint-parser: 2.4.0
pathe: 2.0.3
@@ -13957,19 +14099,19 @@ snapshots:
tinyglobby: 0.2.14
yaml-eslint-parser: 1.3.0
- eslint-plugin-react-debug@1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3):
+ eslint-plugin-react-debug@1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3):
dependencies:
- '@eslint-react/ast': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/core': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@eslint-react/ast': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/core': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@eslint-react/eff': 1.52.3
- '@eslint-react/kit': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/shared': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/var': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@eslint-react/kit': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/shared': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/var': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@typescript-eslint/scope-manager': 8.37.0
- '@typescript-eslint/type-utils': 8.37.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@typescript-eslint/type-utils': 8.37.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@typescript-eslint/types': 8.37.0
- '@typescript-eslint/utils': 8.37.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- eslint: 9.35.0(jiti@2.6.0)
+ '@typescript-eslint/utils': 8.37.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ eslint: 9.35.0(jiti@2.6.1)
string-ts: 2.2.1
ts-pattern: 5.7.1
optionalDependencies:
@@ -13977,19 +14119,19 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-react-dom@1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3):
+ eslint-plugin-react-dom@1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3):
dependencies:
- '@eslint-react/ast': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/core': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@eslint-react/ast': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/core': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@eslint-react/eff': 1.52.3
- '@eslint-react/kit': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/shared': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/var': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@eslint-react/kit': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/shared': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/var': 1.52.3(eslint@9.35.0(jiti@2.6.1))(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.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@typescript-eslint/utils': 8.37.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
compare-versions: 6.1.1
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
string-ts: 2.2.1
ts-pattern: 5.7.1
optionalDependencies:
@@ -13997,19 +14139,19 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-react-hooks-extra@1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3):
+ eslint-plugin-react-hooks-extra@1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3):
dependencies:
- '@eslint-react/ast': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/core': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@eslint-react/ast': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/core': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@eslint-react/eff': 1.52.3
- '@eslint-react/kit': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/shared': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/var': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@eslint-react/kit': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/shared': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/var': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@typescript-eslint/scope-manager': 8.37.0
- '@typescript-eslint/type-utils': 8.37.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@typescript-eslint/type-utils': 8.37.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@typescript-eslint/types': 8.37.0
- '@typescript-eslint/utils': 8.37.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- eslint: 9.35.0(jiti@2.6.0)
+ '@typescript-eslint/utils': 8.37.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ eslint: 9.35.0(jiti@2.6.1)
string-ts: 2.2.1
ts-pattern: 5.7.1
optionalDependencies:
@@ -14017,23 +14159,23 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-react-hooks@5.2.0(eslint@9.35.0(jiti@2.6.0)):
+ eslint-plugin-react-hooks@5.2.0(eslint@9.35.0(jiti@2.6.1)):
dependencies:
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
- eslint-plugin-react-naming-convention@1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3):
+ eslint-plugin-react-naming-convention@1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3):
dependencies:
- '@eslint-react/ast': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/core': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@eslint-react/ast': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/core': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@eslint-react/eff': 1.52.3
- '@eslint-react/kit': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/shared': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/var': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@eslint-react/kit': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/shared': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/var': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@typescript-eslint/scope-manager': 8.37.0
- '@typescript-eslint/type-utils': 8.37.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@typescript-eslint/type-utils': 8.37.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@typescript-eslint/types': 8.37.0
- '@typescript-eslint/utils': 8.37.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- eslint: 9.35.0(jiti@2.6.0)
+ '@typescript-eslint/utils': 8.37.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ eslint: 9.35.0(jiti@2.6.1)
string-ts: 2.2.1
ts-pattern: 5.7.1
optionalDependencies:
@@ -14041,22 +14183,22 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-react-refresh@0.4.20(eslint@9.35.0(jiti@2.6.0)):
+ eslint-plugin-react-refresh@0.4.20(eslint@9.35.0(jiti@2.6.1)):
dependencies:
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
- eslint-plugin-react-web-api@1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3):
+ eslint-plugin-react-web-api@1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3):
dependencies:
- '@eslint-react/ast': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/core': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@eslint-react/ast': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/core': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@eslint-react/eff': 1.52.3
- '@eslint-react/kit': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/shared': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/var': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@eslint-react/kit': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/shared': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/var': 1.52.3(eslint@9.35.0(jiti@2.6.1))(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.35.0(jiti@2.6.0))(typescript@5.8.3)
- eslint: 9.35.0(jiti@2.6.0)
+ '@typescript-eslint/utils': 8.37.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ eslint: 9.35.0(jiti@2.6.1)
string-ts: 2.2.1
ts-pattern: 5.7.1
optionalDependencies:
@@ -14064,21 +14206,21 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-react-x@1.52.3(eslint@9.35.0(jiti@2.6.0))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3):
+ eslint-plugin-react-x@1.52.3(eslint@9.35.0(jiti@2.6.1))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3):
dependencies:
- '@eslint-react/ast': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/core': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@eslint-react/ast': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/core': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@eslint-react/eff': 1.52.3
- '@eslint-react/kit': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/shared': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- '@eslint-react/var': 1.52.3(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@eslint-react/kit': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/shared': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ '@eslint-react/var': 1.52.3(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@typescript-eslint/scope-manager': 8.37.0
- '@typescript-eslint/type-utils': 8.37.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@typescript-eslint/type-utils': 8.37.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
'@typescript-eslint/types': 8.37.0
- '@typescript-eslint/utils': 8.37.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@typescript-eslint/utils': 8.37.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
compare-versions: 6.1.1
- eslint: 9.35.0(jiti@2.6.0)
- is-immutable-type: 5.0.1(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ eslint: 9.35.0(jiti@2.6.1)
+ is-immutable-type: 5.0.1(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
string-ts: 2.2.1
ts-pattern: 5.7.1
optionalDependencies:
@@ -14087,23 +14229,23 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-regexp@2.9.0(eslint@9.35.0(jiti@2.6.0)):
+ eslint-plugin-regexp@2.9.0(eslint@9.35.0(jiti@2.6.1)):
dependencies:
- '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.6.0))
+ '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.6.1))
'@eslint-community/regexpp': 4.12.1
comment-parser: 1.4.1
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
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.35.0(jiti@2.6.0)):
+ eslint-plugin-sonarjs@3.0.4(eslint@9.35.0(jiti@2.6.1)):
dependencies:
'@eslint-community/regexpp': 4.12.1
builtin-modules: 3.3.0
bytes: 3.1.2
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
functional-red-black-tree: 1.0.1
jsx-ast-utils: 3.3.5
lodash.merge: 4.6.2
@@ -14112,11 +14254,11 @@ snapshots:
semver: 7.7.2
typescript: 5.8.3
- eslint-plugin-storybook@9.0.7(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3):
+ eslint-plugin-storybook@9.0.7(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3):
dependencies:
'@storybook/csf': 0.1.13
- '@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- eslint: 9.35.0(jiti@2.6.0)
+ '@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ eslint: 9.35.0(jiti@2.6.1)
ts-dedent: 2.2.0
transitivePeerDependencies:
- supports-color
@@ -14128,26 +14270,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.35.0(jiti@2.6.0)):
+ eslint-plugin-toml@0.12.0(eslint@9.35.0(jiti@2.6.1)):
dependencies:
debug: 4.4.1
- eslint: 9.35.0(jiti@2.6.0)
- eslint-compat-utils: 0.6.5(eslint@9.35.0(jiti@2.6.0))
+ eslint: 9.35.0(jiti@2.6.1)
+ eslint-compat-utils: 0.6.5(eslint@9.35.0(jiti@2.6.1))
lodash: 4.17.21
toml-eslint-parser: 0.10.0
transitivePeerDependencies:
- supports-color
- eslint-plugin-unicorn@60.0.0(eslint@9.35.0(jiti@2.6.0)):
+ eslint-plugin-unicorn@60.0.0(eslint@9.35.0(jiti@2.6.1)):
dependencies:
'@babel/helper-validator-identifier': 7.27.1
- '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.6.0))
+ '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.6.1))
'@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.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
esquery: 1.6.0
find-up-simple: 1.0.1
globals: 16.3.0
@@ -14160,40 +14302,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.35.0(jiti@2.6.0))(typescript@5.8.3))(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3))(eslint@9.35.0(jiti@2.6.0)):
+ eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.35.0(jiti@2.6.1)):
dependencies:
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
optionalDependencies:
- '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3))(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
- eslint-plugin-vue@10.3.0(@typescript-eslint/parser@8.38.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3))(eslint@9.35.0(jiti@2.6.0))(vue-eslint-parser@10.2.0(eslint@9.35.0(jiti@2.6.0))):
+ eslint-plugin-vue@10.3.0(@typescript-eslint/parser@8.38.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.35.0(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.35.0(jiti@2.6.1))):
dependencies:
- '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.6.0))
- eslint: 9.35.0(jiti@2.6.0)
+ '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.6.1))
+ eslint: 9.35.0(jiti@2.6.1)
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.35.0(jiti@2.6.0))
+ vue-eslint-parser: 10.2.0(eslint@9.35.0(jiti@2.6.1))
xml-name-validator: 4.0.0
optionalDependencies:
- '@typescript-eslint/parser': 8.38.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
+ '@typescript-eslint/parser': 8.38.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
- eslint-plugin-yml@1.18.0(eslint@9.35.0(jiti@2.6.0)):
+ eslint-plugin-yml@1.18.0(eslint@9.35.0(jiti@2.6.1)):
dependencies:
debug: 4.4.1
escape-string-regexp: 4.0.0
- eslint: 9.35.0(jiti@2.6.0)
- eslint-compat-utils: 0.6.5(eslint@9.35.0(jiti@2.6.0))
+ eslint: 9.35.0(jiti@2.6.1)
+ eslint-compat-utils: 0.6.5(eslint@9.35.0(jiti@2.6.1))
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.35.0(jiti@2.6.0)):
+ eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.17)(eslint@9.35.0(jiti@2.6.1)):
dependencies:
'@vue/compiler-sfc': 3.5.17
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
eslint-scope@5.1.1:
dependencies:
@@ -14209,9 +14351,9 @@ snapshots:
eslint-visitor-keys@4.2.1: {}
- eslint@9.35.0(jiti@2.6.0):
+ eslint@9.35.0(jiti@2.6.1):
dependencies:
- '@eslint-community/eslint-utils': 4.9.0(eslint@9.35.0(jiti@2.6.0))
+ '@eslint-community/eslint-utils': 4.9.0(eslint@9.35.0(jiti@2.6.1))
'@eslint-community/regexpp': 4.12.1
'@eslint/config-array': 0.21.0
'@eslint/config-helpers': 0.3.1
@@ -14247,7 +14389,7 @@ snapshots:
natural-compare: 1.4.0
optionator: 0.9.4
optionalDependencies:
- jiti: 2.6.0
+ jiti: 2.6.1
transitivePeerDependencies:
- supports-color
@@ -14645,9 +14787,10 @@ snapshots:
hachure-fill@0.5.2: {}
- happy-dom@17.6.3:
+ happy-dom@20.0.0:
dependencies:
- webidl-conversions: 7.0.0
+ '@types/node': 20.19.20
+ '@types/whatwg-mimetype': 3.0.2
whatwg-mimetype: 3.0.0
has-flag@4.0.0: {}
@@ -15001,10 +15144,10 @@ snapshots:
is-hexadecimal@2.0.1: {}
- is-immutable-type@5.0.1(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3):
+ is-immutable-type@5.0.1(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3):
dependencies:
- '@typescript-eslint/type-utils': 8.37.0(eslint@9.35.0(jiti@2.6.0))(typescript@5.8.3)
- eslint: 9.35.0(jiti@2.6.0)
+ '@typescript-eslint/type-utils': 8.37.0(eslint@9.35.0(jiti@2.6.1))(typescript@5.8.3)
+ eslint: 9.35.0(jiti@2.6.1)
ts-api-utils: 2.1.0(typescript@5.8.3)
ts-declaration-location: 1.0.7(typescript@5.8.3)
typescript: 5.8.3
@@ -15421,7 +15564,7 @@ snapshots:
jiti@1.21.7: {}
- jiti@2.6.0: {}
+ jiti@2.6.1: {}
js-audio-recorder@1.0.7: {}
@@ -15496,22 +15639,22 @@ snapshots:
kleur@3.0.3: {}
- knip@5.64.1(@types/node@18.15.0)(typescript@5.8.3):
+ knip@5.64.3(@types/node@18.15.0)(typescript@5.8.3):
dependencies:
'@nodelib/fs.walk': 1.2.8
'@types/node': 18.15.0
fast-glob: 3.3.3
formatly: 0.3.0
- jiti: 2.6.0
+ jiti: 2.6.1
js-yaml: 4.1.0
minimist: 1.2.8
- oxc-resolver: 11.8.4
+ oxc-resolver: 11.9.0
picocolors: 1.1.1
picomatch: 4.0.3
smol-toml: 1.4.2
strip-json-comments: 5.0.2
typescript: 5.8.3
- zod: 4.1.11
+ zod: 4.1.12
kolorist@1.8.0: {}
@@ -15545,9 +15688,9 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
- lexical@0.30.0: {}
+ lexical@0.36.2: {}
- lexical@0.35.0: {}
+ lexical@0.37.0: {}
lib0@0.2.114:
dependencies:
@@ -16331,8 +16474,6 @@ snapshots:
nanoid@3.3.11: {}
- napi-postinstall@0.3.0: {}
-
natural-compare@1.4.0: {}
natural-orderby@5.0.0: {}
@@ -16341,12 +16482,12 @@ snapshots:
neo-async@2.6.2: {}
- next-pwa@5.6.0(@babel/core@7.28.3)(@types/babel__core@7.20.5)(esbuild@0.25.0)(next@15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1))(uglify-js@3.19.3)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3)):
+ next-pwa@5.6.0(@babel/core@7.28.3)(@types/babel__core@7.20.5)(esbuild@0.25.0)(next@15.5.4(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1))(uglify-js@3.19.3)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3)):
dependencies:
babel-loader: 8.4.1(@babel/core@7.28.3)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
clean-webpack-plugin: 4.0.0(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
globby: 11.1.0
- next: 15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1)
+ next: 15.5.4(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1)
terser-webpack-plugin: 5.3.14(esbuild@0.25.0)(uglify-js@3.19.3)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
workbox-webpack-plugin: 6.6.0(@types/babel__core@7.20.5)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
workbox-window: 6.6.0
@@ -16364,26 +16505,26 @@ snapshots:
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
- next@15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1):
+ next@15.5.4(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1):
dependencies:
- '@next/env': 15.5.0
+ '@next/env': 15.5.4
'@swc/helpers': 0.5.15
- caniuse-lite: 1.0.30001727
+ caniuse-lite: 1.0.30001746
postcss: 8.4.31
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
styled-jsx: 5.1.6(@babel/core@7.28.3)(react@19.1.1)
optionalDependencies:
- '@next/swc-darwin-arm64': 15.5.0
- '@next/swc-darwin-x64': 15.5.0
- '@next/swc-linux-arm64-gnu': 15.5.0
- '@next/swc-linux-arm64-musl': 15.5.0
- '@next/swc-linux-x64-gnu': 15.5.0
- '@next/swc-linux-x64-musl': 15.5.0
- '@next/swc-win32-arm64-msvc': 15.5.0
- '@next/swc-win32-x64-msvc': 15.5.0
+ '@next/swc-darwin-arm64': 15.5.4
+ '@next/swc-darwin-x64': 15.5.4
+ '@next/swc-linux-arm64-gnu': 15.5.4
+ '@next/swc-linux-arm64-musl': 15.5.4
+ '@next/swc-linux-x64-gnu': 15.5.4
+ '@next/swc-linux-x64-musl': 15.5.4
+ '@next/swc-win32-arm64-msvc': 15.5.4
+ '@next/swc-win32-x64-msvc': 15.5.4
sass: 1.92.1
- sharp: 0.34.3
+ sharp: 0.34.4
transitivePeerDependencies:
- '@babel/core'
- babel-plugin-macros
@@ -16510,29 +16651,27 @@ snapshots:
os-browserify@0.3.0: {}
- oxc-resolver@11.8.4:
- dependencies:
- napi-postinstall: 0.3.0
+ oxc-resolver@11.9.0:
optionalDependencies:
- '@oxc-resolver/binding-android-arm-eabi': 11.8.4
- '@oxc-resolver/binding-android-arm64': 11.8.4
- '@oxc-resolver/binding-darwin-arm64': 11.8.4
- '@oxc-resolver/binding-darwin-x64': 11.8.4
- '@oxc-resolver/binding-freebsd-x64': 11.8.4
- '@oxc-resolver/binding-linux-arm-gnueabihf': 11.8.4
- '@oxc-resolver/binding-linux-arm-musleabihf': 11.8.4
- '@oxc-resolver/binding-linux-arm64-gnu': 11.8.4
- '@oxc-resolver/binding-linux-arm64-musl': 11.8.4
- '@oxc-resolver/binding-linux-ppc64-gnu': 11.8.4
- '@oxc-resolver/binding-linux-riscv64-gnu': 11.8.4
- '@oxc-resolver/binding-linux-riscv64-musl': 11.8.4
- '@oxc-resolver/binding-linux-s390x-gnu': 11.8.4
- '@oxc-resolver/binding-linux-x64-gnu': 11.8.4
- '@oxc-resolver/binding-linux-x64-musl': 11.8.4
- '@oxc-resolver/binding-wasm32-wasi': 11.8.4
- '@oxc-resolver/binding-win32-arm64-msvc': 11.8.4
- '@oxc-resolver/binding-win32-ia32-msvc': 11.8.4
- '@oxc-resolver/binding-win32-x64-msvc': 11.8.4
+ '@oxc-resolver/binding-android-arm-eabi': 11.9.0
+ '@oxc-resolver/binding-android-arm64': 11.9.0
+ '@oxc-resolver/binding-darwin-arm64': 11.9.0
+ '@oxc-resolver/binding-darwin-x64': 11.9.0
+ '@oxc-resolver/binding-freebsd-x64': 11.9.0
+ '@oxc-resolver/binding-linux-arm-gnueabihf': 11.9.0
+ '@oxc-resolver/binding-linux-arm-musleabihf': 11.9.0
+ '@oxc-resolver/binding-linux-arm64-gnu': 11.9.0
+ '@oxc-resolver/binding-linux-arm64-musl': 11.9.0
+ '@oxc-resolver/binding-linux-ppc64-gnu': 11.9.0
+ '@oxc-resolver/binding-linux-riscv64-gnu': 11.9.0
+ '@oxc-resolver/binding-linux-riscv64-musl': 11.9.0
+ '@oxc-resolver/binding-linux-s390x-gnu': 11.9.0
+ '@oxc-resolver/binding-linux-x64-gnu': 11.9.0
+ '@oxc-resolver/binding-linux-x64-musl': 11.9.0
+ '@oxc-resolver/binding-wasm32-wasi': 11.9.0
+ '@oxc-resolver/binding-win32-arm64-msvc': 11.9.0
+ '@oxc-resolver/binding-win32-ia32-msvc': 11.9.0
+ '@oxc-resolver/binding-win32-x64-msvc': 11.9.0
p-cancelable@2.1.1: {}
@@ -16997,16 +17136,16 @@ snapshots:
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
- react-easy-crop@5.5.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
+ react-easy-crop@5.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
normalize-wheel: 1.0.1
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
tslib: 2.8.1
- react-error-boundary@3.1.4(react@19.1.1):
+ react-error-boundary@6.0.0(react@19.1.1):
dependencies:
- '@babel/runtime': 7.27.6
+ '@babel/runtime': 7.28.4
react: 19.1.1
react-fast-compare@3.2.2: {}
@@ -17568,34 +17707,34 @@ snapshots:
'@img/sharp-win32-ia32': 0.33.5
'@img/sharp-win32-x64': 0.33.5
- sharp@0.34.3:
+ sharp@0.34.4:
dependencies:
- color: 4.2.3
- detect-libc: 2.1.0
+ '@img/colour': 1.0.0
+ detect-libc: 2.1.1
semver: 7.7.2
optionalDependencies:
- '@img/sharp-darwin-arm64': 0.34.3
- '@img/sharp-darwin-x64': 0.34.3
- '@img/sharp-libvips-darwin-arm64': 1.2.0
- '@img/sharp-libvips-darwin-x64': 1.2.0
- '@img/sharp-libvips-linux-arm': 1.2.0
- '@img/sharp-libvips-linux-arm64': 1.2.0
- '@img/sharp-libvips-linux-ppc64': 1.2.0
- '@img/sharp-libvips-linux-s390x': 1.2.0
- '@img/sharp-libvips-linux-x64': 1.2.0
- '@img/sharp-libvips-linuxmusl-arm64': 1.2.0
- '@img/sharp-libvips-linuxmusl-x64': 1.2.0
- '@img/sharp-linux-arm': 0.34.3
- '@img/sharp-linux-arm64': 0.34.3
- '@img/sharp-linux-ppc64': 0.34.3
- '@img/sharp-linux-s390x': 0.34.3
- '@img/sharp-linux-x64': 0.34.3
- '@img/sharp-linuxmusl-arm64': 0.34.3
- '@img/sharp-linuxmusl-x64': 0.34.3
- '@img/sharp-wasm32': 0.34.3
- '@img/sharp-win32-arm64': 0.34.3
- '@img/sharp-win32-ia32': 0.34.3
- '@img/sharp-win32-x64': 0.34.3
+ '@img/sharp-darwin-arm64': 0.34.4
+ '@img/sharp-darwin-x64': 0.34.4
+ '@img/sharp-libvips-darwin-arm64': 1.2.3
+ '@img/sharp-libvips-darwin-x64': 1.2.3
+ '@img/sharp-libvips-linux-arm': 1.2.3
+ '@img/sharp-libvips-linux-arm64': 1.2.3
+ '@img/sharp-libvips-linux-ppc64': 1.2.3
+ '@img/sharp-libvips-linux-s390x': 1.2.3
+ '@img/sharp-libvips-linux-x64': 1.2.3
+ '@img/sharp-libvips-linuxmusl-arm64': 1.2.3
+ '@img/sharp-libvips-linuxmusl-x64': 1.2.3
+ '@img/sharp-linux-arm': 0.34.4
+ '@img/sharp-linux-arm64': 0.34.4
+ '@img/sharp-linux-ppc64': 0.34.4
+ '@img/sharp-linux-s390x': 0.34.4
+ '@img/sharp-linux-x64': 0.34.4
+ '@img/sharp-linuxmusl-arm64': 0.34.4
+ '@img/sharp-linuxmusl-x64': 0.34.4
+ '@img/sharp-wasm32': 0.34.4
+ '@img/sharp-win32-arm64': 0.34.4
+ '@img/sharp-win32-ia32': 0.34.4
+ '@img/sharp-win32-x64': 0.34.4
optional: true
shebang-command@2.0.0:
@@ -17666,6 +17805,8 @@ snapshots:
source-map@0.7.4: {}
+ source-map@0.7.6: {}
+
source-map@0.8.0-beta.0:
dependencies:
whatwg-url: 7.1.0
@@ -18061,6 +18202,8 @@ snapshots:
uglify-js@3.19.3: {}
+ undici-types@6.21.0: {}
+
unicode-canonical-property-names-ecmascript@2.0.1: {}
unicode-match-property-ecmascript@2.0.0:
@@ -18257,10 +18400,10 @@ snapshots:
vscode-uri@3.0.8: {}
- vue-eslint-parser@10.2.0(eslint@9.35.0(jiti@2.6.0)):
+ vue-eslint-parser@10.2.0(eslint@9.35.0(jiti@2.6.1)):
dependencies:
debug: 4.4.1
- eslint: 9.35.0(jiti@2.6.0)
+ eslint: 9.35.0(jiti@2.6.1)
eslint-scope: 8.4.0
eslint-visitor-keys: 4.2.1
espree: 10.4.0
@@ -18287,8 +18430,6 @@ snapshots:
webidl-conversions@4.0.2: {}
- webidl-conversions@7.0.0: {}
-
webpack-bundle-analyzer@4.10.1:
dependencies:
'@discoveryjs/json-ext': 0.5.7
@@ -18589,9 +18730,7 @@ snapshots:
zod@3.25.76: {}
- zod@4.0.5: {}
-
- zod@4.1.11: {}
+ zod@4.1.12: {}
zrender@5.6.1:
dependencies:
diff --git a/web/service/base.ts b/web/service/base.ts
index 526c8d75d2..358f54183b 100644
--- a/web/service/base.ts
+++ b/web/service/base.ts
@@ -180,9 +180,9 @@ const handleStream = (
let isFirstMessage = true
function read() {
let hasError = false
- reader?.read().then((result: any) => {
+ reader?.read().then((result: ReadableStreamReadResult) => {
if (result.done) {
- onCompleted && onCompleted()
+ onCompleted?.()
return
}
buffer += decoder.decode(result.value, { stream: true })
@@ -322,7 +322,21 @@ const handleStream = (
const baseFetch = base
-export const upload = async (options: any, isPublicAPI?: boolean, url?: string, searchParams?: string): Promise => {
+type UploadOptions = {
+ xhr: XMLHttpRequest
+ method: string
+ url?: string
+ headers?: Record
+ data: FormData
+ onprogress?: (this: XMLHttpRequest, ev: ProgressEvent) => void
+}
+
+type UploadResponse = {
+ id: string
+ [key: string]: unknown
+}
+
+export const upload = async (options: UploadOptions, isPublicAPI?: boolean, url?: string, searchParams?: string): Promise => {
const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX
const token = await getAccessToken(isPublicAPI)
const defaultOptions = {
@@ -331,18 +345,18 @@ export const upload = async (options: any, isPublicAPI?: boolean, url?: string,
headers: {
Authorization: `Bearer ${token}`,
},
- data: {},
}
- options = {
+ const mergedOptions = {
...defaultOptions,
...options,
- headers: { ...defaultOptions.headers, ...options.headers },
+ url: options.url || defaultOptions.url,
+ headers: { ...defaultOptions.headers, ...options.headers } as Record,
}
return new Promise((resolve, reject) => {
- const xhr = options.xhr
- xhr.open(options.method, options.url)
- for (const key in options.headers)
- xhr.setRequestHeader(key, options.headers[key])
+ const xhr = mergedOptions.xhr
+ xhr.open(mergedOptions.method, mergedOptions.url)
+ for (const key in mergedOptions.headers)
+ xhr.setRequestHeader(key, mergedOptions.headers[key])
xhr.withCredentials = true
xhr.responseType = 'json'
@@ -354,8 +368,9 @@ export const upload = async (options: any, isPublicAPI?: boolean, url?: string,
reject(xhr)
}
}
- xhr.upload.onprogress = options.onprogress
- xhr.send(options.data)
+ if (mergedOptions.onprogress)
+ xhr.upload.onprogress = mergedOptions.onprogress
+ xhr.send(mergedOptions.data)
})
}
@@ -432,7 +447,7 @@ export const ssePost = async (
if (!/^[23]\d{2}$/.test(String(res.status))) {
if (res.status === 401) {
if (isPublicAPI) {
- res.json().then((data: any) => {
+ res.json().then((data: { code?: string; message?: string }) => {
if (isPublicAPI) {
if (data.code === 'web_app_access_denied')
requiredWebSSOLogin(data.message, 403)
diff --git a/web/service/knowledge/use-hit-testing.ts b/web/service/knowledge/use-hit-testing.ts
index e69de29bb2..336ce12bb9 100644
--- a/web/service/knowledge/use-hit-testing.ts
+++ b/web/service/knowledge/use-hit-testing.ts
@@ -0,0 +1 @@
+export {}
diff --git a/web/service/share.ts b/web/service/share.ts
index f1e512564b..ab8e0deb4a 100644
--- a/web/service/share.ts
+++ b/web/service/share.ts
@@ -290,8 +290,10 @@ export const fetchAccessToken = async ({ appCode, userId, webAppAccessToken }: {
const headers = new Headers()
headers.append('X-App-Code', appCode)
const params = new URLSearchParams()
- webAppAccessToken && params.append('web_app_access_token', webAppAccessToken)
- userId && params.append('user_id', userId)
+ if (webAppAccessToken)
+ params.append('web_app_access_token', webAppAccessToken)
+ if (userId)
+ params.append('user_id', userId)
const url = `/passport?${params.toString()}`
return get(url, { headers }) as Promise<{ access_token: string }>
}
diff --git a/web/utils/mcp.ts b/web/utils/mcp.ts
new file mode 100644
index 0000000000..dcbb63ee8a
--- /dev/null
+++ b/web/utils/mcp.ts
@@ -0,0 +1,22 @@
+/**
+ * MCP (Model Context Protocol) utility functions
+ */
+
+/**
+ * Determines if the MCP icon should be used based on the icon source
+ * @param src - The icon source, can be a string URL or an object with content and background
+ * @returns true if the MCP icon should be used (when it's an emoji object with 🔗 content)
+ */
+export const shouldUseMcpIcon = (src: any): boolean => {
+ return typeof src === 'object' && src?.content === '🔗'
+}
+
+/**
+ * Checks if an app icon should use the MCP icon
+ * @param iconType - The type of icon ('emoji' | 'image')
+ * @param icon - The icon content (emoji or file ID)
+ * @returns true if the MCP icon should be used
+ */
+export const shouldUseMcpIconForAppIcon = (iconType: string, icon: string): boolean => {
+ return iconType === 'emoji' && icon === '🔗'
+}
diff --git a/web/utils/model-config.ts b/web/utils/model-config.ts
index c940a6969f..3f655ce036 100644
--- a/web/utils/model-config.ts
+++ b/web/utils/model-config.ts
@@ -61,6 +61,17 @@ export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] |
default: content.default,
})
}
+ else if (type === 'boolean') {
+ promptVariables.push({
+ key: content.variable,
+ name: content.label,
+ required: content.required,
+ type: 'checkbox',
+ options: [],
+ hide: content.hide,
+ default: content.default,
+ })
+ }
else if (type === 'select') {
promptVariables.push({
key: content.variable,
diff --git a/web/utils/var.ts b/web/utils/var.ts
index a9849bdc4b..3181d2bbd7 100644
--- a/web/utils/var.ts
+++ b/web/utils/var.ts
@@ -29,7 +29,7 @@ export const getNewVar = (key: string, type: string) => {
}
export const getNewVarInWorkflow = (key: string, type = InputVarType.textInput): InputVar => {
- const { max_length, ...rest } = VAR_ITEM_TEMPLATE_IN_WORKFLOW
+ const { max_length: _maxLength, ...rest } = VAR_ITEM_TEMPLATE_IN_WORKFLOW
if (type !== InputVarType.textInput) {
return {
...rest,
@@ -49,7 +49,7 @@ export const getNewVarInWorkflow = (key: string, type = InputVarType.textInput):
}
}
-export const checkKey = (key: string, canBeEmpty?: boolean, keys?: string[]) => {
+export const checkKey = (key: string, canBeEmpty?: boolean, _keys?: string[]) => {
if (key.length === 0 && !canBeEmpty)
return 'canNoBeEmpty'