diff --git a/api/controllers/common/schema.py b/api/controllers/common/schema.py index a5a3e4ebbd..8d112c203b 100644 --- a/api/controllers/common/schema.py +++ b/api/controllers/common/schema.py @@ -5,8 +5,6 @@ from enum import StrEnum from flask_restx import Namespace from pydantic import BaseModel, TypeAdapter -from controllers.console import console_ns - DEFAULT_REF_TEMPLATE_SWAGGER_2_0 = "#/definitions/{model}" @@ -24,6 +22,9 @@ def register_schema_models(namespace: Namespace, *models: type[BaseModel]) -> No def get_or_create_model(model_name: str, field_def): + # Import lazily to avoid circular imports between console controllers and schema helpers. + from controllers.console import console_ns + existing = console_ns.models.get(model_name) if existing is None: existing = console_ns.model(model_name, field_def) diff --git a/api/controllers/service_api/__init__.py b/api/controllers/service_api/__init__.py index 9032733e2c..67d8b598b0 100644 --- a/api/controllers/service_api/__init__.py +++ b/api/controllers/service_api/__init__.py @@ -34,6 +34,7 @@ from .dataset import ( metadata, segment, ) +from .end_user import end_user from .workspace import models __all__ = [ @@ -44,6 +45,7 @@ __all__ = [ "conversation", "dataset", "document", + "end_user", "file", "file_preview", "hit_testing", diff --git a/api/controllers/service_api/end_user/__init__.py b/api/controllers/service_api/end_user/__init__.py new file mode 100644 index 0000000000..8c54ac2662 --- /dev/null +++ b/api/controllers/service_api/end_user/__init__.py @@ -0,0 +1,3 @@ +from . import end_user + +__all__ = ["end_user"] diff --git a/api/controllers/service_api/end_user/end_user.py b/api/controllers/service_api/end_user/end_user.py new file mode 100644 index 0000000000..fbeb96f446 --- /dev/null +++ b/api/controllers/service_api/end_user/end_user.py @@ -0,0 +1,41 @@ +from uuid import UUID + +from flask_restx import Resource + +from controllers.service_api import service_api_ns +from controllers.service_api.end_user.error import EndUserNotFoundError +from controllers.service_api.wraps import validate_app_token +from fields.end_user_fields import EndUserDetail +from models.model import App +from services.end_user_service import EndUserService + + +@service_api_ns.route("/end-users/") +class EndUserApi(Resource): + """Resource for retrieving end user details by ID.""" + + @service_api_ns.doc("get_end_user") + @service_api_ns.doc(description="Get an end user by ID") + @service_api_ns.doc( + params={"end_user_id": "End user ID"}, + responses={ + 200: "End user retrieved successfully", + 401: "Unauthorized - invalid API token", + 404: "End user not found", + }, + ) + @validate_app_token + def get(self, app_model: App, end_user_id: UUID): + """Get end user detail. + + This endpoint is scoped to the current app token's tenant/app to prevent + cross-tenant/app access when an end-user ID is known. + """ + + end_user = EndUserService.get_end_user_by_id( + tenant_id=app_model.tenant_id, app_id=app_model.id, end_user_id=str(end_user_id) + ) + if end_user is None: + raise EndUserNotFoundError() + + return EndUserDetail.model_validate(end_user).model_dump(mode="json") diff --git a/api/controllers/service_api/end_user/error.py b/api/controllers/service_api/end_user/error.py new file mode 100644 index 0000000000..1a3a133231 --- /dev/null +++ b/api/controllers/service_api/end_user/error.py @@ -0,0 +1,7 @@ +from libs.exception import BaseHTTPException + + +class EndUserNotFoundError(BaseHTTPException): + error_code = "end_user_not_found" + description = "End user not found." + code = 404 diff --git a/api/fields/end_user_fields.py b/api/fields/end_user_fields.py index effe7bfb20..df1980616a 100644 --- a/api/fields/end_user_fields.py +++ b/api/fields/end_user_fields.py @@ -1,7 +1,9 @@ from __future__ import annotations +from datetime import datetime + from flask_restx import fields -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel, ConfigDict, Field simple_end_user_fields = { "id": fields.String, @@ -10,6 +12,19 @@ simple_end_user_fields = { "session_id": fields.String, } +end_user_detail_fields = { + "id": fields.String, + "tenant_id": fields.String, + "app_id": fields.String, + "type": fields.String, + "external_user_id": fields.String, + "name": fields.String, + "is_anonymous": fields.Boolean, + "session_id": fields.String, + "created_at": fields.DateTime, + "updated_at": fields.DateTime, +} + class ResponseModel(BaseModel): model_config = ConfigDict( @@ -26,3 +41,23 @@ class SimpleEndUser(ResponseModel): type: str is_anonymous: bool session_id: str | None = None + + +class EndUserDetail(ResponseModel): + """Full EndUser record for API responses. + + Note: The SQLAlchemy model defines an `is_anonymous` property for Flask-Login semantics + (always False). The database column is exposed as `_is_anonymous`, so this DTO maps + `is_anonymous` from `_is_anonymous` to return the stored value. + """ + + id: str + tenant_id: str + app_id: str | None = None + type: str + external_user_id: str | None = None + name: str | None = None + is_anonymous: bool = Field(validation_alias="_is_anonymous") + session_id: str + created_at: datetime + updated_at: datetime diff --git a/api/services/end_user_service.py b/api/services/end_user_service.py index 81098e95bb..326f46780d 100644 --- a/api/services/end_user_service.py +++ b/api/services/end_user_service.py @@ -16,6 +16,25 @@ class EndUserService: Service for managing end users. """ + @classmethod + def get_end_user_by_id(cls, *, tenant_id: str, app_id: str, end_user_id: str) -> EndUser | None: + """Get an end user by primary key. + + This is scoped to the provided tenant and app to prevent cross-tenant/app access + when an end-user ID is known. + """ + + with Session(db.engine, expire_on_commit=False) as session: + return ( + session.query(EndUser) + .where( + EndUser.id == end_user_id, + EndUser.tenant_id == tenant_id, + EndUser.app_id == app_id, + ) + .first() + ) + @classmethod def get_or_create_end_user(cls, app_model: App, user_id: str | None = None) -> EndUser: """ diff --git a/api/tests/unit_tests/controllers/service_api/end_user/test_end_user.py b/api/tests/unit_tests/controllers/service_api/end_user/test_end_user.py new file mode 100644 index 0000000000..3cc444e467 --- /dev/null +++ b/api/tests/unit_tests/controllers/service_api/end_user/test_end_user.py @@ -0,0 +1,61 @@ +from datetime import UTC, datetime +from unittest.mock import Mock +from uuid import UUID, uuid4 + +import pytest + +from controllers.service_api.end_user.end_user import EndUserApi +from controllers.service_api.end_user.error import EndUserNotFoundError +from models.model import App, EndUser + + +class TestEndUserApi: + @pytest.fixture + def resource(self) -> EndUserApi: + return EndUserApi() + + @pytest.fixture + def app_model(self) -> App: + app = Mock(spec=App) + app.id = str(uuid4()) + app.tenant_id = str(uuid4()) + return app + + def test_get_end_user_returns_all_attributes(self, mocker, resource: EndUserApi, app_model: App) -> None: + end_user = Mock(spec=EndUser) + end_user.id = str(uuid4()) + end_user.tenant_id = app_model.tenant_id + end_user.app_id = app_model.id + end_user.type = "service_api" + end_user.external_user_id = "external-123" + end_user.name = "Alice" + end_user._is_anonymous = True + end_user.session_id = "session-xyz" + end_user.created_at = datetime(2024, 1, 1, tzinfo=UTC) + end_user.updated_at = datetime(2024, 1, 2, tzinfo=UTC) + + get_end_user_by_id = mocker.patch( + "controllers.service_api.end_user.end_user.EndUserService.get_end_user_by_id", return_value=end_user + ) + + result = EndUserApi.get.__wrapped__(resource, app_model=app_model, end_user_id=UUID(end_user.id)) + + get_end_user_by_id.assert_called_once_with( + tenant_id=app_model.tenant_id, app_id=app_model.id, end_user_id=end_user.id + ) + assert result["id"] == end_user.id + assert result["tenant_id"] == end_user.tenant_id + assert result["app_id"] == end_user.app_id + assert result["type"] == end_user.type + assert result["external_user_id"] == end_user.external_user_id + assert result["name"] == end_user.name + assert result["is_anonymous"] is True + assert result["session_id"] == end_user.session_id + assert result["created_at"].startswith("2024-01-01T00:00:00") + assert result["updated_at"].startswith("2024-01-02T00:00:00") + + def test_get_end_user_not_found(self, mocker, resource: EndUserApi, app_model: App) -> None: + mocker.patch("controllers.service_api.end_user.end_user.EndUserService.get_end_user_by_id", return_value=None) + + with pytest.raises(EndUserNotFoundError): + EndUserApi.get.__wrapped__(resource, app_model=app_model, end_user_id=uuid4()) diff --git a/api/tests/unit_tests/services/test_end_user_service.py b/api/tests/unit_tests/services/test_end_user_service.py index 3575743a92..0f8ba43624 100644 --- a/api/tests/unit_tests/services/test_end_user_service.py +++ b/api/tests/unit_tests/services/test_end_user_service.py @@ -492,3 +492,45 @@ class TestEndUserServiceGetOrCreateEndUserByType: # Assert added_user = mock_session.add.call_args[0][0] assert added_user.type == invoke_type + + +class TestEndUserServiceGetEndUserById: + """Unit tests for EndUserService.get_end_user_by_id.""" + + @patch("services.end_user_service.Session") + @patch("services.end_user_service.db") + def test_get_end_user_by_id_returns_end_user(self, mock_db, mock_session_class): + tenant_id = "tenant-123" + app_id = "app-456" + end_user_id = "end-user-789" + existing_user = MagicMock(spec=EndUser) + + mock_session = MagicMock() + mock_session_class.return_value.__enter__.return_value = mock_session + + mock_query = MagicMock() + mock_session.query.return_value = mock_query + mock_query.where.return_value = mock_query + mock_query.first.return_value = existing_user + + result = EndUserService.get_end_user_by_id(tenant_id=tenant_id, app_id=app_id, end_user_id=end_user_id) + + assert result == existing_user + mock_session.query.assert_called_once_with(EndUser) + mock_query.where.assert_called_once() + assert len(mock_query.where.call_args[0]) == 3 + + @patch("services.end_user_service.Session") + @patch("services.end_user_service.db") + def test_get_end_user_by_id_returns_none(self, mock_db, mock_session_class): + mock_session = MagicMock() + mock_session_class.return_value.__enter__.return_value = mock_session + + mock_query = MagicMock() + mock_session.query.return_value = mock_query + mock_query.where.return_value = mock_query + mock_query.first.return_value = None + + result = EndUserService.get_end_user_by_id(tenant_id="tenant", app_id="app", end_user_id="end-user") + + assert result is None diff --git a/web/app/components/datasets/metadata/base/date-picker.tsx b/web/app/components/datasets/metadata/base/date-picker.tsx index 2f61549859..1d1d532bde 100644 --- a/web/app/components/datasets/metadata/base/date-picker.tsx +++ b/web/app/components/datasets/metadata/base/date-picker.tsx @@ -7,6 +7,7 @@ import dayjs from 'dayjs' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import DatePicker from '@/app/components/base/date-and-time-picker/date-picker' +import { useAppContext } from '@/context/app-context' import useTimestamp from '@/hooks/use-timestamp' import { cn } from '@/utils/classnames' @@ -21,7 +22,7 @@ const WrappedDatePicker = ({ onChange, }: Props) => { const { t } = useTranslation() - // const { userProfile: { timezone } } = useAppContext() + const { userProfile: { timezone } } = useAppContext() const { formatTime: formatTimestamp } = useTimestamp() const handleDateChange = useCallback((date?: dayjs.Dayjs) => { @@ -64,6 +65,7 @@ const WrappedDatePicker = ({ return ( --- + + + + Retrieve an end user by ID. + + This is useful when other APIs return an end-user ID (e.g. `created_by` from File Upload). + + ### Path Parameters + - `end_user_id` (uuid) Required + End user ID. + + ### Response + Returns an EndUser object. + - `id` (uuid) ID + - `tenant_id` (uuid) Tenant ID + - `app_id` (uuid) App ID + - `type` (string) End user type + - `external_user_id` (string) External user ID + - `name` (string) Name + - `is_anonymous` (boolean) Whether anonymous + - `session_id` (string) Session ID + - `created_at` (string) ISO 8601 datetime + - `updated_at` (string) ISO 8601 datetime + + ### Errors + - 404, `end_user_not_found`, end user not found + - 500, internal server error + + + + ### Request Example + + + ### Response Example + + ```json {{ title: 'Response' }} + { + "id": "6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13", + "tenant_id": "8c0f3f3a-66b0-4b55-a0bf-8b8e0d6aee7d", + "app_id": "6c8c3f41-2c6f-4e1b-8f4f-7f11c8f2ad2a", + "type": "service_api", + "external_user_id": "abc-123", + "name": "Alice", + "is_anonymous": false, + "session_id": "abc-123", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } + ``` + + + +--- + --- + + + + エンドユーザー ID からエンドユーザー情報を取得します。 + + 他の API がエンドユーザー ID(例:ファイルアップロードの `created_by`)を返す場合に利用できます。 + + ### パスパラメータ + - `end_user_id` (uuid) 必須 + エンドユーザー ID。 + + ### レスポンス + EndUser オブジェクトを返します。 + - `id` (uuid) ID + - `tenant_id` (uuid) テナント ID + - `app_id` (uuid) アプリ ID + - `type` (string) エンドユーザー種別 + - `external_user_id` (string) 外部ユーザー ID + - `name` (string) 名前 + - `is_anonymous` (boolean) 匿名ユーザーかどうか + - `session_id` (string) セッション ID + - `created_at` (string) ISO 8601 日時 + - `updated_at` (string) ISO 8601 日時 + + ### エラー + - 404, `end_user_not_found`, エンドユーザーが見つかりません + - 500, 内部サーバーエラー + + + + ### リクエスト例 + + + ### レスポンス例 + + ```json {{ title: 'Response' }} + { + "id": "6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13", + "tenant_id": "8c0f3f3a-66b0-4b55-a0bf-8b8e0d6aee7d", + "app_id": "6c8c3f41-2c6f-4e1b-8f4f-7f11c8f2ad2a", + "type": "service_api", + "external_user_id": "abc-123", + "name": "Alice", + "is_anonymous": false, + "session_id": "abc-123", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } + ``` + + + +--- + --- + + + + 通过终端用户 ID 获取终端用户信息。 + + 当其他 API 返回终端用户 ID(例如:上传文件接口返回的 `created_by`)时,可使用该接口查询对应的终端用户信息。 + + ### 路径参数 + - `end_user_id` (uuid) 必需 + 终端用户 ID。 + + ### Response + 返回 EndUser 对象。 + - `id` (uuid) ID + - `tenant_id` (uuid) 工作空间(Tenant)ID + - `app_id` (uuid) 应用 ID + - `type` (string) 终端用户类型 + - `external_user_id` (string) 外部用户 ID + - `name` (string) 名称 + - `is_anonymous` (boolean) 是否匿名 + - `session_id` (string) 会话 ID + - `created_at` (string) ISO 8601 时间 + - `updated_at` (string) ISO 8601 时间 + + ### Errors + - 404,`end_user_not_found`,终端用户不存在 + - 500,内部服务器错误 + + + + + + + ```json {{ title: 'Response' }} + { + "id": "6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13", + "tenant_id": "8c0f3f3a-66b0-4b55-a0bf-8b8e0d6aee7d", + "app_id": "6c8c3f41-2c6f-4e1b-8f4f-7f11c8f2ad2a", + "type": "service_api", + "external_user_id": "abc-123", + "name": "Alice", + "is_anonymous": false, + "session_id": "abc-123", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } + ``` + + + +--- + --- + + + + Retrieve an end user by ID. + + This is useful when other APIs return an end-user ID (e.g. `created_by` from File Upload). + + ### Path Parameters + - `end_user_id` (uuid) Required + End user ID. + + ### Response + Returns an EndUser object. + - `id` (uuid) ID + - `tenant_id` (uuid) Tenant ID + - `app_id` (uuid) App ID + - `type` (string) End user type + - `external_user_id` (string) External user ID + - `name` (string) Name + - `is_anonymous` (boolean) Whether anonymous + - `session_id` (string) Session ID + - `created_at` (string) ISO 8601 datetime + - `updated_at` (string) ISO 8601 datetime + + ### Errors + - 404, `end_user_not_found`, end user not found + - 500, internal server error + + + + ### Request Example + + + ### Response Example + + ```json {{ title: 'Response' }} + { + "id": "6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13", + "tenant_id": "8c0f3f3a-66b0-4b55-a0bf-8b8e0d6aee7d", + "app_id": "6c8c3f41-2c6f-4e1b-8f4f-7f11c8f2ad2a", + "type": "service_api", + "external_user_id": "abc-123", + "name": "Alice", + "is_anonymous": false, + "session_id": "abc-123", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } + ``` + + + +--- + --- + + + + エンドユーザー ID からエンドユーザー情報を取得します。 + + 他の API がエンドユーザー ID(例:ファイルアップロードの `created_by`)を返す場合に利用できます。 + + ### パスパラメータ + - `end_user_id` (uuid) 必須 + エンドユーザー ID。 + + ### レスポンス + EndUser オブジェクトを返します。 + - `id` (uuid) ID + - `tenant_id` (uuid) テナント ID + - `app_id` (uuid) アプリ ID + - `type` (string) エンドユーザー種別 + - `external_user_id` (string) 外部ユーザー ID + - `name` (string) 名前 + - `is_anonymous` (boolean) 匿名ユーザーかどうか + - `session_id` (string) セッション ID + - `created_at` (string) ISO 8601 日時 + - `updated_at` (string) ISO 8601 日時 + + ### エラー + - 404, `end_user_not_found`, エンドユーザーが見つかりません + - 500, 内部サーバーエラー + + + + ### リクエスト例 + + + ### レスポンス例 + + ```json {{ title: 'Response' }} + { + "id": "6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13", + "tenant_id": "8c0f3f3a-66b0-4b55-a0bf-8b8e0d6aee7d", + "app_id": "6c8c3f41-2c6f-4e1b-8f4f-7f11c8f2ad2a", + "type": "service_api", + "external_user_id": "abc-123", + "name": "Alice", + "is_anonymous": false, + "session_id": "abc-123", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } + ``` + + + +--- + --- + + + + 通过终端用户 ID 获取终端用户信息。 + + 当其他 API 返回终端用户 ID(例如:上传文件接口返回的 `created_by`)时,可使用该接口查询对应的终端用户信息。 + + ### 路径参数 + - `end_user_id` (uuid) 必需 + 终端用户 ID。 + + ### Response + 返回 EndUser 对象。 + - `id` (uuid) ID + - `tenant_id` (uuid) 工作空间(Tenant)ID + - `app_id` (uuid) 应用 ID + - `type` (string) 终端用户类型 + - `external_user_id` (string) 外部用户 ID + - `name` (string) 名称 + - `is_anonymous` (boolean) 是否匿名 + - `session_id` (string) 会话 ID + - `created_at` (string) ISO 8601 时间 + - `updated_at` (string) ISO 8601 时间 + + ### Errors + - 404,`end_user_not_found`,终端用户不存在 + - 500,内部服务器错误 + + + + + + + ```json {{ title: 'Response' }} + { + "id": "6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13", + "tenant_id": "8c0f3f3a-66b0-4b55-a0bf-8b8e0d6aee7d", + "app_id": "6c8c3f41-2c6f-4e1b-8f4f-7f11c8f2ad2a", + "type": "service_api", + "external_user_id": "abc-123", + "name": "Alice", + "is_anonymous": false, + "session_id": "abc-123", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } + ``` + + + +--- + --- + + + + Retrieve an end user by ID. + + This is useful when other APIs return an end-user ID (e.g. `created_by` from File Upload). + + ### Path Parameters + - `end_user_id` (uuid) Required + End user ID. + + ### Response + Returns an EndUser object. + - `id` (uuid) ID + - `tenant_id` (uuid) Tenant ID + - `app_id` (uuid) App ID + - `type` (string) End user type + - `external_user_id` (string) External user ID + - `name` (string) Name + - `is_anonymous` (boolean) Whether anonymous + - `session_id` (string) Session ID + - `created_at` (string) ISO 8601 datetime + - `updated_at` (string) ISO 8601 datetime + + ### Errors + - 404, `end_user_not_found`, end user not found + - 500, internal server error + + + + ### Request Example + + + ### Response Example + + ```json {{ title: 'Response' }} + { + "id": "6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13", + "tenant_id": "8c0f3f3a-66b0-4b55-a0bf-8b8e0d6aee7d", + "app_id": "6c8c3f41-2c6f-4e1b-8f4f-7f11c8f2ad2a", + "type": "service_api", + "external_user_id": "abc-123", + "name": "Alice", + "is_anonymous": false, + "session_id": "abc-123", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } + ``` + + + +--- + --- + + + + エンドユーザー ID からエンドユーザー情報を取得します。 + + 他の API がエンドユーザー ID(例:ファイルアップロードの `created_by`)を返す場合に利用できます。 + + ### パスパラメータ + - `end_user_id` (uuid) 必須 + エンドユーザー ID。 + + ### レスポンス + EndUser オブジェクトを返します。 + - `id` (uuid) ID + - `tenant_id` (uuid) テナント ID + - `app_id` (uuid) アプリ ID + - `type` (string) エンドユーザー種別 + - `external_user_id` (string) 外部ユーザー ID + - `name` (string) 名前 + - `is_anonymous` (boolean) 匿名ユーザーかどうか + - `session_id` (string) セッション ID + - `created_at` (string) ISO 8601 日時 + - `updated_at` (string) ISO 8601 日時 + + ### エラー + - 404, `end_user_not_found`, エンドユーザーが見つかりません + - 500, 内部サーバーエラー + + + + ### リクエスト例 + + + ### レスポンス例 + + ```json {{ title: 'Response' }} + { + "id": "6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13", + "tenant_id": "8c0f3f3a-66b0-4b55-a0bf-8b8e0d6aee7d", + "app_id": "6c8c3f41-2c6f-4e1b-8f4f-7f11c8f2ad2a", + "type": "service_api", + "external_user_id": "abc-123", + "name": "Alice", + "is_anonymous": false, + "session_id": "abc-123", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } + ``` + + + +--- + --- + + + + 通过终端用户 ID 获取终端用户信息。 + + 当其他 API 返回终端用户 ID(例如:上传文件接口返回的 `created_by`)时,可使用该接口查询对应的终端用户信息。 + + ### 路径参数 + - `end_user_id` (uuid) 必需 + 终端用户 ID。 + + ### Response + 返回 EndUser 对象。 + - `id` (uuid) ID + - `tenant_id` (uuid) 工作空间(Tenant)ID + - `app_id` (uuid) 应用 ID + - `type` (string) 终端用户类型 + - `external_user_id` (string) 外部用户 ID + - `name` (string) 名称 + - `is_anonymous` (boolean) 是否匿名 + - `session_id` (string) 会话 ID + - `created_at` (string) ISO 8601 时间 + - `updated_at` (string) ISO 8601 时间 + + ### Errors + - 404,`end_user_not_found`,终端用户不存在 + - 500,内部服务器错误 + + + + + + + ```json {{ title: 'Response' }} + { + "id": "6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13", + "tenant_id": "8c0f3f3a-66b0-4b55-a0bf-8b8e0d6aee7d", + "app_id": "6c8c3f41-2c6f-4e1b-8f4f-7f11c8f2ad2a", + "type": "service_api", + "external_user_id": "abc-123", + "name": "Alice", + "is_anonymous": false, + "session_id": "abc-123", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } + ``` + + + +--- + + + + Retrieve an end user by ID. + + This is useful when other APIs return an end-user ID (e.g. `created_by` from File Upload). + + ### Path Parameters + - `end_user_id` (uuid) Required + End user ID. + + ### Response + Returns an EndUser object. + - `id` (uuid) ID + - `tenant_id` (uuid) Tenant ID + - `app_id` (uuid) App ID + - `type` (string) End user type + - `external_user_id` (string) External user ID + - `name` (string) Name + - `is_anonymous` (boolean) Whether anonymous + - `session_id` (string) Session ID + - `created_at` (string) ISO 8601 datetime + - `updated_at` (string) ISO 8601 datetime + + ### Errors + - 404, `end_user_not_found`, end user not found + - 500, internal server error + + + + ### Request Example + + + ### Response Example + + ```json {{ title: 'Response' }} + { + "id": "6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13", + "tenant_id": "8c0f3f3a-66b0-4b55-a0bf-8b8e0d6aee7d", + "app_id": "6c8c3f41-2c6f-4e1b-8f4f-7f11c8f2ad2a", + "type": "service_api", + "external_user_id": "abc-123", + "name": "Alice", + "is_anonymous": false, + "session_id": "abc-123", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } + ``` + + + +--- + + + + エンドユーザー ID からエンドユーザー情報を取得します。 + + 他の API がエンドユーザー ID(例:ファイルアップロードの `created_by`)を返す場合に利用できます。 + + ### パスパラメータ + - `end_user_id` (uuid) 必須 + エンドユーザー ID。 + + ### レスポンス + EndUser オブジェクトを返します。 + - `id` (uuid) ID + - `tenant_id` (uuid) テナント ID + - `app_id` (uuid) アプリ ID + - `type` (string) エンドユーザー種別 + - `external_user_id` (string) 外部ユーザー ID + - `name` (string) 名前 + - `is_anonymous` (boolean) 匿名ユーザーかどうか + - `session_id` (string) セッション ID + - `created_at` (string) ISO 8601 日時 + - `updated_at` (string) ISO 8601 日時 + + ### エラー + - 404, `end_user_not_found`, エンドユーザーが見つかりません + - 500, 内部サーバーエラー + + + + ### リクエスト例 + + + ### レスポンス例 + + ```json {{ title: 'Response' }} + { + "id": "6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13", + "tenant_id": "8c0f3f3a-66b0-4b55-a0bf-8b8e0d6aee7d", + "app_id": "6c8c3f41-2c6f-4e1b-8f4f-7f11c8f2ad2a", + "type": "service_api", + "external_user_id": "abc-123", + "name": "Alice", + "is_anonymous": false, + "session_id": "abc-123", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } + ``` + + + +--- + ___ - diff --git a/web/app/components/develop/template/template_workflow.zh.mdx b/web/app/components/develop/template/template_workflow.zh.mdx index 32ad342c71..30858ec6e6 100644 --- a/web/app/components/develop/template/template_workflow.zh.mdx +++ b/web/app/components/develop/template/template_workflow.zh.mdx @@ -727,6 +727,69 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 --- + + + + 通过终端用户 ID 获取终端用户信息。 + + 当其他 API 返回终端用户 ID(例如:上传文件接口返回的 `created_by`)时,可使用该接口查询对应的终端用户信息。 + + ### 路径参数 + - `end_user_id` (uuid) 必需 + 终端用户 ID。 + + ### Response + 返回 EndUser 对象。 + - `id` (uuid) ID + - `tenant_id` (uuid) 工作空间(Tenant)ID + - `app_id` (uuid) 应用 ID + - `type` (string) 终端用户类型 + - `external_user_id` (string) 外部用户 ID + - `name` (string) 名称 + - `is_anonymous` (boolean) 是否匿名 + - `session_id` (string) 会话 ID + - `created_at` (string) ISO 8601 时间 + - `updated_at` (string) ISO 8601 时间 + + ### Errors + - 404,`end_user_not_found`,终端用户不存在 + - 500,内部服务器错误 + + + + + + + ```json {{ title: 'Response' }} + { + "id": "6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13", + "tenant_id": "8c0f3f3a-66b0-4b55-a0bf-8b8e0d6aee7d", + "app_id": "6c8c3f41-2c6f-4e1b-8f4f-7f11c8f2ad2a", + "type": "service_api", + "external_user_id": "abc-123", + "name": "Alice", + "is_anonymous": false, + "session_id": "abc-123", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } + ``` + + + +--- + = ({ banner, autoplayDelay, isPause className="flex min-w-[480px] max-w-[680px] flex-[1_0_0] flex-col pr-4" style={responsiveStyle} > -

