mirror of
https://github.com/langgenius/dify.git
synced 2026-02-09 15:10:13 -05:00
Compare commits
12 Commits
ca5b5b49ad
...
205c9dfc99
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
205c9dfc99 | ||
|
|
b014e91740 | ||
|
|
231378d647 | ||
|
|
8f6a8997f4 | ||
|
|
63d965bc44 | ||
|
|
a303560b98 | ||
|
|
6f50915d2b | ||
|
|
bc9ca4e0dd | ||
|
|
ca243d7efc | ||
|
|
2cb11b0a67 | ||
|
|
b289e6a2b6 | ||
|
|
2d6b30f3b8 |
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
3
api/controllers/service_api/end_user/__init__.py
Normal file
3
api/controllers/service_api/end_user/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from . import end_user
|
||||
|
||||
__all__ = ["end_user"]
|
||||
41
api/controllers/service_api/end_user/end_user.py
Normal file
41
api/controllers/service_api/end_user/end_user.py
Normal 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")
|
||||
7
api/controllers/service_api/end_user/error.py
Normal file
7
api/controllers/service_api/end_user/error.py
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -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())
|
||||
@@ -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
|
||||
|
||||
@@ -20,6 +20,7 @@ import type {
|
||||
VariableBlockType,
|
||||
WorkflowVariableBlockType,
|
||||
} from './types'
|
||||
import type { Node as WorkflowNode } from '@/app/components/workflow/types'
|
||||
import type { EventPayload } from '@/context/event-emitter'
|
||||
import { CodeNode } from '@lexical/code'
|
||||
import { LexicalComposer } from '@lexical/react/LexicalComposer'
|
||||
@@ -38,6 +39,8 @@ import {
|
||||
import * as React from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { WorkflowContext } from '@/app/components/workflow/context'
|
||||
import { HooksStoreContext } from '@/app/components/workflow/hooks-store/provider'
|
||||
import { FileReferenceNode } from '@/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/node'
|
||||
import { FilePreviewContextProvider } from '@/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/preview-context'
|
||||
import FileReferenceReplacementBlock from '@/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/replacement-block'
|
||||
@@ -169,6 +172,30 @@ const EnterCommandPlugin: FC<{ onEnter?: (event: KeyboardEvent) => void }> = ({
|
||||
return null
|
||||
}
|
||||
|
||||
type WorkflowAvailableNodesProps = {
|
||||
nodeId?: string
|
||||
isSupportSandbox?: boolean
|
||||
children: (availableNodes: WorkflowNode[]) => React.ReactNode
|
||||
}
|
||||
|
||||
const WorkflowAvailableNodes: FC<WorkflowAvailableNodesProps> = ({
|
||||
nodeId,
|
||||
isSupportSandbox,
|
||||
children,
|
||||
}) => {
|
||||
const { getBeforeNodesInSameBranch } = useWorkflow()
|
||||
const availableNodes = React.useMemo(
|
||||
() => nodeId && isSupportSandbox ? getBeforeNodesInSameBranch(nodeId || '') : [],
|
||||
[getBeforeNodesInSameBranch, isSupportSandbox, nodeId],
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{children(availableNodes)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export type PromptEditorProps = {
|
||||
instanceId?: string
|
||||
nodeId?: string
|
||||
@@ -204,7 +231,11 @@ export type PromptEditorProps = {
|
||||
shortcutPopups?: Array<{ hotkey: Hotkey, Popup: React.ComponentType<{ onClose: () => void, onInsert: (command: LexicalCommand<unknown>, params: unknown[]) => void }> }>
|
||||
}
|
||||
|
||||
const PromptEditor: FC<PromptEditorProps> = ({
|
||||
type PromptEditorContentProps = PromptEditorProps & {
|
||||
availableNodes: WorkflowNode[]
|
||||
}
|
||||
|
||||
const PromptEditorContent: FC<PromptEditorContentProps> = ({
|
||||
instanceId,
|
||||
nodeId,
|
||||
compact,
|
||||
@@ -237,6 +268,7 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
disableToolBlocks,
|
||||
onEnter,
|
||||
shortcutPopups = [],
|
||||
availableNodes,
|
||||
}) => {
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const initialConfig = {
|
||||
@@ -288,12 +320,6 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
} as EventPayload)
|
||||
}, [eventEmitter, historyBlock?.history])
|
||||
|
||||
const { getBeforeNodesInSameBranch } = useWorkflow()
|
||||
const availableNodes = React.useMemo(
|
||||
() => nodeId && isSupportSandbox ? getBeforeNodesInSameBranch(nodeId || '') : [],
|
||||
[getBeforeNodesInSameBranch, isSupportSandbox, nodeId],
|
||||
)
|
||||
|
||||
const toolBlockContextValue = React.useMemo(() => {
|
||||
if (!onToolMetadataChange)
|
||||
return null
|
||||
@@ -307,6 +333,10 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
}
|
||||
}, [availableNodes, nodeId, onToolMetadataChange, toolMetadata, workflowVariableBlock?.variables])
|
||||
|
||||
const filePreviewContextValue = React.useMemo(() => ({
|
||||
enabled: Boolean(isSupportSandbox),
|
||||
}), [isSupportSandbox])
|
||||
|
||||
const sandboxPlaceHolder = React.useMemo(() => {
|
||||
if (!isSupportSandbox)
|
||||
return null
|
||||
@@ -361,7 +391,7 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
return (
|
||||
<LexicalComposer initialConfig={{ ...initialConfig, editable }}>
|
||||
<ToolBlockContextProvider value={toolBlockContextValue}>
|
||||
<FilePreviewContextProvider value={{ enabled: Boolean(isSupportSandbox) }}>
|
||||
<FilePreviewContextProvider value={filePreviewContextValue}>
|
||||
<div
|
||||
className={cn('relative', wrapperClassName)}
|
||||
data-skill-editor-root={isSupportSandbox ? 'true' : undefined}
|
||||
@@ -549,4 +579,33 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
const PromptEditor: FC<PromptEditorProps> = (props) => {
|
||||
const workflowStore = React.useContext(WorkflowContext)
|
||||
const hooksStore = React.useContext(HooksStoreContext)
|
||||
const hasWorkflowContext = Boolean(workflowStore && hooksStore)
|
||||
|
||||
if (!hasWorkflowContext) {
|
||||
return (
|
||||
<PromptEditorContent
|
||||
{...props}
|
||||
availableNodes={[]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<WorkflowAvailableNodes
|
||||
nodeId={props.nodeId}
|
||||
isSupportSandbox={props.isSupportSandbox}
|
||||
>
|
||||
{availableNodes => (
|
||||
<PromptEditorContent
|
||||
{...props}
|
||||
availableNodes={availableNodes}
|
||||
/>
|
||||
)}
|
||||
</WorkflowAvailableNodes>
|
||||
)
|
||||
}
|
||||
|
||||
export default PromptEditor
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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) 工作空间(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,内部服务器错误
|
||||
|
||||
</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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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) 工作空间(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,内部服务器错误
|
||||
|
||||
</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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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) 工作空间(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,内部服务器错误
|
||||
|
||||
</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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
___
|
||||
|
||||
|
||||
@@ -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) 工作空间(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,内部服务器错误
|
||||
|
||||
</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'
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -845,6 +845,7 @@ export const useWorkflowRun = () => {
|
||||
const handleStopRun = useCallback((taskId: string) => {
|
||||
const setStoppedState = () => {
|
||||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
setIsListening,
|
||||
setShowVariableInspectPanel,
|
||||
@@ -852,16 +853,27 @@ export const useWorkflowRun = () => {
|
||||
setListeningTriggerNodeId,
|
||||
} = workflowStore.getState()
|
||||
|
||||
setWorkflowRunningData({
|
||||
result: {
|
||||
status: WorkflowRunningStatus.Stopped,
|
||||
inputs_truncated: false,
|
||||
process_data_truncated: false,
|
||||
outputs_truncated: false,
|
||||
},
|
||||
tracing: [],
|
||||
resultText: '',
|
||||
})
|
||||
if (workflowRunningData) {
|
||||
setWorkflowRunningData({
|
||||
...workflowRunningData,
|
||||
result: {
|
||||
...workflowRunningData.result,
|
||||
status: WorkflowRunningStatus.Stopped,
|
||||
},
|
||||
})
|
||||
}
|
||||
else {
|
||||
setWorkflowRunningData({
|
||||
result: {
|
||||
status: WorkflowRunningStatus.Stopped,
|
||||
inputs_truncated: false,
|
||||
process_data_truncated: false,
|
||||
outputs_truncated: false,
|
||||
},
|
||||
tracing: [],
|
||||
resultText: '',
|
||||
})
|
||||
}
|
||||
setIsListening(false)
|
||||
setListeningTriggerType(null)
|
||||
setListeningTriggerNodeId(null)
|
||||
|
||||
@@ -71,6 +71,10 @@ const WorkflowPreview = () => {
|
||||
return 'TRACING'
|
||||
})()
|
||||
|
||||
const shouldShowTracingLoading = effectiveTab === 'TRACING'
|
||||
&& !workflowRunningData?.tracing?.length
|
||||
&& (workflowRunningData?.result?.status === WorkflowRunningStatus.Running || !workflowRunningData?.result)
|
||||
|
||||
const handleTabChange = (tab: string) => {
|
||||
setUserSelectedTab(tab)
|
||||
}
|
||||
@@ -285,7 +289,7 @@ const WorkflowPreview = () => {
|
||||
/>
|
||||
)
|
||||
: null}
|
||||
{effectiveTab === 'TRACING' && !workflowRunningData?.tracing?.length
|
||||
{shouldShowTracingLoading
|
||||
? (
|
||||
<div className="flex h-full items-center justify-center !bg-background-section-burn">
|
||||
<Loading />
|
||||
|
||||
@@ -94,6 +94,8 @@ const SkillEditor = ({
|
||||
onAutoFocus,
|
||||
toolPickerScope = 'all',
|
||||
}: SkillEditorProps) => {
|
||||
const filePreviewContextValue = React.useMemo(() => ({ enabled: false }), [])
|
||||
|
||||
const initialConfig = {
|
||||
namespace: 'skill-editor',
|
||||
nodes: [
|
||||
@@ -123,7 +125,7 @@ const SkillEditor = ({
|
||||
|
||||
return (
|
||||
<LexicalComposer initialConfig={{ ...initialConfig, editable }}>
|
||||
<FilePreviewContextProvider value={{ enabled: false }}>
|
||||
<FilePreviewContextProvider value={filePreviewContextValue}>
|
||||
<div
|
||||
className={cn('relative', showLineNumbers && styles.lineNumbersScope, wrapperClassName)}
|
||||
data-skill-editor-root="true"
|
||||
|
||||
@@ -45,7 +45,7 @@ const FileReferenceBlock = ({ nodeKey, resourceId }: FileReferenceBlockProps) =>
|
||||
const [previewOpen, setPreviewOpen] = useState(false)
|
||||
const [previewStyle, setPreviewStyle] = useState<React.CSSProperties | null>(null)
|
||||
const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
const { enabled: isPreviewEnabled } = useFilePreviewContext()
|
||||
const isPreviewEnabled = useFilePreviewContext(context => context.enabled)
|
||||
const { t } = useTranslation()
|
||||
const isInteractive = editor.isEditable()
|
||||
|
||||
|
||||
@@ -1,13 +1,62 @@
|
||||
import * as React from 'react'
|
||||
import type { ReactNode } from 'react'
|
||||
import { createContext, useCallback, useContext, useEffect, useRef } from 'react'
|
||||
import { useStore } from 'zustand'
|
||||
import { createStore } from 'zustand/vanilla'
|
||||
|
||||
type FilePreviewContextValue = {
|
||||
export type FilePreviewContextValue = {
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
const FilePreviewContext = React.createContext<FilePreviewContextValue>({ enabled: false })
|
||||
|
||||
export const FilePreviewContextProvider = FilePreviewContext.Provider
|
||||
|
||||
export const useFilePreviewContext = () => {
|
||||
return React.useContext(FilePreviewContext)
|
||||
type FilePreviewStoreState = {
|
||||
enabled: boolean
|
||||
setEnabled: (enabled: boolean) => void
|
||||
}
|
||||
|
||||
const createFilePreviewStore = (enabled: boolean) => createStore<FilePreviewStoreState>(set => ({
|
||||
enabled,
|
||||
setEnabled: nextEnabled => set({ enabled: nextEnabled }),
|
||||
}))
|
||||
|
||||
type FilePreviewStore = ReturnType<typeof createFilePreviewStore>
|
||||
|
||||
const defaultFilePreviewStore = createFilePreviewStore(false)
|
||||
|
||||
const FilePreviewStoreContext = createContext<FilePreviewStore | null>(null)
|
||||
|
||||
type FilePreviewContextProviderProps = {
|
||||
value?: FilePreviewContextValue
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export const FilePreviewContextProvider = ({ value, children }: FilePreviewContextProviderProps) => {
|
||||
const storeRef = useRef<FilePreviewStore>(null)
|
||||
if (!storeRef.current)
|
||||
storeRef.current = createFilePreviewStore(value?.enabled ?? false)
|
||||
|
||||
useEffect(() => {
|
||||
storeRef.current?.getState().setEnabled(value?.enabled ?? false)
|
||||
}, [value?.enabled])
|
||||
|
||||
return (
|
||||
<FilePreviewStoreContext.Provider value={storeRef.current}>
|
||||
{children}
|
||||
</FilePreviewStoreContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const useFilePreviewContext = <T = FilePreviewContextValue>(
|
||||
selector?: (context: FilePreviewContextValue) => T,
|
||||
) => {
|
||||
const store = useContext(FilePreviewStoreContext) ?? defaultFilePreviewStore
|
||||
const selectContext = useCallback(
|
||||
(state: FilePreviewStoreState) => {
|
||||
if (selector)
|
||||
return selector({ enabled: state.enabled })
|
||||
return { enabled: state.enabled } as T
|
||||
},
|
||||
[selector],
|
||||
)
|
||||
|
||||
return useStore(store, selectContext)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import * as React from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import { InfoCircle } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
@@ -127,9 +128,25 @@ const ToolBlockComponent = ({
|
||||
const { t } = useTranslation()
|
||||
const authBadgeLabel = t('skillEditor.authorizationBadge', { ns: 'workflow' })
|
||||
const { theme } = useTheme()
|
||||
const toolBlockContext = useToolBlockContext()
|
||||
const isUsingExternalMetadata = Boolean(toolBlockContext?.onMetadataChange)
|
||||
const useModal = Boolean(toolBlockContext?.useModal)
|
||||
const {
|
||||
metadata,
|
||||
onMetadataChange,
|
||||
useModal,
|
||||
nodeId: contextNodeId,
|
||||
nodesOutputVars,
|
||||
availableNodes,
|
||||
} = useToolBlockContext(
|
||||
useShallow(context => ({
|
||||
metadata: context?.metadata,
|
||||
onMetadataChange: context?.onMetadataChange,
|
||||
useModal: context?.useModal,
|
||||
nodeId: context?.nodeId,
|
||||
nodesOutputVars: context?.nodesOutputVars,
|
||||
availableNodes: context?.availableNodes,
|
||||
})),
|
||||
)
|
||||
const isUsingExternalMetadata = Boolean(onMetadataChange)
|
||||
const useModalValue = Boolean(useModal)
|
||||
const [isSettingOpen, setIsSettingOpen] = useState(false)
|
||||
const [toolValue, setToolValue] = useState<ToolValue | null>(null)
|
||||
const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null)
|
||||
@@ -183,14 +200,14 @@ const ToolBlockComponent = ({
|
||||
|
||||
const toolConfigFromMetadata = useMemo(() => {
|
||||
if (isUsingExternalMetadata) {
|
||||
const metadata = toolBlockContext?.metadata as SkillFileMetadata | undefined
|
||||
return metadata?.tools?.[configId]
|
||||
const externalMetadata = metadata as SkillFileMetadata | undefined
|
||||
return externalMetadata?.tools?.[configId]
|
||||
}
|
||||
if (!activeTabId || activeTabId === START_TAB_ID)
|
||||
return undefined
|
||||
const metadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined
|
||||
return metadata?.tools?.[configId]
|
||||
}, [activeTabId, configId, fileMetadata, isUsingExternalMetadata, toolBlockContext?.metadata])
|
||||
const resultMetadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined
|
||||
return resultMetadata?.tools?.[configId]
|
||||
}, [activeTabId, configId, fileMetadata, isUsingExternalMetadata, metadata])
|
||||
|
||||
const isInteractive = editor.isEditable()
|
||||
|
||||
@@ -281,7 +298,7 @@ const ToolBlockComponent = ({
|
||||
}, [configuredToolValue, isSettingOpen])
|
||||
|
||||
useEffect(() => {
|
||||
if (useModal)
|
||||
if (useModalValue)
|
||||
return
|
||||
const containerFromRef = ref.current?.closest('[data-skill-editor-root="true"]') as HTMLElement | null
|
||||
const fallbackContainer = document.querySelector('[data-skill-editor-root="true"]') as HTMLElement | null
|
||||
@@ -289,10 +306,10 @@ const ToolBlockComponent = ({
|
||||
if (container)
|
||||
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
|
||||
setPortalContainer(container)
|
||||
}, [ref, useModal])
|
||||
}, [ref, useModalValue])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSettingOpen || useModal)
|
||||
if (!isSettingOpen || useModalValue)
|
||||
return
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
@@ -316,7 +333,7 @@ const ToolBlockComponent = ({
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||
}, [isSettingOpen, portalContainer, ref, useModal])
|
||||
}, [isSettingOpen, portalContainer, ref, useModalValue])
|
||||
|
||||
const displayLabel = label || toolMeta?.label || tool
|
||||
const resolvedIcon = (() => {
|
||||
@@ -398,7 +415,7 @@ const ToolBlockComponent = ({
|
||||
return
|
||||
const credentialId = normalizeCredentialId(nextValue.credential_id)
|
||||
if (isUsingExternalMetadata) {
|
||||
const metadata = (toolBlockContext?.metadata || {}) as SkillFileMetadata
|
||||
const externalMetadata = (metadata || {}) as SkillFileMetadata
|
||||
const toolType = currentProvider.type === CollectionType.mcp ? 'mcp' : 'builtin'
|
||||
const buildFields = (value: Record<string, unknown> | undefined) => {
|
||||
if (!value)
|
||||
@@ -415,9 +432,9 @@ const ToolBlockComponent = ({
|
||||
...buildFields(nextValue.parameters),
|
||||
]
|
||||
const nextMetadata: SkillFileMetadata = {
|
||||
...metadata,
|
||||
...externalMetadata,
|
||||
tools: {
|
||||
...(metadata.tools || {}),
|
||||
...(externalMetadata.tools || {}),
|
||||
[configId]: {
|
||||
type: toolType,
|
||||
configuration: { fields },
|
||||
@@ -425,12 +442,12 @@ const ToolBlockComponent = ({
|
||||
},
|
||||
},
|
||||
}
|
||||
toolBlockContext?.onMetadataChange?.(nextMetadata)
|
||||
onMetadataChange?.(nextMetadata)
|
||||
return
|
||||
}
|
||||
if (!activeTabId || activeTabId === START_TAB_ID)
|
||||
return
|
||||
const metadata = (fileMetadata.get(activeTabId) || {}) as SkillFileMetadata
|
||||
const currentMetadata = (fileMetadata.get(activeTabId) || {}) as SkillFileMetadata
|
||||
const toolType = currentProvider.type === CollectionType.mcp ? 'mcp' : 'builtin'
|
||||
const buildFields = (value: Record<string, unknown> | undefined) => {
|
||||
if (!value)
|
||||
@@ -447,9 +464,9 @@ const ToolBlockComponent = ({
|
||||
...buildFields(nextValue.parameters),
|
||||
]
|
||||
const nextMetadata: SkillFileMetadata = {
|
||||
...metadata,
|
||||
...currentMetadata,
|
||||
tools: {
|
||||
...(metadata.tools || {}),
|
||||
...(currentMetadata.tools || {}),
|
||||
[configId]: {
|
||||
type: toolType,
|
||||
configuration: { fields },
|
||||
@@ -488,13 +505,13 @@ const ToolBlockComponent = ({
|
||||
return nextMetadata
|
||||
}
|
||||
if (isUsingExternalMetadata) {
|
||||
toolBlockContext?.onMetadataChange?.(applyCredential(toolBlockContext?.metadata as SkillFileMetadata | undefined))
|
||||
onMetadataChange?.(applyCredential(metadata as SkillFileMetadata | undefined))
|
||||
return
|
||||
}
|
||||
if (!activeTabId || activeTabId === START_TAB_ID)
|
||||
return
|
||||
const metadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined
|
||||
const nextMetadata = applyCredential(metadata)
|
||||
const currentMetadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined
|
||||
const nextMetadata = applyCredential(currentMetadata)
|
||||
storeApi.getState().setDraftMetadata(activeTabId, nextMetadata)
|
||||
storeApi.getState().pinTab(activeTabId)
|
||||
}
|
||||
@@ -535,10 +552,10 @@ const ToolBlockComponent = ({
|
||||
currentTool={currentTool}
|
||||
value={toolValue}
|
||||
onChange={handleToolValueChange}
|
||||
nodeId={toolBlockContext?.nodeId}
|
||||
nodesOutputVars={toolBlockContext?.nodesOutputVars}
|
||||
availableNodes={toolBlockContext?.availableNodes}
|
||||
enableVariableReference={useModal}
|
||||
nodeId={contextNodeId}
|
||||
nodesOutputVars={nodesOutputVars}
|
||||
availableNodes={availableNodes}
|
||||
enableVariableReference={useModalValue}
|
||||
/>
|
||||
{readmeEntrance}
|
||||
</div>
|
||||
@@ -577,7 +594,7 @@ const ToolBlockComponent = ({
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
{useModal && (
|
||||
{useModalValue && (
|
||||
<Modal
|
||||
isShow={isSettingOpen}
|
||||
onClose={() => setIsSettingOpen(false)}
|
||||
@@ -589,7 +606,7 @@ const ToolBlockComponent = ({
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
{!useModal && portalContainer && isSettingOpen && createPortal(
|
||||
{!useModalValue && portalContainer && isSettingOpen && createPortal(
|
||||
<div
|
||||
className="absolute bottom-4 right-4 top-4 z-[99]"
|
||||
data-tool-setting-panel="true"
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import type { Node, NodeOutPutVar } from '@/app/components/workflow/types'
|
||||
import { createContext, useContext } from 'react'
|
||||
import { createContext, useCallback, useContext, useEffect, useRef } from 'react'
|
||||
import { useStore } from 'zustand'
|
||||
import { createStore } from 'zustand/vanilla'
|
||||
|
||||
type ToolBlockContextValue = {
|
||||
export type ToolBlockContextValue = {
|
||||
metadata?: Record<string, unknown>
|
||||
onMetadataChange?: (metadata: Record<string, unknown>) => void
|
||||
useModal?: boolean
|
||||
@@ -10,8 +13,56 @@ type ToolBlockContextValue = {
|
||||
availableNodes?: Node[]
|
||||
}
|
||||
|
||||
const ToolBlockContext = createContext<ToolBlockContextValue | null>(null)
|
||||
type ToolBlockStoreState = {
|
||||
context: ToolBlockContextValue | null
|
||||
setContext: (context: ToolBlockContextValue | null) => void
|
||||
}
|
||||
|
||||
export const ToolBlockContextProvider = ToolBlockContext.Provider
|
||||
const createToolBlockStore = (initialContext: ToolBlockContextValue | null) => createStore<ToolBlockStoreState>(set => ({
|
||||
context: initialContext,
|
||||
setContext: context => set({ context }),
|
||||
}))
|
||||
|
||||
export const useToolBlockContext = () => useContext(ToolBlockContext)
|
||||
type ToolBlockStore = ReturnType<typeof createToolBlockStore>
|
||||
|
||||
const defaultToolBlockStore = createToolBlockStore(null)
|
||||
|
||||
const ToolBlockStoreContext = createContext<ToolBlockStore | null>(null)
|
||||
|
||||
type ToolBlockContextProviderProps = {
|
||||
value?: ToolBlockContextValue | null
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export const ToolBlockContextProvider = ({ value, children }: ToolBlockContextProviderProps) => {
|
||||
const storeRef = useRef<ToolBlockStore>(null)
|
||||
if (!storeRef.current)
|
||||
storeRef.current = createToolBlockStore(value ?? null)
|
||||
|
||||
useEffect(() => {
|
||||
storeRef.current?.getState().setContext(value ?? null)
|
||||
}, [value])
|
||||
|
||||
return (
|
||||
<ToolBlockStoreContext.Provider value={storeRef.current}>
|
||||
{children}
|
||||
</ToolBlockStoreContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const useToolBlockContext = <T = ToolBlockContextValue | null,>(
|
||||
selector?: (context: ToolBlockContextValue | null) => T,
|
||||
) => {
|
||||
const store = useContext(ToolBlockStoreContext) ?? defaultToolBlockStore
|
||||
const selectContext = useCallback(
|
||||
(state: ToolBlockStoreState) => {
|
||||
if (selector)
|
||||
return selector(state.context)
|
||||
return state.context as T
|
||||
},
|
||||
[selector],
|
||||
)
|
||||
|
||||
return useStore(store, selectContext)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import * as React from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { useSelectOrDelete } from '@/app/components/base/prompt-editor/hooks'
|
||||
@@ -112,9 +113,25 @@ const ToolGroupBlockComponent = ({
|
||||
const authBadgeLabel = t('skillEditor.authorizationBadge', { ns: 'workflow' })
|
||||
const language = useGetLanguage()
|
||||
const { theme } = useTheme()
|
||||
const toolBlockContext = useToolBlockContext()
|
||||
const isUsingExternalMetadata = Boolean(toolBlockContext?.onMetadataChange)
|
||||
const useModal = Boolean(toolBlockContext?.useModal)
|
||||
const {
|
||||
metadata,
|
||||
onMetadataChange,
|
||||
useModal,
|
||||
nodeId: contextNodeId,
|
||||
nodesOutputVars,
|
||||
availableNodes,
|
||||
} = useToolBlockContext(
|
||||
useShallow(context => ({
|
||||
metadata: context?.metadata,
|
||||
onMetadataChange: context?.onMetadataChange,
|
||||
useModal: context?.useModal,
|
||||
nodeId: context?.nodeId,
|
||||
nodesOutputVars: context?.nodesOutputVars,
|
||||
availableNodes: context?.availableNodes,
|
||||
})),
|
||||
)
|
||||
const isUsingExternalMetadata = Boolean(onMetadataChange)
|
||||
const useModalValue = Boolean(useModal)
|
||||
const isInteractive = editor.isEditable()
|
||||
const [isSettingOpen, setIsSettingOpen] = useState(false)
|
||||
const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null)
|
||||
@@ -196,22 +213,22 @@ const ToolGroupBlockComponent = ({
|
||||
if (!activeToolItem)
|
||||
return undefined
|
||||
if (isUsingExternalMetadata) {
|
||||
const metadata = toolBlockContext?.metadata as SkillFileMetadata | undefined
|
||||
return metadata?.tools?.[activeToolItem.configId]
|
||||
const externalMetadata = metadata as SkillFileMetadata | undefined
|
||||
return externalMetadata?.tools?.[activeToolItem.configId]
|
||||
}
|
||||
if (!activeTabId)
|
||||
return undefined
|
||||
const metadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined
|
||||
return metadata?.tools?.[activeToolItem.configId]
|
||||
}, [activeTabId, activeToolItem, fileMetadata, isUsingExternalMetadata, toolBlockContext?.metadata])
|
||||
const resultMetadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined
|
||||
return resultMetadata?.tools?.[activeToolItem.configId]
|
||||
}, [activeTabId, activeToolItem, fileMetadata, isUsingExternalMetadata, metadata])
|
||||
|
||||
const metadataTools = useMemo(() => {
|
||||
if (isUsingExternalMetadata)
|
||||
return ((toolBlockContext?.metadata as SkillFileMetadata | undefined)?.tools || {}) as Record<string, ToolConfigMetadata>
|
||||
return ((metadata as SkillFileMetadata | undefined)?.tools || {}) as Record<string, ToolConfigMetadata>
|
||||
if (!activeTabId || activeTabId === START_TAB_ID)
|
||||
return {}
|
||||
return ((fileMetadata.get(activeTabId) as SkillFileMetadata | undefined)?.tools || {}) as Record<string, ToolConfigMetadata>
|
||||
}, [activeTabId, fileMetadata, isUsingExternalMetadata, toolBlockContext?.metadata])
|
||||
}, [activeTabId, fileMetadata, isUsingExternalMetadata, metadata])
|
||||
|
||||
const getVarKindType = (type: FormTypeEnum | string) => {
|
||||
if (type === FormTypeEnum.file || type === FormTypeEnum.files)
|
||||
@@ -348,7 +365,7 @@ const ToolGroupBlockComponent = ({
|
||||
}, [configuredToolValue, isSettingOpen])
|
||||
|
||||
useEffect(() => {
|
||||
if (useModal)
|
||||
if (useModalValue)
|
||||
return
|
||||
const containerFromRef = ref.current?.closest('[data-skill-editor-root="true"]') as HTMLElement | null
|
||||
const fallbackContainer = document.querySelector('[data-skill-editor-root="true"]') as HTMLElement | null
|
||||
@@ -356,10 +373,10 @@ const ToolGroupBlockComponent = ({
|
||||
if (container)
|
||||
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
|
||||
setPortalContainer(container)
|
||||
}, [ref, useModal])
|
||||
}, [ref, useModalValue])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSettingOpen || useModal)
|
||||
if (!isSettingOpen || useModalValue)
|
||||
return
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
@@ -384,7 +401,7 @@ const ToolGroupBlockComponent = ({
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||
}, [isSettingOpen, portalContainer, ref, useModal])
|
||||
}, [isSettingOpen, portalContainer, ref, useModalValue])
|
||||
|
||||
const resolvedEnabledByConfigId = useMemo(() => {
|
||||
const next = { ...enabledByConfigId }
|
||||
@@ -413,9 +430,9 @@ const ToolGroupBlockComponent = ({
|
||||
setToolValue(nextValue)
|
||||
const credentialId = normalizeCredentialId(nextValue.credential_id)
|
||||
if (isUsingExternalMetadata) {
|
||||
const metadata = (toolBlockContext?.metadata || {}) as SkillFileMetadata
|
||||
const externalMetadata = (metadata || {}) as SkillFileMetadata
|
||||
const toolType = currentProvider.type === CollectionType.mcp ? 'mcp' : 'builtin'
|
||||
const currentToolMetadata = (metadata.tools || {})[activeToolItem.configId]
|
||||
const currentToolMetadata = (externalMetadata.tools || {})[activeToolItem.configId]
|
||||
const buildFields = (value: Record<string, unknown> | undefined) => {
|
||||
if (!value)
|
||||
return []
|
||||
@@ -431,9 +448,9 @@ const ToolGroupBlockComponent = ({
|
||||
...buildFields(nextValue.parameters),
|
||||
]
|
||||
const nextMetadata: SkillFileMetadata = {
|
||||
...metadata,
|
||||
...externalMetadata,
|
||||
tools: {
|
||||
...(metadata.tools || {}),
|
||||
...(externalMetadata.tools || {}),
|
||||
[activeToolItem.configId]: {
|
||||
type: toolType,
|
||||
configuration: { fields },
|
||||
@@ -442,14 +459,14 @@ const ToolGroupBlockComponent = ({
|
||||
},
|
||||
},
|
||||
}
|
||||
toolBlockContext?.onMetadataChange?.(nextMetadata)
|
||||
onMetadataChange?.(nextMetadata)
|
||||
return
|
||||
}
|
||||
if (!activeTabId || activeTabId === START_TAB_ID)
|
||||
return
|
||||
const metadata = (fileMetadata.get(activeTabId) || {}) as SkillFileMetadata
|
||||
const currentMetaData = (fileMetadata.get(activeTabId) || {}) as SkillFileMetadata
|
||||
const toolType = currentProvider.type === CollectionType.mcp ? 'mcp' : 'builtin'
|
||||
const currentToolMetadata = (metadata.tools || {})[activeToolItem.configId]
|
||||
const currentToolMetadata = (currentMetaData.tools || {})[activeToolItem.configId]
|
||||
const buildFields = (value: Record<string, unknown> | undefined) => {
|
||||
if (!value)
|
||||
return []
|
||||
@@ -465,9 +482,9 @@ const ToolGroupBlockComponent = ({
|
||||
...buildFields(nextValue.parameters),
|
||||
]
|
||||
const nextMetadata: SkillFileMetadata = {
|
||||
...metadata,
|
||||
...currentMetaData,
|
||||
tools: {
|
||||
...(metadata.tools || {}),
|
||||
...(currentMetaData.tools || {}),
|
||||
[activeToolItem.configId]: {
|
||||
type: toolType,
|
||||
configuration: { fields },
|
||||
@@ -502,16 +519,16 @@ const ToolGroupBlockComponent = ({
|
||||
return nextMetadata
|
||||
}
|
||||
if (isUsingExternalMetadata) {
|
||||
toolBlockContext?.onMetadataChange?.(applyEnabled(toolBlockContext?.metadata as SkillFileMetadata | undefined))
|
||||
onMetadataChange?.(applyEnabled(metadata as SkillFileMetadata | undefined))
|
||||
return
|
||||
}
|
||||
if (!activeTabId || activeTabId === START_TAB_ID)
|
||||
return
|
||||
const metadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined
|
||||
const nextMetadata = applyEnabled(metadata)
|
||||
const currentMetadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined
|
||||
const nextMetadata = applyEnabled(currentMetadata)
|
||||
storeApi.getState().setDraftMetadata(activeTabId, nextMetadata)
|
||||
storeApi.getState().pinTab(activeTabId)
|
||||
}, [activeTabId, currentProvider?.type, fileMetadata, isUsingExternalMetadata, storeApi, toolBlockContext])
|
||||
}, [activeTabId, currentProvider?.type, fileMetadata, isUsingExternalMetadata, metadata, onMetadataChange, storeApi])
|
||||
|
||||
const renderIcon = () => {
|
||||
if (!resolvedIcon)
|
||||
@@ -611,10 +628,10 @@ const ToolGroupBlockComponent = ({
|
||||
currentTool={currentTool}
|
||||
value={toolValue}
|
||||
onChange={handleToolValueChange}
|
||||
nodeId={toolBlockContext?.nodeId}
|
||||
nodesOutputVars={toolBlockContext?.nodesOutputVars}
|
||||
availableNodes={toolBlockContext?.availableNodes}
|
||||
enableVariableReference={useModal}
|
||||
nodeId={contextNodeId}
|
||||
nodesOutputVars={nodesOutputVars}
|
||||
availableNodes={availableNodes}
|
||||
enableVariableReference={useModalValue}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@@ -724,13 +741,13 @@ const ToolGroupBlockComponent = ({
|
||||
return nextMetadata
|
||||
}
|
||||
if (isUsingExternalMetadata) {
|
||||
toolBlockContext?.onMetadataChange?.(applyCredential(toolBlockContext?.metadata as SkillFileMetadata | undefined))
|
||||
onMetadataChange?.(applyCredential(metadata as SkillFileMetadata | undefined))
|
||||
return
|
||||
}
|
||||
if (!activeTabId || activeTabId === START_TAB_ID)
|
||||
return
|
||||
const metadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined
|
||||
const nextMetadata = applyCredential(metadata)
|
||||
const currentMetadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined
|
||||
const nextMetadata = applyCredential(currentMetadata)
|
||||
storeApi.getState().setDraftMetadata(activeTabId, nextMetadata)
|
||||
storeApi.getState().pinTab(activeTabId)
|
||||
}}
|
||||
@@ -841,7 +858,7 @@ const ToolGroupBlockComponent = ({
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
{useModal && (
|
||||
{useModalValue && (
|
||||
<Modal
|
||||
isShow={isSettingOpen}
|
||||
onClose={() => {
|
||||
@@ -856,7 +873,7 @@ const ToolGroupBlockComponent = ({
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
{!useModal && portalContainer && isSettingOpen && createPortal(
|
||||
{!useModalValue && portalContainer && isSettingOpen && createPortal(
|
||||
<div
|
||||
className="absolute bottom-4 right-4 top-4 z-[99]"
|
||||
data-tool-group-setting-panel="true"
|
||||
|
||||
@@ -13,6 +13,7 @@ import * as React from 'react'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { useBasicTypeaheadTriggerMatch } from '@/app/components/base/prompt-editor/hooks'
|
||||
import { $splitNodeContainingQuery } from '@/app/components/base/prompt-editor/utils'
|
||||
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
@@ -42,8 +43,13 @@ const ToolPickerBlock = ({ scope = 'all', enableAutoDefault = false }: ToolPicke
|
||||
maxLength: 75,
|
||||
})
|
||||
const storeApi = useWorkflowStore()
|
||||
const toolBlockContext = useToolBlockContext()
|
||||
const isUsingExternalMetadata = Boolean(toolBlockContext?.onMetadataChange)
|
||||
const { metadata, onMetadataChange } = useToolBlockContext(
|
||||
useShallow(context => ({
|
||||
metadata: context?.metadata,
|
||||
onMetadataChange: context?.onMetadataChange,
|
||||
})),
|
||||
)
|
||||
const isUsingExternalMetadata = Boolean(onMetadataChange)
|
||||
const [queryString, setQueryString] = useState('')
|
||||
|
||||
const canUseAutoByType = useCallback(
|
||||
@@ -133,21 +139,21 @@ const ToolPickerBlock = ({ scope = 'all', enableAutoDefault = false }: ToolPicke
|
||||
})
|
||||
|
||||
if (isUsingExternalMetadata) {
|
||||
const metadata = (toolBlockContext?.metadata || {}) as Record<string, unknown>
|
||||
const nextMetadata = buildNextMetadata(metadata, toolEntries)
|
||||
toolBlockContext?.onMetadataChange?.(nextMetadata)
|
||||
const externalMetadata = (metadata || {}) as Record<string, unknown>
|
||||
const nextMetadata = buildNextMetadata(externalMetadata, toolEntries)
|
||||
onMetadataChange?.(nextMetadata)
|
||||
return
|
||||
}
|
||||
const { activeTabId, fileMetadata, setDraftMetadata, pinTab } = storeApi.getState()
|
||||
if (!activeTabId || activeTabId === START_TAB_ID)
|
||||
return
|
||||
const metadata = (fileMetadata.get(activeTabId) || {}) as Record<string, unknown>
|
||||
const nextMetadata = buildNextMetadata(metadata, toolEntries)
|
||||
const currentMetadata = (fileMetadata.get(activeTabId) || {}) as Record<string, unknown>
|
||||
const nextMetadata = buildNextMetadata(currentMetadata, toolEntries)
|
||||
setDraftMetadata(activeTabId, {
|
||||
...nextMetadata,
|
||||
})
|
||||
pinTab(activeTabId)
|
||||
}, [buildNextMetadata, editor, getMatchFromSelection, isUsingExternalMetadata, storeApi, toolBlockContext])
|
||||
}, [buildNextMetadata, editor, getMatchFromSelection, isUsingExternalMetadata, metadata, onMetadataChange, storeApi])
|
||||
|
||||
const renderMenu = useCallback((
|
||||
anchorElementRef: React.RefObject<HTMLElement | null>,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user