Merge remote-tracking branch 'origin/feat/support-agent-sandbox' into pre-align-hitl-frontend

This commit is contained in:
yyh
2026-02-09 16:06:17 +08:00
29 changed files with 1169 additions and 25 deletions

View File

@@ -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)

View File

@@ -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",

View File

@@ -0,0 +1,3 @@
from . import end_user
__all__ = ["end_user"]

View File

@@ -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/<uuid:end_user_id>")
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")

View File

@@ -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

View File

@@ -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

View File

@@ -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:
"""

View File

@@ -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())

View File

@@ -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

View File

@@ -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 (
<DatePicker
value={dayjs(value ? value * 1000 : Date.now())}
timezone={timezone}
onChange={handleDateChange}
onClear={handleDateChange}
renderTrigger={renderTrigger}

View File

@@ -273,6 +273,71 @@ The text generation application offers non-session support and is ideal for tran
</Row>
---
<Heading
url='/end-users/:end_user_id'
method='GET'
title='Get End User'
name='#end-user'
/>
<Row>
<Col>
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
</Col>
<Col sticky>
### Request Example
<CodeGroup
title="Request"
tag="GET"
label="/end-users/:end_user_id"
targetCode={`curl -X GET '${props.appDetail.api_base_url}/end-users/6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13' \\
--header 'Authorization: Bearer {api_key}'`}
/>
### Response Example
<CodeGroup title="Response">
```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"
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/files/:file_id/preview'
method='GET'

View File

@@ -272,6 +272,71 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
</Row>
---
<Heading
url='/end-users/:end_user_id'
method='GET'
title='エンドユーザーを取得'
name='#end-user'
/>
<Row>
<Col>
エンドユーザー 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, 内部サーバーエラー
</Col>
<Col sticky>
### リクエスト例
<CodeGroup
title="Request"
tag="GET"
label="/end-users/:end_user_id"
targetCode={`curl -X GET '${props.appDetail.api_base_url}/end-users/6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13' \\
--header 'Authorization: Bearer {api_key}'`}
/>
### レスポンス例
<CodeGroup title="Response">
```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"
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/files/:file_id/preview'
method='GET'

View File

@@ -249,6 +249,69 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
</Row>
---
<Heading
url='/end-users/:end_user_id'
method='GET'
title='获取终端用户'
name='#end-user'
/>
<Row>
<Col>
通过终端用户 ID 获取终端用户信息。
当其他 API 返回终端用户 ID例如上传文件接口返回的 `created_by`)时,可使用该接口查询对应的终端用户信息。
### 路径参数
- `end_user_id` (uuid) 必需
终端用户 ID。
### Response
返回 EndUser 对象。
- `id` (uuid) ID
- `tenant_id` (uuid) 工作空间TenantID
- `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内部服务器错误
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="GET"
label="/end-users/:end_user_id"
targetCode={`curl -X GET '${props.appDetail.api_base_url}/end-users/6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13' \\
--header 'Authorization: Bearer {api_key}'`}
/>
<CodeGroup title="Response">
```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"
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/files/:file_id/preview'
method='GET'

View File

@@ -392,6 +392,71 @@ Chat applications support session persistence, allowing previous chat history to
</Row>
---
<Heading
url='/end-users/:end_user_id'
method='GET'
title='Get End User'
name='#end-user'
/>
<Row>
<Col>
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
</Col>
<Col sticky>
### Request Example
<CodeGroup
title="Request"
tag="GET"
label="/end-users/:end_user_id"
targetCode={`curl -X GET '${props.appDetail.api_base_url}/end-users/6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13' \\
--header 'Authorization: Bearer {api_key}'`}
/>
### Response Example
<CodeGroup title="Response">
```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"
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/files/:file_id/preview'
method='GET'

View File

@@ -393,6 +393,71 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
</Row>
---
<Heading
url='/end-users/:end_user_id'
method='GET'
title='エンドユーザーを取得'
name='#end-user'
/>
<Row>
<Col>
エンドユーザー 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, 内部サーバーエラー
</Col>
<Col sticky>
### リクエスト例
<CodeGroup
title="Request"
tag="GET"
label="/end-users/:end_user_id"
targetCode={`curl -X GET '${props.appDetail.api_base_url}/end-users/6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13' \\
--header 'Authorization: Bearer {api_key}'`}
/>
### レスポンス例
<CodeGroup title="Response">
```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"
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/files/:file_id/preview'
method='GET'

View File

@@ -390,6 +390,69 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
</Row>
---
<Heading
url='/end-users/:end_user_id'
method='GET'
title='获取终端用户'
name='#end-user'
/>
<Row>
<Col>
通过终端用户 ID 获取终端用户信息。
当其他 API 返回终端用户 ID例如上传文件接口返回的 `created_by`)时,可使用该接口查询对应的终端用户信息。
### 路径参数
- `end_user_id` (uuid) 必需
终端用户 ID。
### Response
返回 EndUser 对象。
- `id` (uuid) ID
- `tenant_id` (uuid) 工作空间TenantID
- `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内部服务器错误
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="GET"
label="/end-users/:end_user_id"
targetCode={`curl -X GET '${props.appDetail.api_base_url}/end-users/6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13' \\
--header 'Authorization: Bearer {api_key}'`}
/>
<CodeGroup title="Response">
```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"
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/files/:file_id/preview'
method='GET'

View File

@@ -362,6 +362,71 @@ Chat applications support session persistence, allowing previous chat history to
</Row>
---
<Heading
url='/end-users/:end_user_id'
method='GET'
title='Get End User'
name='#end-user'
/>
<Row>
<Col>
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
</Col>
<Col sticky>
### Request Example
<CodeGroup
title="Request"
tag="GET"
label="/end-users/:end_user_id"
targetCode={`curl -X GET '${props.appDetail.api_base_url}/end-users/6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13' \\
--header 'Authorization: Bearer {api_key}'`}
/>
### Response Example
<CodeGroup title="Response">
```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"
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/files/:file_id/preview'
method='GET'

View File

@@ -362,6 +362,71 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
</Row>
---
<Heading
url='/end-users/:end_user_id'
method='GET'
title='エンドユーザーを取得'
name='#end-user'
/>
<Row>
<Col>
エンドユーザー 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, 内部サーバーエラー
</Col>
<Col sticky>
### リクエスト例
<CodeGroup
title="Request"
tag="GET"
label="/end-users/:end_user_id"
targetCode={`curl -X GET '${props.appDetail.api_base_url}/end-users/6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13' \\
--header 'Authorization: Bearer {api_key}'`}
/>
### レスポンス例
<CodeGroup title="Response">
```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"
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/files/:file_id/preview'
method='GET'

View File

@@ -368,6 +368,69 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
</Row>
---
<Heading
url='/end-users/:end_user_id'
method='GET'
title='获取终端用户'
name='#end-user'
/>
<Row>
<Col>
通过终端用户 ID 获取终端用户信息。
当其他 API 返回终端用户 ID例如上传文件接口返回的 `created_by`)时,可使用该接口查询对应的终端用户信息。
### 路径参数
- `end_user_id` (uuid) 必需
终端用户 ID。
### Response
返回 EndUser 对象。
- `id` (uuid) ID
- `tenant_id` (uuid) 工作空间TenantID
- `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内部服务器错误
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="GET"
label="/end-users/:end_user_id"
targetCode={`curl -X GET '${props.appDetail.api_base_url}/end-users/6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13' \\
--header 'Authorization: Bearer {api_key}'`}
/>
<CodeGroup title="Response">
```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"
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/files/:file_id/preview'
method='GET'

View File

@@ -740,6 +740,71 @@ Workflow applications offers non-session support and is ideal for translation, a
---
<Heading
url='/end-users/:end_user_id'
method='GET'
title='Get End User'
name='#end-user'
/>
<Row>
<Col>
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
</Col>
<Col sticky>
### Request Example
<CodeGroup
title="Request"
tag="GET"
label="/end-users/:end_user_id"
targetCode={`curl -X GET '${props.appDetail.api_base_url}/end-users/6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13' \\
--header 'Authorization: Bearer {api_key}'`}
/>
### Response Example
<CodeGroup title="Response">
```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"
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/workflows/logs'
method='GET'

View File

@@ -736,6 +736,71 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
---
<Heading
url='/end-users/:end_user_id'
method='GET'
title='エンドユーザーを取得'
name='#end-user'
/>
<Row>
<Col>
エンドユーザー 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, 内部サーバーエラー
</Col>
<Col sticky>
### リクエスト例
<CodeGroup
title="Request"
tag="GET"
label="/end-users/:end_user_id"
targetCode={`curl -X GET '${props.appDetail.api_base_url}/end-users/6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13' \\
--header 'Authorization: Bearer {api_key}'`}
/>
### レスポンス例
<CodeGroup title="Response">
```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"
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/workflows/logs'
method='GET'
@@ -1047,4 +1112,3 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
</Col>
</Row>
___

View File

@@ -727,6 +727,69 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
</Row>
---
<Heading
url='/end-users/:end_user_id'
method='GET'
title='获取终端用户'
name='#end-user'
/>
<Row>
<Col>
通过终端用户 ID 获取终端用户信息。
当其他 API 返回终端用户 ID例如上传文件接口返回的 `created_by`)时,可使用该接口查询对应的终端用户信息。
### 路径参数
- `end_user_id` (uuid) 必需
终端用户 ID。
### Response
返回 EndUser 对象。
- `id` (uuid) ID
- `tenant_id` (uuid) 工作空间TenantID
- `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内部服务器错误
</Col>
<Col sticky>
<CodeGroup
title="Request"
tag="GET"
label="/end-users/:end_user_id"
targetCode={`curl -X GET '${props.appDetail.api_base_url}/end-users/6ad1ab0a-73ff-4ac1-b9e4-cdb312f71f13' \\
--header 'Authorization: Bearer {api_key}'`}
/>
<CodeGroup title="Response">
```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"
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading
url='/workflows/logs'
method='GET'

View File

@@ -116,10 +116,10 @@ export const BannerItem: FC<BannerItemProps> = ({ banner, autoplayDelay, isPause
className="flex min-w-[480px] max-w-[680px] flex-[1_0_0] flex-col pr-4"
style={responsiveStyle}
>
<p className="title-4xl-semi-bold line-clamp-1 text-dify-logo-dify-logo-blue">
<p className="title-4xl-semi-bold line-clamp-1 text-dify-logo-blue">
{category}
</p>
<p className="title-4xl-semi-bold line-clamp-2 text-dify-logo-dify-logo-black">
<p className="title-4xl-semi-bold line-clamp-2 text-dify-logo-black">
{title}
</p>
</div>

View File

@@ -1138,17 +1138,27 @@ describe('CommonCreateModal', () => {
mockVerifyCredentials.mockImplementation((params, { onError }) => {
onError(new Error('Raw error'))
})
const builder = createMockSubscriptionBuilder()
render(<CommonCreateModal {...defaultProps} />)
await waitFor(() => {
expect(mockCreateBuilder).toHaveBeenCalled()
})
render(<CommonCreateModal {...defaultProps} builder={builder} />)
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))
})
})

View File

@@ -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 () => {

View File

@@ -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<CommonResponse & { updated_at: number, hash: string }>(url, { body: sanitized }, { silent: true })
return post<CommonResponse & { updated_at: number, hash: string }>(url, { body: params }, { silent: true })
}
export const fetchNodesDefaultConfigs = (url: string) => {

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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