+

{category}

-

+

{title}

diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.spec.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.spec.tsx index 5a70f11202..2c9d0f5002 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.spec.tsx @@ -1138,17 +1138,27 @@ describe('CommonCreateModal', () => { mockVerifyCredentials.mockImplementation((params, { onError }) => { onError(new Error('Raw error')) }) + const builder = createMockSubscriptionBuilder() - render() - - await waitFor(() => { - expect(mockCreateBuilder).toHaveBeenCalled() - }) + render() fireEvent.click(screen.getByTestId('modal-confirm')) await waitFor(() => { - expect(mockParsePluginErrorMessage).toHaveBeenCalled() + expect(mockVerifyCredentials).toHaveBeenCalledWith( + expect.objectContaining({ + provider: 'test-provider', + subscriptionBuilderId: builder.id, + }), + expect.objectContaining({ + onSuccess: expect.any(Function), + onError: expect.any(Function), + }), + ) + }) + + await waitFor(() => { + expect(mockParsePluginErrorMessage).toHaveBeenCalledWith(expect.any(Error)) }) }) diff --git a/web/app/components/tools/workflow-tool/configure-button.spec.tsx b/web/app/components/tools/workflow-tool/configure-button.spec.tsx index 7925c9d454..659aeb4a49 100644 --- a/web/app/components/tools/workflow-tool/configure-button.spec.tsx +++ b/web/app/components/tools/workflow-tool/configure-button.spec.tsx @@ -309,7 +309,7 @@ describe('WorkflowToolConfigureButton', () => { it('should render loading state when published and fetching details', async () => { // Arrange - mockFetchWorkflowToolDetailByAppID.mockImplementation(() => new Promise(() => {})) // Never resolves + mockFetchWorkflowToolDetailByAppID.mockImplementation(() => new Promise(() => { })) // Never resolves const props = createDefaultConfigureButtonProps({ published: true }) // Act @@ -774,7 +774,9 @@ describe('WorkflowToolConfigureButton', () => { }) // Component should still render without crashing - expect(screen.getByText('workflow.common.workflowAsTool')).toBeInTheDocument() + await waitFor(() => { + expect(screen.getByText('workflow.common.workflowAsTool')).toBeInTheDocument() + }) }) it('should handle rapid publish/unpublish state changes', async () => { diff --git a/web/service/workflow.ts b/web/service/workflow.ts index 870f337991..256ded5c8a 100644 --- a/web/service/workflow.ts +++ b/web/service/workflow.ts @@ -14,7 +14,6 @@ import type { import { get, post } from './base' import { consoleClient } from './client' import { getFlowPrefix } from './utils' -import { sanitizeWorkflowDraftPayload } from './workflow-payload' export type { WorkflowDraftFeaturesPayload } from '@/contract/console/workflow' @@ -31,8 +30,7 @@ export const syncWorkflowDraft = ({ url, params, canNotSaveEmpty }: { if (params.graph.nodes.length === 0 && canNotSaveEmpty) { throw new Error('Cannot sync workflow draft with zero nodes.') } - const sanitized = sanitizeWorkflowDraftPayload(params) - return post(url, { body: sanitized }, { silent: true }) + return post(url, { body: params }, { silent: true }) } export const fetchNodesDefaultConfigs = (url: string) => { diff --git a/web/themes/dark.css b/web/themes/dark.css index 186080854a..59c9811412 100644 --- a/web/themes/dark.css +++ b/web/themes/dark.css @@ -101,6 +101,17 @@ html[data-theme="dark"] { --color-components-button-indigo-bg-hover: #6172f3; --color-components-button-indigo-bg-disabled: rgb(255 255 255 / 0.03); + --color-components-button-debug-text: rgb(255 255 255 / 0.95); + --color-components-button-debug-text-disabled: rgb(255 255 255 / 0.2); + --color-components-button-debug-bg: #ff4405; + --color-components-button-debug-bg-hover: #ff692e; + --color-components-button-debug-bg-disabled: rgb(255 68 5 / 0.08); + --color-components-button-debug-border: rgb(255 255 255 / 0.12); + --color-components-button-debug-border-hover: rgb(255 255 255 / 0.2); + --color-components-button-debug-border-disabled: rgb(255 255 255 / 0.08); + + --color-components-button-button-seam: rgb(0 0 0 / 0.15); + --color-components-checkbox-icon: rgb(255 255 255 / 0.95); --color-components-checkbox-icon-disabled: rgb(255 255 255 / 0.2); --color-components-checkbox-bg: #296dff; @@ -161,6 +172,7 @@ html[data-theme="dark"] { --color-components-panel-on-panel-item-bg-destructive-hover-transparent: rgb(255 251 250 / 0); --color-components-panel-bg-transparent: rgb(34 34 37 / 0); + --color-components-panel-bg-blur-burn: rgb(31 31 35 / 0.9); --color-components-main-nav-nav-button-text: rgb(200 206 218 / 0.6); --color-components-main-nav-nav-button-text-active: #f4f4f5; @@ -171,6 +183,26 @@ html[data-theme="dark"] { --color-components-main-nav-nav-user-border: rgb(255 255 255 / 0.05); + --color-components-main-nav-text: #a8a8b3; + --color-components-main-nav-text-active: #ffffff; + --color-components-main-nav-glass-edge-highlight-first: rgb(196 207 255 / 0.15); + --color-components-main-nav-glass-edge-highlight-middle: rgb(72 108 255 / 0); + --color-components-main-nav-glass-edge-highlight-end: rgb(196 207 255 / 0.05); + + --color-components-main-nav-glass-edge-reflection-first: rgb(92 124 255 / 0); + --color-components-main-nav-glass-edge-reflection-middle: rgb(210 219 255 / 0.8); + --color-components-main-nav-glass-edge-reflection-end: rgb(92 124 255 / 0); + + --color-components-main-nav-glass-surface-first: rgb(196 207 255 / 0.08); + --color-components-main-nav-glass-surface-middle-1: rgb(210 219 255 / 0.12); + --color-components-main-nav-glass-surface-middle-2: rgb(210 219 255 / 0.1); + --color-components-main-nav-glass-surface-end: rgb(196 207 255 / 0.08); + + --color-components-main-nav-glass-inner-glow: rgb(210 219 255 / 0.05); + --color-components-main-nav-glass-shadow-reflection: rgb(210 219 255 / 0.04); + --color-components-main-nav-glass-shadow-reflection-glow: rgb(255 255 255 / 0.02); + --color-components-main-nav-glass-text-glow: rgb(245 246 255 / 0.27); + --color-components-slider-knob: #f4f4f5; --color-components-slider-knob-hover: #fefefe; --color-components-slider-knob-disabled: rgb(255 255 255 / 0.2); @@ -369,6 +401,8 @@ html[data-theme="dark"] { --color-components-icon-bg-orange-solid: #f79009; --color-components-icon-bg-orange-soft: rgb(247 144 9 / 0.2); + --color-components-marketplace-header-bg: rgb(31 31 35 / 0.9); + --color-text-primary: #fbfbfc; --color-text-secondary: #d9d9de; --color-text-tertiary: rgb(200 206 218 / 0.6); @@ -430,6 +464,7 @@ html[data-theme="dark"] { --color-background-overlay-backdrop: rgb(24 24 27 / 0.95); --color-background-body-transparent: rgb(29 29 32 / 0); --color-background-section-burn-inverted: #27272b; + --color-background-default-hover-alpha-0: rgb(39 39 43 / 0); --color-shadow-shadow-1: rgb(0 0 0 / 0.05); --color-shadow-shadow-3: rgb(0 0 0 / 0.1); @@ -447,7 +482,7 @@ html[data-theme="dark"] { --color-workflow-block-bg: #27272b; --color-workflow-block-bg-transparent: rgb(39 39 43 / 0.96); --color-workflow-block-border-highlight: rgb(200 206 218 / 0.2); - --color-workflow-block-wrapper-bg-1: #323236; + --color-workflow-block-wrapper-bg-1: #27272b; --color-workflow-block-wrapper-bg-2: rgb(39 39 43 / 0.2); --color-workflow-canvas-workflow-dot-color: rgb(133 133 173 / 0.11); @@ -513,6 +548,18 @@ html[data-theme="dark"] { --color-workflow-workflow-progress-bg-1: rgb(24 24 27 / 0.25); --color-workflow-workflow-progress-bg-2: rgb(24 24 27 / 0.04); + --color-workflow-debug-run-status-bg: rgb(230 46 5 / 0.4); + --color-workflow-debug-breakpoint: #ff692e; + --color-workflow-debug-text: #ff9c66; + --color-workflow-debug-text-disabled: rgb(255 68 5 / 0.2); + --color-workflow-debug-run-status-bg-alt: rgb(255 46 0 / 0.5); + + --color-workflow-test-run-run-status-bg: rgb(21 90 239 / 0.5); + --color-workflow-test-run-text: #d1e0ff; + --color-workflow-test-run-run-status-bg-alt: rgb(45 90 190 / 0.9); + --color-workflow-test-run-paused-bg: rgb(247 144 9 / 0.3); + --color-workflow-test-run-paused-text: #fdb022; + --color-divider-subtle: rgb(200 206 218 / 0.08); --color-divider-regular: rgb(200 206 218 / 0.14); --color-divider-deep: rgb(200 206 218 / 0.2); @@ -557,6 +604,7 @@ html[data-theme="dark"] { --color-effects-highlight-lightmode-off: rgb(200 206 218 / 0.08); --color-effects-image-frame: #ffffff; --color-effects-icon-border: rgb(255 255 255 / 0.15); + --color-effects-highlight-subtle: rgb(200 206 218 / 0.04); --color-util-colors-orange-dark-orange-dark-50: #57130a; --color-util-colors-orange-dark-orange-dark-100: #771a0d; @@ -771,7 +819,9 @@ html[data-theme="dark"] { --color-saas-background-inverted: rgb(255 255 255 / 0.9); --color-saas-background-inverted-hover: #ffffff; - --color-dify-logo-dify-logo-blue: #e8e8e8; - --color-dify-logo-dify-logo-black: #e8e8e8; + --color-dify-logo-blue: #e8e8e8; + --color-dify-logo-black: #e8e8e8; + --color-dify-logo-outline-1: #ffffff; + --color-dify-logo-outline-2: #e8e8e8; } \ No newline at end of file diff --git a/web/themes/light.css b/web/themes/light.css index 93b76cbfec..3901de4d8b 100644 --- a/web/themes/light.css +++ b/web/themes/light.css @@ -89,6 +89,17 @@ html[data-theme="light"] { --color-components-button-indigo-bg-hover: #3538cd; --color-components-button-indigo-bg-disabled: rgb(97 114 243 / 0.14); + --color-components-button-debug-text: #ffffff; + --color-components-button-debug-text-disabled: rgb(255 255 255 / 0.6); + --color-components-button-debug-bg: #ff4405; + --color-components-button-debug-bg-hover: #e62e05; + --color-components-button-debug-bg-disabled: rgb(255 68 5 / 0.2); + --color-components-button-debug-border: rgb(16 24 40 / 0.04); + --color-components-button-debug-border-hover: rgb(16 24 40 / 0.08); + --color-components-button-debug-border-disabled: rgb(255 255 255 / 0); + + --color-components-button-button-seam: rgb(0 0 0 / 0.03); + --color-components-checkbox-icon: #ffffff; --color-components-checkbox-icon-disabled: rgb(255 255 255 / 0.5); --color-components-checkbox-bg: #155aef; @@ -149,6 +160,7 @@ html[data-theme="light"] { --color-components-panel-on-panel-item-bg-destructive-hover-transparent: rgb(254 243 242 / 0); --color-components-panel-bg-transparent: rgb(255 255 255 / 0); + --color-components-panel-bg-blur-burn: rgb(255 255 255 / 0.9); --color-components-main-nav-nav-button-text: #495464; --color-components-main-nav-nav-button-text-active: #155aef; @@ -159,6 +171,26 @@ html[data-theme="light"] { --color-components-main-nav-nav-user-border: #ffffff; + --color-components-main-nav-text: #495464; + --color-components-main-nav-text-active: #0033ff; + --color-components-main-nav-glass-edge-highlight-first: rgb(255 255 255 / 0.98); + --color-components-main-nav-glass-edge-highlight-middle: rgb(255 255 255 / 0); + --color-components-main-nav-glass-edge-highlight-end: rgb(255 255 255 / 0.42); + + --color-components-main-nav-glass-edge-reflection-first: rgb(0 51 255 / 0); + --color-components-main-nav-glass-edge-reflection-middle: rgb(0 51 255 / 0.6); + --color-components-main-nav-glass-edge-reflection-end: rgb(0 51 255 / 0); + + --color-components-main-nav-glass-surface-first: rgb(0 51 255 / 0.08); + --color-components-main-nav-glass-surface-middle-1: rgb(0 51 255 / 0.12); + --color-components-main-nav-glass-surface-middle-2: rgb(0 51 255 / 0.1); + --color-components-main-nav-glass-surface-end: rgb(0 51 255 / 0.08); + + --color-components-main-nav-glass-inner-glow: rgb(255 255 255 / 0.3); + --color-components-main-nav-glass-shadow-reflection: rgb(0 51 255 / 0.06); + --color-components-main-nav-glass-shadow-reflection-glow: rgb(255 255 255 / 0); + --color-components-main-nav-glass-text-glow: rgb(49 70 255 / 0.18); + --color-components-slider-knob: #ffffff; --color-components-slider-knob-hover: #ffffff; --color-components-slider-knob-disabled: rgb(255 255 255 / 0.95); @@ -357,6 +389,8 @@ html[data-theme="light"] { --color-components-icon-bg-orange-solid: #f79009; --color-components-icon-bg-orange-soft: #fffaeb; + --color-components-marketplace-header-bg: rgb(255 255 255 / 0.98); + --color-text-primary: #101828; --color-text-secondary: #354052; --color-text-tertiary: #676f83; @@ -418,6 +452,7 @@ html[data-theme="light"] { --color-background-overlay-backdrop: rgb(242 244 247 / 0.95); --color-background-body-transparent: rgb(242 244 247 / 0); --color-background-section-burn-inverted: #f2f4f7; + --color-background-default-hover-alpha-0: rgb(249 250 251 / 0); --color-shadow-shadow-1: rgb(9 9 11 / 0.03); --color-shadow-shadow-3: rgb(9 9 11 / 0.05); @@ -501,6 +536,18 @@ html[data-theme="light"] { --color-workflow-workflow-progress-bg-1: rgb(200 206 218 / 0.2); --color-workflow-workflow-progress-bg-2: rgb(200 206 218 / 0.04); + --color-workflow-debug-run-status-bg: rgb(255 68 5 / 0.08); + --color-workflow-debug-breakpoint: #e62e05; + --color-workflow-debug-text: #e62e05; + --color-workflow-debug-text-disabled: rgb(255 68 5 / 0.2); + --color-workflow-debug-run-status-bg-alt: rgb(255 68 5 / 0.14); + + --color-workflow-test-run-run-status-bg: rgb(21 90 239 / 0.08); + --color-workflow-test-run-text: #004aeb; + --color-workflow-test-run-run-status-bg-alt: rgb(21 90 239 / 0.14); + --color-workflow-test-run-paused-bg: rgb(247 144 9 / 0.14); + --color-workflow-test-run-paused-text: #dc6803; + --color-divider-subtle: rgb(16 24 40 / 0.04); --color-divider-regular: rgb(16 24 40 / 0.08); --color-divider-deep: rgb(16 24 40 / 0.14); @@ -545,6 +592,7 @@ html[data-theme="light"] { --color-effects-highlight-lightmode-off: rgb(255 255 255 / 0); --color-effects-image-frame: #ffffff; --color-effects-icon-border: rgb(16 24 40 / 0.08); + --color-effects-highlight-subtle: rgb(255 255 255 / 0.5); --color-util-colors-orange-dark-orange-dark-50: #fff4ed; --color-util-colors-orange-dark-orange-dark-100: #ffe6d5; @@ -759,7 +807,9 @@ html[data-theme="light"] { --color-saas-background-inverted: #0b0b0e; --color-saas-background-inverted-hover: #222225; - --color-dify-logo-dify-logo-blue: #0033ff; - --color-dify-logo-dify-logo-black: #000000; + --color-dify-logo-blue: #0033ff; + --color-dify-logo-black: #000000; + --color-dify-logo-outline-1: rgb(0 0 0 / 0); + --color-dify-logo-outline-2: rgb(0 0 0 / 0); } \ No newline at end of file diff --git a/web/themes/tailwind-theme-var-define.ts b/web/themes/tailwind-theme-var-define.ts index 23d65b4bab..2ea617284f 100644 --- a/web/themes/tailwind-theme-var-define.ts +++ b/web/themes/tailwind-theme-var-define.ts @@ -89,6 +89,17 @@ const vars = { 'components-button-indigo-bg-hover': 'var(--color-components-button-indigo-bg-hover)', 'components-button-indigo-bg-disabled': 'var(--color-components-button-indigo-bg-disabled)', + 'components-button-debug-text': 'var(--color-components-button-debug-text)', + 'components-button-debug-text-disabled': 'var(--color-components-button-debug-text-disabled)', + 'components-button-debug-bg': 'var(--color-components-button-debug-bg)', + 'components-button-debug-bg-hover': 'var(--color-components-button-debug-bg-hover)', + 'components-button-debug-bg-disabled': 'var(--color-components-button-debug-bg-disabled)', + 'components-button-debug-border': 'var(--color-components-button-debug-border)', + 'components-button-debug-border-hover': 'var(--color-components-button-debug-border-hover)', + 'components-button-debug-border-disabled': 'var(--color-components-button-debug-border-disabled)', + + 'components-button-button-seam': 'var(--color-components-button-button-seam)', + 'components-checkbox-icon': 'var(--color-components-checkbox-icon)', 'components-checkbox-icon-disabled': 'var(--color-components-checkbox-icon-disabled)', 'components-checkbox-bg': 'var(--color-components-checkbox-bg)', @@ -149,6 +160,7 @@ const vars = { 'components-panel-on-panel-item-bg-destructive-hover-transparent': 'var(--color-components-panel-on-panel-item-bg-destructive-hover-transparent)', 'components-panel-bg-transparent': 'var(--color-components-panel-bg-transparent)', + 'components-panel-bg-blur-burn': 'var(--color-components-panel-bg-blur-burn)', 'components-main-nav-nav-button-text': 'var(--color-components-main-nav-nav-button-text)', 'components-main-nav-nav-button-text-active': 'var(--color-components-main-nav-nav-button-text-active)', @@ -159,6 +171,26 @@ const vars = { 'components-main-nav-nav-user-border': 'var(--color-components-main-nav-nav-user-border)', + 'components-main-nav-text': 'var(--color-components-main-nav-text)', + 'components-main-nav-text-active': 'var(--color-components-main-nav-text-active)', + 'components-main-nav-glass-edge-highlight-first': 'var(--color-components-main-nav-glass-edge-highlight-first)', + 'components-main-nav-glass-edge-highlight-middle': 'var(--color-components-main-nav-glass-edge-highlight-middle)', + 'components-main-nav-glass-edge-highlight-end': 'var(--color-components-main-nav-glass-edge-highlight-end)', + + 'components-main-nav-glass-edge-reflection-first': 'var(--color-components-main-nav-glass-edge-reflection-first)', + 'components-main-nav-glass-edge-reflection-middle': 'var(--color-components-main-nav-glass-edge-reflection-middle)', + 'components-main-nav-glass-edge-reflection-end': 'var(--color-components-main-nav-glass-edge-reflection-end)', + + 'components-main-nav-glass-surface-first': 'var(--color-components-main-nav-glass-surface-first)', + 'components-main-nav-glass-surface-middle-1': 'var(--color-components-main-nav-glass-surface-middle-1)', + 'components-main-nav-glass-surface-middle-2': 'var(--color-components-main-nav-glass-surface-middle-2)', + 'components-main-nav-glass-surface-end': 'var(--color-components-main-nav-glass-surface-end)', + + 'components-main-nav-glass-inner-glow': 'var(--color-components-main-nav-glass-inner-glow)', + 'components-main-nav-glass-shadow-reflection': 'var(--color-components-main-nav-glass-shadow-reflection)', + 'components-main-nav-glass-shadow-reflection-glow': 'var(--color-components-main-nav-glass-shadow-reflection-glow)', + 'components-main-nav-glass-text-glow': 'var(--color-components-main-nav-glass-text-glow)', + 'components-slider-knob': 'var(--color-components-slider-knob)', 'components-slider-knob-hover': 'var(--color-components-slider-knob-hover)', 'components-slider-knob-disabled': 'var(--color-components-slider-knob-disabled)', @@ -357,6 +389,8 @@ const vars = { 'components-icon-bg-orange-solid': 'var(--color-components-icon-bg-orange-solid)', 'components-icon-bg-orange-soft': 'var(--color-components-icon-bg-orange-soft)', + 'components-marketplace-header-bg': 'var(--color-components-marketplace-header-bg)', + 'text-primary': 'var(--color-text-primary)', 'text-secondary': 'var(--color-text-secondary)', 'text-tertiary': 'var(--color-text-tertiary)', @@ -418,6 +452,7 @@ const vars = { 'background-overlay-backdrop': 'var(--color-background-overlay-backdrop)', 'background-body-transparent': 'var(--color-background-body-transparent)', 'background-section-burn-inverted': 'var(--color-background-section-burn-inverted)', + 'background-default-hover-alpha-0': 'var(--color-background-default-hover-alpha-0)', 'shadow-shadow-1': 'var(--color-shadow-shadow-1)', 'shadow-shadow-3': 'var(--color-shadow-shadow-3)', @@ -501,6 +536,18 @@ const vars = { 'workflow-workflow-progress-bg-1': 'var(--color-workflow-workflow-progress-bg-1)', 'workflow-workflow-progress-bg-2': 'var(--color-workflow-workflow-progress-bg-2)', + 'workflow-debug-run-status-bg': 'var(--color-workflow-debug-run-status-bg)', + 'workflow-debug-breakpoint': 'var(--color-workflow-debug-breakpoint)', + 'workflow-debug-text': 'var(--color-workflow-debug-text)', + 'workflow-debug-text-disabled': 'var(--color-workflow-debug-text-disabled)', + 'workflow-debug-run-status-bg-alt': 'var(--color-workflow-debug-run-status-bg-alt)', + + 'workflow-test-run-run-status-bg': 'var(--color-workflow-test-run-run-status-bg)', + 'workflow-test-run-text': 'var(--color-workflow-test-run-text)', + 'workflow-test-run-run-status-bg-alt': 'var(--color-workflow-test-run-run-status-bg-alt)', + 'workflow-test-run-paused-bg': 'var(--color-workflow-test-run-paused-bg)', + 'workflow-test-run-paused-text': 'var(--color-workflow-test-run-paused-text)', + 'divider-subtle': 'var(--color-divider-subtle)', 'divider-regular': 'var(--color-divider-regular)', 'divider-deep': 'var(--color-divider-deep)', @@ -545,6 +592,7 @@ const vars = { 'effects-highlight-lightmode-off': 'var(--color-effects-highlight-lightmode-off)', 'effects-image-frame': 'var(--color-effects-image-frame)', 'effects-icon-border': 'var(--color-effects-icon-border)', + 'effects-highlight-subtle': 'var(--color-effects-highlight-subtle)', 'util-colors-orange-dark-orange-dark-50': 'var(--color-util-colors-orange-dark-orange-dark-50)', 'util-colors-orange-dark-orange-dark-100': 'var(--color-util-colors-orange-dark-orange-dark-100)', @@ -759,8 +807,10 @@ const vars = { 'saas-background-inverted': 'var(--color-saas-background-inverted)', 'saas-background-inverted-hover': 'var(--color-saas-background-inverted-hover)', - 'dify-logo-dify-logo-blue': 'var(--color-dify-logo-dify-logo-blue)', - 'dify-logo-dify-logo-black': 'var(--color-dify-logo-dify-logo-black)', + 'dify-logo-blue': 'var(--color-dify-logo-blue)', + 'dify-logo-black': 'var(--color-dify-logo-black)', + 'dify-logo-outline-1': 'var(--color-dify-logo-outline-1)', + 'dify-logo-outline-2': 'var(--color-dify-logo-outline-2)', } export default vars