mirror of
https://github.com/langgenius/dify.git
synced 2026-01-07 14:58:32 +00:00
merge main
This commit is contained in:
@@ -47,6 +47,8 @@ RUN \
|
||||
curl nodejs libgmp-dev libmpfr-dev libmpc-dev \
|
||||
# For Security
|
||||
expat libldap-2.5-0 perl libsqlite3-0 zlib1g \
|
||||
# install fonts to support the use of tools like pypdfium2
|
||||
fonts-noto-cjk \
|
||||
# install a package to improve the accuracy of guessing mime type and file extension
|
||||
media-types \
|
||||
# install libmagic to support the use of python-magic guess MIMETYPE
|
||||
|
||||
@@ -69,7 +69,7 @@ class MCPClient:
|
||||
|
||||
parsed_url = urlparse(self.server_url)
|
||||
path = parsed_url.path or ""
|
||||
method_name = path.removesuffix("/").lower()
|
||||
method_name = path.rstrip("/").split("/")[-1] if path else ""
|
||||
if method_name in connection_methods:
|
||||
client_factory = connection_methods[method_name]
|
||||
self.connect_server(client_factory, method_name)
|
||||
|
||||
@@ -32,6 +32,13 @@ class MarketplacePluginDeclaration(BaseModel):
|
||||
latest_package_identifier: str = Field(
|
||||
..., description="Unique identifier for the latest package release of the plugin"
|
||||
)
|
||||
status: str = Field(..., description="Indicate the status of marketplace plugin, enum from `active` `deleted`")
|
||||
deprecated_reason: str = Field(
|
||||
..., description="Not empty when status='deleted', indicates the reason why this plugin is deleted(deprecated)"
|
||||
)
|
||||
alternative_plugin_id: str = Field(
|
||||
..., description="Optional, indicates the alternative plugin for user to switch to"
|
||||
)
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
|
||||
@@ -206,9 +206,19 @@ class TencentVector(BaseVector):
|
||||
def delete_by_ids(self, ids: list[str]) -> None:
|
||||
if not ids:
|
||||
return
|
||||
self._client.delete(
|
||||
database_name=self._client_config.database, collection_name=self.collection_name, document_ids=ids
|
||||
)
|
||||
|
||||
total_count = len(ids)
|
||||
batch_size = self._client_config.max_upsert_batch_size
|
||||
batch = math.ceil(total_count / batch_size)
|
||||
|
||||
for j in range(batch):
|
||||
start_idx = j * batch_size
|
||||
end_idx = min(total_count, (j + 1) * batch_size)
|
||||
batch_ids = ids[start_idx:end_idx]
|
||||
|
||||
self._client.delete(
|
||||
database_name=self._client_config.database, collection_name=self.collection_name, document_ids=batch_ids
|
||||
)
|
||||
|
||||
def delete_by_metadata_field(self, key: str, value: str) -> None:
|
||||
self._client.delete(
|
||||
|
||||
@@ -486,7 +486,7 @@ class AgentNode(BaseNode):
|
||||
|
||||
text = ""
|
||||
files: list[File] = []
|
||||
json: list[dict] = []
|
||||
json_list: list[dict] = []
|
||||
|
||||
agent_logs: list[AgentLogEvent] = []
|
||||
agent_execution_metadata: Mapping[WorkflowNodeExecutionMetadataKey, Any] = {}
|
||||
@@ -564,7 +564,7 @@ class AgentNode(BaseNode):
|
||||
if key in WorkflowNodeExecutionMetadataKey.__members__.values()
|
||||
}
|
||||
if message.message.json_object is not None:
|
||||
json.append(message.message.json_object)
|
||||
json_list.append(message.message.json_object)
|
||||
elif message.type == ToolInvokeMessage.MessageType.LINK:
|
||||
assert isinstance(message.message, ToolInvokeMessage.TextMessage)
|
||||
stream_text = f"Link: {message.message.text}\n"
|
||||
@@ -676,8 +676,8 @@ class AgentNode(BaseNode):
|
||||
}
|
||||
)
|
||||
# Step 2: normalize JSON into {"data": [...]}.change json to list[dict]
|
||||
if json:
|
||||
json_output.extend(json)
|
||||
if json_list:
|
||||
json_output.extend(json_list)
|
||||
else:
|
||||
json_output.append({"data": []})
|
||||
|
||||
|
||||
@@ -163,6 +163,7 @@ class WorkflowEntry:
|
||||
graph=graph,
|
||||
graph_runtime_state=GraphRuntimeState(variable_pool=variable_pool, start_at=time.perf_counter()),
|
||||
)
|
||||
node.init_node_data(node_config_data)
|
||||
|
||||
try:
|
||||
# variable selector to variable mapping
|
||||
@@ -273,6 +274,7 @@ class WorkflowEntry:
|
||||
graph=graph,
|
||||
graph_runtime_state=GraphRuntimeState(variable_pool=variable_pool, start_at=time.perf_counter()),
|
||||
)
|
||||
node.init_node_data(node_data)
|
||||
|
||||
try:
|
||||
# variable selector to variable mapping
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 16081485540c
|
||||
Revises: d28f2004b072
|
||||
Create Date: 2025-05-15 16:35:39.113777
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import models as models
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '16081485540c'
|
||||
down_revision = '2adcbe1f5dfb'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('tenant_plugin_auto_upgrade_strategies',
|
||||
sa.Column('id', models.types.StringUUID(), server_default=sa.text('uuid_generate_v4()'), nullable=False),
|
||||
sa.Column('tenant_id', models.types.StringUUID(), nullable=False),
|
||||
sa.Column('strategy_setting', sa.String(length=16), server_default='fix_only', nullable=False),
|
||||
sa.Column('upgrade_time_of_day', sa.Integer(), nullable=False),
|
||||
sa.Column('upgrade_mode', sa.String(length=16), server_default='exclude', nullable=False),
|
||||
sa.Column('exclude_plugins', sa.ARRAY(sa.String(length=255)), nullable=False),
|
||||
sa.Column('include_plugins', sa.ARRAY(sa.String(length=255)), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id', name='tenant_plugin_auto_upgrade_strategy_pkey'),
|
||||
sa.UniqueConstraint('tenant_id', name='unique_tenant_plugin_auto_upgrade_strategy')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('tenant_plugin_auto_upgrade_strategies')
|
||||
# ### end Alembic commands ###
|
||||
@@ -12,7 +12,7 @@ import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '4474872b0ee6'
|
||||
down_revision = '16081485540c'
|
||||
down_revision = '2adcbe1f5dfb'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ class PluginService:
|
||||
plugin_id: str
|
||||
version: str
|
||||
unique_identifier: str
|
||||
status: str
|
||||
deprecated_reason: str
|
||||
alternative_plugin_id: str
|
||||
|
||||
REDIS_KEY_PREFIX = "plugin_service:latest_plugin:"
|
||||
REDIS_TTL = 60 * 5 # 5 minutes
|
||||
@@ -71,6 +74,9 @@ class PluginService:
|
||||
plugin_id=plugin_id,
|
||||
version=manifest.latest_version,
|
||||
unique_identifier=manifest.latest_package_identifier,
|
||||
status=manifest.status,
|
||||
deprecated_reason=manifest.deprecated_reason,
|
||||
alternative_plugin_id=manifest.alternative_plugin_id,
|
||||
)
|
||||
|
||||
# Store in Redis
|
||||
|
||||
150
api/tests/unit_tests/services/auth/test_auth_type.py
Normal file
150
api/tests/unit_tests/services/auth/test_auth_type.py
Normal file
@@ -0,0 +1,150 @@
|
||||
import pytest
|
||||
|
||||
from services.auth.auth_type import AuthType
|
||||
|
||||
|
||||
class TestAuthType:
|
||||
"""Test cases for AuthType enum"""
|
||||
|
||||
def test_auth_type_is_str_enum(self):
|
||||
"""Test that AuthType is properly a StrEnum"""
|
||||
assert issubclass(AuthType, str)
|
||||
assert hasattr(AuthType, "__members__")
|
||||
|
||||
def test_auth_type_has_expected_values(self):
|
||||
"""Test that all expected auth types exist with correct values"""
|
||||
expected_values = {
|
||||
"FIRECRAWL": "firecrawl",
|
||||
"WATERCRAWL": "watercrawl",
|
||||
"JINA": "jinareader",
|
||||
}
|
||||
|
||||
# Verify all expected members exist
|
||||
for member_name, expected_value in expected_values.items():
|
||||
assert hasattr(AuthType, member_name)
|
||||
assert getattr(AuthType, member_name).value == expected_value
|
||||
|
||||
# Verify no extra members exist
|
||||
assert len(AuthType) == len(expected_values)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("auth_type", "expected_string"),
|
||||
[
|
||||
(AuthType.FIRECRAWL, "firecrawl"),
|
||||
(AuthType.WATERCRAWL, "watercrawl"),
|
||||
(AuthType.JINA, "jinareader"),
|
||||
],
|
||||
)
|
||||
def test_auth_type_string_representation(self, auth_type, expected_string):
|
||||
"""Test string representation of auth types"""
|
||||
assert str(auth_type) == expected_string
|
||||
assert auth_type.value == expected_string
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("auth_type", "compare_value", "expected_result"),
|
||||
[
|
||||
(AuthType.FIRECRAWL, "firecrawl", True),
|
||||
(AuthType.WATERCRAWL, "watercrawl", True),
|
||||
(AuthType.JINA, "jinareader", True),
|
||||
(AuthType.FIRECRAWL, "FIRECRAWL", False), # Case sensitive
|
||||
(AuthType.FIRECRAWL, "watercrawl", False),
|
||||
(AuthType.JINA, "jina", False), # Full value mismatch
|
||||
],
|
||||
)
|
||||
def test_auth_type_comparison(self, auth_type, compare_value, expected_result):
|
||||
"""Test auth type comparison with strings"""
|
||||
assert (auth_type == compare_value) is expected_result
|
||||
|
||||
def test_auth_type_iteration(self):
|
||||
"""Test that AuthType can be iterated over"""
|
||||
auth_types = list(AuthType)
|
||||
assert len(auth_types) == 3
|
||||
assert AuthType.FIRECRAWL in auth_types
|
||||
assert AuthType.WATERCRAWL in auth_types
|
||||
assert AuthType.JINA in auth_types
|
||||
|
||||
def test_auth_type_membership(self):
|
||||
"""Test membership checking for AuthType"""
|
||||
assert "firecrawl" in [auth.value for auth in AuthType]
|
||||
assert "watercrawl" in [auth.value for auth in AuthType]
|
||||
assert "jinareader" in [auth.value for auth in AuthType]
|
||||
assert "invalid" not in [auth.value for auth in AuthType]
|
||||
|
||||
def test_auth_type_invalid_attribute_access(self):
|
||||
"""Test accessing non-existent auth type raises AttributeError"""
|
||||
with pytest.raises(AttributeError):
|
||||
_ = AuthType.INVALID_TYPE
|
||||
|
||||
def test_auth_type_immutability(self):
|
||||
"""Test that enum values cannot be modified"""
|
||||
# In Python 3.11+, enum members are read-only
|
||||
with pytest.raises(AttributeError):
|
||||
AuthType.FIRECRAWL = "modified"
|
||||
|
||||
def test_auth_type_from_value(self):
|
||||
"""Test creating AuthType from string value"""
|
||||
assert AuthType("firecrawl") == AuthType.FIRECRAWL
|
||||
assert AuthType("watercrawl") == AuthType.WATERCRAWL
|
||||
assert AuthType("jinareader") == AuthType.JINA
|
||||
|
||||
# Test invalid value
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
AuthType("invalid_auth_type")
|
||||
assert "invalid_auth_type" in str(exc_info.value)
|
||||
|
||||
def test_auth_type_name_property(self):
|
||||
"""Test the name property of enum members"""
|
||||
assert AuthType.FIRECRAWL.name == "FIRECRAWL"
|
||||
assert AuthType.WATERCRAWL.name == "WATERCRAWL"
|
||||
assert AuthType.JINA.name == "JINA"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"auth_type",
|
||||
[AuthType.FIRECRAWL, AuthType.WATERCRAWL, AuthType.JINA],
|
||||
)
|
||||
def test_auth_type_isinstance_checks(self, auth_type):
|
||||
"""Test isinstance checks for auth types"""
|
||||
assert isinstance(auth_type, AuthType)
|
||||
assert isinstance(auth_type, str)
|
||||
assert isinstance(auth_type.value, str)
|
||||
|
||||
def test_auth_type_hash(self):
|
||||
"""Test that auth types are hashable and can be used in sets/dicts"""
|
||||
auth_set = {AuthType.FIRECRAWL, AuthType.WATERCRAWL, AuthType.JINA}
|
||||
assert len(auth_set) == 3
|
||||
|
||||
auth_dict = {
|
||||
AuthType.FIRECRAWL: "firecrawl_handler",
|
||||
AuthType.WATERCRAWL: "watercrawl_handler",
|
||||
AuthType.JINA: "jina_handler",
|
||||
}
|
||||
assert auth_dict[AuthType.FIRECRAWL] == "firecrawl_handler"
|
||||
|
||||
def test_auth_type_json_serializable(self):
|
||||
"""Test that auth types can be JSON serialized"""
|
||||
import json
|
||||
|
||||
auth_data = {
|
||||
"provider": AuthType.FIRECRAWL,
|
||||
"enabled": True,
|
||||
}
|
||||
|
||||
# Should serialize to string value
|
||||
json_str = json.dumps(auth_data, default=str)
|
||||
assert '"provider": "firecrawl"' in json_str
|
||||
|
||||
def test_auth_type_matches_factory_usage(self):
|
||||
"""Test that all AuthType values are handled by ApiKeyAuthFactory"""
|
||||
# This test verifies that the enum values match what's expected
|
||||
# by the factory implementation
|
||||
from services.auth.api_key_auth_factory import ApiKeyAuthFactory
|
||||
|
||||
for auth_type in AuthType:
|
||||
# Should not raise ValueError for valid auth types
|
||||
try:
|
||||
auth_class = ApiKeyAuthFactory.get_apikey_auth_factory(auth_type)
|
||||
assert auth_class is not None
|
||||
except ImportError:
|
||||
# It's OK if the actual auth implementation doesn't exist
|
||||
# We're just testing that the enum value is recognized
|
||||
pass
|
||||
@@ -47,7 +47,7 @@ const AccessControlDialog = ({
|
||||
>
|
||||
<Dialog.Panel className={cn('relative h-auto min-h-[323px] w-[600px] overflow-y-auto rounded-2xl bg-components-panel-bg p-0 shadow-xl transition-all', className)}>
|
||||
<div onClick={() => close()} className="absolute right-5 top-5 z-10 flex h-8 w-8 cursor-pointer items-center justify-center">
|
||||
<RiCloseLine className='h-5 w-5' />
|
||||
<RiCloseLine className='h-5 w-5 text-text-tertiary' />
|
||||
</div>
|
||||
{children}
|
||||
</Dialog.Panel>
|
||||
|
||||
@@ -72,7 +72,7 @@ export default function AccessControl(props: AccessControlProps) {
|
||||
</div>
|
||||
<div className='flex flex-col gap-y-1 px-6 pb-3'>
|
||||
<div className='leading-6'>
|
||||
<p className='system-sm-medium'>{t('app.accessControlDialog.accessLabel')}</p>
|
||||
<p className='system-sm-medium text-text-tertiary'>{t('app.accessControlDialog.accessLabel')}</p>
|
||||
</div>
|
||||
<AccessControlItem type={AccessMode.ORGANIZATION}>
|
||||
<div className='flex items-center p-3'>
|
||||
|
||||
@@ -35,6 +35,22 @@ export type FileUpload = {
|
||||
number_limits?: number
|
||||
transfer_methods?: TransferMethod[]
|
||||
}
|
||||
document?: EnabledOrDisabled & {
|
||||
number_limits?: number
|
||||
transfer_methods?: TransferMethod[]
|
||||
}
|
||||
audio?: EnabledOrDisabled & {
|
||||
number_limits?: number
|
||||
transfer_methods?: TransferMethod[]
|
||||
}
|
||||
video?: EnabledOrDisabled & {
|
||||
number_limits?: number
|
||||
transfer_methods?: TransferMethod[]
|
||||
}
|
||||
custom?: EnabledOrDisabled & {
|
||||
number_limits?: number
|
||||
transfer_methods?: TransferMethod[]
|
||||
}
|
||||
allowed_file_types?: string[]
|
||||
allowed_file_extensions?: string[]
|
||||
allowed_file_upload_methods?: TransferMethod[]
|
||||
|
||||
@@ -580,11 +580,30 @@ The text generation application offers non-session support and is ideal for tran
|
||||
- `default` (string) Default value
|
||||
- `options` (array[string]) Option values
|
||||
- `file_upload` (object) File upload configuration
|
||||
- `image` (object) Image settings
|
||||
Currently only supports image types: `png`, `jpg`, `jpeg`, `webp`, `gif`
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Image number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods, remote_url, local_file, must choose one
|
||||
- `document` (object) Document settings
|
||||
Currently only supports document types: `txt`, `md`, `markdown`, `pdf`, `html`, `xlsx`, `xls`, `docx`, `csv`, `eml`, `msg`, `pptx`, `ppt`, `xml`, `epub`.
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Document number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods: `remote_url`, `local_file`. Must choose one.
|
||||
- `image` (object) Image settings
|
||||
Currently only supports image types: `png`, `jpg`, `jpeg`, `webp`, `gif`.
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Image number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods: `remote_url`, `local_file`. Must choose one.
|
||||
- `audio` (object) Audio settings
|
||||
Currently only supports audio types: `mp3`, `m4a`, `wav`, `webm`, `amr`.
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Audio number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods: `remote_url`, `local_file`. Must choose one.
|
||||
- `video` (object) Video settings
|
||||
Currently only supports video types: `mp4`, `mov`, `mpeg`, `mpga`.
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Video number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods: `remote_url`, `local_file`. Must choose one.
|
||||
- `custom` (object) Custom settings
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Custom number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods: `remote_url`, `local_file`. Must choose one.
|
||||
- `system_parameters` (object) System parameters
|
||||
- `file_size_limit` (int) Document upload size limit (MB)
|
||||
- `image_file_size_limit` (int) Image file upload size limit (MB)
|
||||
|
||||
@@ -578,11 +578,30 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
- `default` (string) デフォルト値
|
||||
- `options` (array[string]) オプション値
|
||||
- `file_upload` (object) ファイルアップロード設定
|
||||
- `image` (object) 画像設定
|
||||
現在は画像タイプのみ対応:`png`、`jpg`、`jpeg`、`webp`、`gif`
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) 画像数制限、デフォルトは3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト、remote_url、local_file、いずれかを選択
|
||||
- `document` (object) ドキュメント設定
|
||||
現在サポートされているドキュメントタイプ:`txt`, `md`, `markdown`, `pdf`, `html`, `xlsx`, `xls`, `docx`, `csv`, `eml`, `msg`, `pptx`, `ppt`, `xml`, `epub`。
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) ドキュメント数の上限。デフォルトは 3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト:`remote_url`, `local_file`。いずれかを選択する必要があります。
|
||||
- `image` (object) 画像設定
|
||||
現在サポートされている画像タイプ:`png`, `jpg`, `jpeg`, `webp`, `gif`。
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) 画像数の上限。デフォルトは 3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト:`remote_url`, `local_file`。いずれかを選択する必要があります。
|
||||
- `audio` (object) オーディオ設定
|
||||
現在サポートされているオーディオタイプ:`mp3`, `m4a`, `wav`, `webm`, `amr`。
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) オーディオ数の上限。デフォルトは 3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト:`remote_url`, `local_file`。いずれかを選択する必要があります。
|
||||
- `video` (object) ビデオ設定
|
||||
現在サポートされているビデオタイプ:`mp4`, `mov`, `mpeg`, `mpga`。
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) ビデオ数の上限。デフォルトは 3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト:`remote_url`, `local_file`。いずれかを選択する必要があります。
|
||||
- `custom` (object) カスタム設定
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) カスタム数の上限。デフォルトは 3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト:`remote_url`, `local_file`。いずれかを選択する必要があります。
|
||||
- `system_parameters` (object) システムパラメータ
|
||||
- `file_size_limit` (int) ドキュメントアップロードサイズ制限(MB)
|
||||
- `image_file_size_limit` (int) 画像ファイルアップロードサイズ制限(MB)
|
||||
|
||||
@@ -552,11 +552,30 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
- `default` (string) 默认值
|
||||
- `options` (array[string]) 选项值
|
||||
- `file_upload` (object) 文件上传配置
|
||||
- `image` (object) 图片设置
|
||||
当前仅支持图片类型:`png`, `jpg`, `jpeg`, `webp`, `gif`
|
||||
- `enabled` (bool) 是否开启
|
||||
- `number_limits` (int) 图片数量限制,默认 3
|
||||
- `transfer_methods` (array[string]) 传递方式列表,remote_url , local_file,必选一个
|
||||
- `document` (object) 文档设置
|
||||
当前仅支持文档类型:`txt`, `md`, `markdown`, `pdf`, `html`, `xlsx`, `xls`, `docx`, `csv`, `eml`, `msg`, `pptx`, `ppt`, `xml`, `epub`。
|
||||
- `enabled` (bool) 是否启用
|
||||
- `number_limits` (int) 文档数量限制,默认为 3
|
||||
- `transfer_methods` (array[string]) 传输方式列表:`remote_url`, `local_file`,必须选择一个。
|
||||
- `image` (object) 图片设置
|
||||
当前仅支持图片类型:`png`, `jpg`, `jpeg`, `webp`, `gif`。
|
||||
- `enabled` (bool) 是否启用
|
||||
- `number_limits` (int) 图片数量限制,默认为 3
|
||||
- `transfer_methods` (array[string]) 传输方式列表:`remote_url`, `local_file`,必须选择一个。
|
||||
- `audio` (object) 音频设置
|
||||
当前仅支持音频类型:`mp3`, `m4a`, `wav`, `webm`, `amr`。
|
||||
- `enabled` (bool) 是否启用
|
||||
- `number_limits` (int) 音频数量限制,默认为 3
|
||||
- `transfer_methods` (array[string]) 传输方式列表:`remote_url`, `local_file`,必须选择一个。
|
||||
- `video` (object) 视频设置
|
||||
当前仅支持视频类型:`mp4`, `mov`, `mpeg`, `mpga`。
|
||||
- `enabled` (bool) 是否启用
|
||||
- `number_limits` (int) 视频数量限制,默认为 3
|
||||
- `transfer_methods` (array[string]) 传输方式列表:`remote_url`, `local_file`,必须选择一个。
|
||||
- `custom` (object) 自定义设置
|
||||
- `enabled` (bool) 是否启用
|
||||
- `number_limits` (int) 自定义数量限制,默认为 3
|
||||
- `transfer_methods` (array[string]) 传输方式列表:`remote_url`, `local_file`,必须选择一个。
|
||||
- `system_parameters` (object) 系统参数
|
||||
- `file_size_limit` (int) 文档上传大小限制 (MB)
|
||||
- `image_file_size_limit` (int) 图片文件上传大小限制(MB)
|
||||
|
||||
@@ -1197,11 +1197,30 @@ Chat applications support session persistence, allowing previous chat history to
|
||||
- `default` (string) Default value
|
||||
- `options` (array[string]) Option values
|
||||
- `file_upload` (object) File upload configuration
|
||||
- `image` (object) Image settings
|
||||
Currently only supports image types: `png`, `jpg`, `jpeg`, `webp`, `gif`
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Image number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods, remote_url, local_file, must choose one
|
||||
- `document` (object) Document settings
|
||||
Currently only supports document types: `txt`, `md`, `markdown`, `pdf`, `html`, `xlsx`, `xls`, `docx`, `csv`, `eml`, `msg`, `pptx`, `ppt`, `xml`, `epub`.
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Document number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods: `remote_url`, `local_file`. Must choose one.
|
||||
- `image` (object) Image settings
|
||||
Currently only supports image types: `png`, `jpg`, `jpeg`, `webp`, `gif`.
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Image number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods: `remote_url`, `local_file`. Must choose one.
|
||||
- `audio` (object) Audio settings
|
||||
Currently only supports audio types: `mp3`, `m4a`, `wav`, `webm`, `amr`.
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Audio number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods: `remote_url`, `local_file`. Must choose one.
|
||||
- `video` (object) Video settings
|
||||
Currently only supports video types: `mp4`, `mov`, `mpeg`, `mpga`.
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Video number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods: `remote_url`, `local_file`. Must choose one.
|
||||
- `custom` (object) Custom settings
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Custom number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods: `remote_url`, `local_file`. Must choose one.
|
||||
- `system_parameters` (object) System parameters
|
||||
- `file_size_limit` (int) Document upload size limit (MB)
|
||||
- `image_file_size_limit` (int) Image file upload size limit (MB)
|
||||
|
||||
@@ -1197,11 +1197,30 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
- `default` (string) デフォルト値
|
||||
- `options` (array[string]) オプション値
|
||||
- `file_upload` (object) ファイルアップロード設定
|
||||
- `image` (object) 画像設定
|
||||
現在サポートされている画像タイプ:`png`, `jpg`, `jpeg`, `webp`, `gif`
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) 画像数の制限、デフォルトは3
|
||||
- `transfer_methods` (array[string]) 転送方法のリスト、remote_url, local_file、いずれかを選択する必要があります
|
||||
- `document` (object) ドキュメント設定
|
||||
現在サポートされているドキュメントタイプ:`txt`, `md`, `markdown`, `pdf`, `html`, `xlsx`, `xls`, `docx`, `csv`, `eml`, `msg`, `pptx`, `ppt`, `xml`, `epub`。
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) ドキュメント数の上限。デフォルトは 3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト:`remote_url`, `local_file`。いずれかを選択する必要があります。
|
||||
- `image` (object) 画像設定
|
||||
現在サポートされている画像タイプ:`png`, `jpg`, `jpeg`, `webp`, `gif`。
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) 画像数の上限。デフォルトは 3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト:`remote_url`, `local_file`。いずれかを選択する必要があります。
|
||||
- `audio` (object) オーディオ設定
|
||||
現在サポートされているオーディオタイプ:`mp3`, `m4a`, `wav`, `webm`, `amr`。
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) オーディオ数の上限。デフォルトは 3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト:`remote_url`, `local_file`。いずれかを選択する必要があります。
|
||||
- `video` (object) ビデオ設定
|
||||
現在サポートされているビデオタイプ:`mp4`, `mov`, `mpeg`, `mpga`。
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) ビデオ数の上限。デフォルトは 3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト:`remote_url`, `local_file`。いずれかを選択する必要があります。
|
||||
- `custom` (object) カスタム設定
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) カスタム数の上限。デフォルトは 3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト:`remote_url`, `local_file`。いずれかを選択する必要があります。
|
||||
- `system_parameters` (object) システムパラメータ
|
||||
- `file_size_limit` (int) ドキュメントアップロードサイズ制限(MB)
|
||||
- `image_file_size_limit` (int) 画像ファイルアップロードサイズ制限(MB)
|
||||
|
||||
@@ -1229,11 +1229,30 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
- `default` (string) 默认值
|
||||
- `options` (array[string]) 选项值
|
||||
- `file_upload` (object) 文件上传配置
|
||||
- `image` (object) 图片设置
|
||||
当前仅支持图片类型:`png`, `jpg`, `jpeg`, `webp`, `gif`
|
||||
- `enabled` (bool) 是否开启
|
||||
- `number_limits` (int) 图片数量限制,默认 3
|
||||
- `transfer_methods` (array[string]) 传递方式列表,remote_url , local_file,必选一个
|
||||
- `document` (object) 文档设置
|
||||
当前仅支持文档类型:`txt`, `md`, `markdown`, `pdf`, `html`, `xlsx`, `xls`, `docx`, `csv`, `eml`, `msg`, `pptx`, `ppt`, `xml`, `epub`。
|
||||
- `enabled` (bool) 是否启用
|
||||
- `number_limits` (int) 文档数量限制,默认为 3
|
||||
- `transfer_methods` (array[string]) 传输方式列表:`remote_url`, `local_file`,必须选择一个。
|
||||
- `image` (object) 图片设置
|
||||
当前仅支持图片类型:`png`, `jpg`, `jpeg`, `webp`, `gif`。
|
||||
- `enabled` (bool) 是否启用
|
||||
- `number_limits` (int) 图片数量限制,默认为 3
|
||||
- `transfer_methods` (array[string]) 传输方式列表:`remote_url`, `local_file`,必须选择一个。
|
||||
- `audio` (object) 音频设置
|
||||
当前仅支持音频类型:`mp3`, `m4a`, `wav`, `webm`, `amr`。
|
||||
- `enabled` (bool) 是否启用
|
||||
- `number_limits` (int) 音频数量限制,默认为 3
|
||||
- `transfer_methods` (array[string]) 传输方式列表:`remote_url`, `local_file`,必须选择一个。
|
||||
- `video` (object) 视频设置
|
||||
当前仅支持视频类型:`mp4`, `mov`, `mpeg`, `mpga`。
|
||||
- `enabled` (bool) 是否启用
|
||||
- `number_limits` (int) 视频数量限制,默认为 3
|
||||
- `transfer_methods` (array[string]) 传输方式列表:`remote_url`, `local_file`,必须选择一个。
|
||||
- `custom` (object) 自定义设置
|
||||
- `enabled` (bool) 是否启用
|
||||
- `number_limits` (int) 自定义数量限制,默认为 3
|
||||
- `transfer_methods` (array[string]) 传输方式列表:`remote_url`, `local_file`,必须选择一个。
|
||||
- `system_parameters` (object) 系统参数
|
||||
- `file_size_limit` (int) Document upload size limit (MB)
|
||||
- `image_file_size_limit` (int) Image file upload size limit (MB)
|
||||
|
||||
@@ -1234,11 +1234,30 @@ Chat applications support session persistence, allowing previous chat history to
|
||||
- `default` (string) Default value
|
||||
- `options` (array[string]) Option values
|
||||
- `file_upload` (object) File upload configuration
|
||||
- `image` (object) Image settings
|
||||
Currently only supports image types: `png`, `jpg`, `jpeg`, `webp`, `gif`
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Image number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods, remote_url, local_file, must choose one
|
||||
- `document` (object) Document settings
|
||||
Currently only supports document types: `txt`, `md`, `markdown`, `pdf`, `html`, `xlsx`, `xls`, `docx`, `csv`, `eml`, `msg`, `pptx`, `ppt`, `xml`, `epub`.
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Document number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods: `remote_url`, `local_file`. Must choose one.
|
||||
- `image` (object) Image settings
|
||||
Currently only supports image types: `png`, `jpg`, `jpeg`, `webp`, `gif`.
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Image number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods: `remote_url`, `local_file`. Must choose one.
|
||||
- `audio` (object) Audio settings
|
||||
Currently only supports audio types: `mp3`, `m4a`, `wav`, `webm`, `amr`.
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Audio number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods: `remote_url`, `local_file`. Must choose one.
|
||||
- `video` (object) Video settings
|
||||
Currently only supports video types: `mp4`, `mov`, `mpeg`, `mpga`.
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Video number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods: `remote_url`, `local_file`. Must choose one.
|
||||
- `custom` (object) Custom settings
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Custom number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods: `remote_url`, `local_file`. Must choose one.
|
||||
- `system_parameters` (object) System parameters
|
||||
- `file_size_limit` (int) Document upload size limit (MB)
|
||||
- `image_file_size_limit` (int) Image file upload size limit (MB)
|
||||
|
||||
@@ -1224,12 +1224,31 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
- `required` (bool) 必須かどうか
|
||||
- `default` (string) デフォルト値
|
||||
- `options` (array[string]) オプション値
|
||||
- `file_upload` (object) ファイルアップロード構成
|
||||
- `image` (object) 画像設定
|
||||
現在サポートされている画像タイプ:`png`, `jpg`, `jpeg`, `webp`, `gif`
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) 画像数の制限、デフォルトは3
|
||||
- `transfer_methods` (array[string]) 転送方法のリスト、remote_url, local_file、いずれかを選択する必要があります
|
||||
- `file_upload` (object) ファイルアップロード設定
|
||||
- `document` (object) ドキュメント設定
|
||||
現在サポートされているドキュメントタイプ:`txt`, `md`, `markdown`, `pdf`, `html`, `xlsx`, `xls`, `docx`, `csv`, `eml`, `msg`, `pptx`, `ppt`, `xml`, `epub`。
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) ドキュメント数の上限。デフォルトは 3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト:`remote_url`, `local_file`。いずれかを選択する必要があります。
|
||||
- `image` (object) 画像設定
|
||||
現在サポートされている画像タイプ:`png`, `jpg`, `jpeg`, `webp`, `gif`。
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) 画像数の上限。デフォルトは 3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト:`remote_url`, `local_file`。いずれかを選択する必要があります。
|
||||
- `audio` (object) オーディオ設定
|
||||
現在サポートされているオーディオタイプ:`mp3`, `m4a`, `wav`, `webm`, `amr`。
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) オーディオ数の上限。デフォルトは 3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト:`remote_url`, `local_file`。いずれかを選択する必要があります。
|
||||
- `video` (object) ビデオ設定
|
||||
現在サポートされているビデオタイプ:`mp4`, `mov`, `mpeg`, `mpga`。
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) ビデオ数の上限。デフォルトは 3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト:`remote_url`, `local_file`。いずれかを選択する必要があります。
|
||||
- `custom` (object) カスタム設定
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) カスタム数の上限。デフォルトは 3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト:`remote_url`, `local_file`。いずれかを選択する必要があります。
|
||||
- `system_parameters` (object) システムパラメータ
|
||||
- `file_size_limit` (int) ドキュメントアップロードサイズ制限(MB)
|
||||
- `image_file_size_limit` (int) 画像ファイルアップロードサイズ制限(MB)
|
||||
|
||||
@@ -1237,11 +1237,30 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
- `default` (string) 默认值
|
||||
- `options` (array[string]) 选项值
|
||||
- `file_upload` (object) 文件上传配置
|
||||
- `image` (object) 图片设置
|
||||
当前仅支持图片类型:`png`, `jpg`, `jpeg`, `webp`, `gif`
|
||||
- `enabled` (bool) 是否开启
|
||||
- `number_limits` (int) 图片数量限制,默认 3
|
||||
- `transfer_methods` (array[string]) 传递方式列表,remote_url , local_file,必选一个
|
||||
- `document` (object) 文档设置
|
||||
当前仅支持文档类型:`txt`, `md`, `markdown`, `pdf`, `html`, `xlsx`, `xls`, `docx`, `csv`, `eml`, `msg`, `pptx`, `ppt`, `xml`, `epub`。
|
||||
- `enabled` (bool) 是否启用
|
||||
- `number_limits` (int) 文档数量限制,默认为 3
|
||||
- `transfer_methods` (array[string]) 传输方式列表:`remote_url`, `local_file`,必须选择一个。
|
||||
- `image` (object) 图片设置
|
||||
当前仅支持图片类型:`png`, `jpg`, `jpeg`, `webp`, `gif`。
|
||||
- `enabled` (bool) 是否启用
|
||||
- `number_limits` (int) 图片数量限制,默认为 3
|
||||
- `transfer_methods` (array[string]) 传输方式列表:`remote_url`, `local_file`,必须选择一个。
|
||||
- `audio` (object) 音频设置
|
||||
当前仅支持音频类型:`mp3`, `m4a`, `wav`, `webm`, `amr`。
|
||||
- `enabled` (bool) 是否启用
|
||||
- `number_limits` (int) 音频数量限制,默认为 3
|
||||
- `transfer_methods` (array[string]) 传输方式列表:`remote_url`, `local_file`,必须选择一个。
|
||||
- `video` (object) 视频设置
|
||||
当前仅支持视频类型:`mp4`, `mov`, `mpeg`, `mpga`。
|
||||
- `enabled` (bool) 是否启用
|
||||
- `number_limits` (int) 视频数量限制,默认为 3
|
||||
- `transfer_methods` (array[string]) 传输方式列表:`remote_url`, `local_file`,必须选择一个。
|
||||
- `custom` (object) 自定义设置
|
||||
- `enabled` (bool) 是否启用
|
||||
- `number_limits` (int) 自定义数量限制,默认为 3
|
||||
- `transfer_methods` (array[string]) 传输方式列表:`remote_url`, `local_file`,必须选择一个。
|
||||
- `system_parameters` (object) 系统参数
|
||||
- `file_size_limit` (int) 文档上传大小限制 (MB)
|
||||
- `image_file_size_limit` (int) 图片文件上传大小限制(MB)
|
||||
|
||||
@@ -690,11 +690,30 @@ Workflow applications offers non-session support and is ideal for translation, a
|
||||
- `default` (string) Default value
|
||||
- `options` (array[string]) Option values
|
||||
- `file_upload` (object) File upload configuration
|
||||
- `image` (object) Image settings
|
||||
Currently only supports image types: `png`, `jpg`, `jpeg`, `webp`, `gif`
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Image number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods, remote_url, local_file, must choose one
|
||||
- `document` (object) Document settings
|
||||
Currently only supports document types: `txt`, `md`, `markdown`, `pdf`, `html`, `xlsx`, `xls`, `docx`, `csv`, `eml`, `msg`, `pptx`, `ppt`, `xml`, `epub`.
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Document number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods: `remote_url`, `local_file`. Must choose one.
|
||||
- `image` (object) Image settings
|
||||
Currently only supports image types: `png`, `jpg`, `jpeg`, `webp`, `gif`.
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Image number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods: `remote_url`, `local_file`. Must choose one.
|
||||
- `audio` (object) Audio settings
|
||||
Currently only supports audio types: `mp3`, `m4a`, `wav`, `webm`, `amr`.
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Audio number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods: `remote_url`, `local_file`. Must choose one.
|
||||
- `video` (object) Video settings
|
||||
Currently only supports video types: `mp4`, `mov`, `mpeg`, `mpga`.
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Video number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods: `remote_url`, `local_file`. Must choose one.
|
||||
- `custom` (object) Custom settings
|
||||
- `enabled` (bool) Whether it is enabled
|
||||
- `number_limits` (int) Custom number limit, default is 3
|
||||
- `transfer_methods` (array[string]) List of transfer methods: `remote_url`, `local_file`. Must choose one.
|
||||
- `system_parameters` (object) System parameters
|
||||
- `file_size_limit` (int) Document upload size limit (MB)
|
||||
- `image_file_size_limit` (int) Image file upload size limit (MB)
|
||||
|
||||
@@ -691,11 +691,30 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
- `default` (string) デフォルト値
|
||||
- `options` (array[string]) オプション値
|
||||
- `file_upload` (object) ファイルアップロード設定
|
||||
- `image` (object) 画像設定
|
||||
現在サポートされている画像タイプのみ:`png`, `jpg`, `jpeg`, `webp`, `gif`
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) 画像数の制限、デフォルトは3
|
||||
- `transfer_methods` (array[string]) 転送方法のリスト、remote_url, local_file、いずれかを選択する必要があります
|
||||
- `document` (object) ドキュメント設定
|
||||
現在サポートされているドキュメントタイプ:`txt`, `md`, `markdown`, `pdf`, `html`, `xlsx`, `xls`, `docx`, `csv`, `eml`, `msg`, `pptx`, `ppt`, `xml`, `epub`。
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) ドキュメント数の上限。デフォルトは 3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト:`remote_url`, `local_file`。いずれかを選択する必要があります。
|
||||
- `image` (object) 画像設定
|
||||
現在サポートされている画像タイプ:`png`, `jpg`, `jpeg`, `webp`, `gif`。
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) 画像数の上限。デフォルトは 3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト:`remote_url`, `local_file`。いずれかを選択する必要があります。
|
||||
- `audio` (object) オーディオ設定
|
||||
現在サポートされているオーディオタイプ:`mp3`, `m4a`, `wav`, `webm`, `amr`。
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) オーディオ数の上限。デフォルトは 3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト:`remote_url`, `local_file`。いずれかを選択する必要があります。
|
||||
- `video` (object) ビデオ設定
|
||||
現在サポートされているビデオタイプ:`mp4`, `mov`, `mpeg`, `mpga`。
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) ビデオ数の上限。デフォルトは 3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト:`remote_url`, `local_file`。いずれかを選択する必要があります。
|
||||
- `custom` (object) カスタム設定
|
||||
- `enabled` (bool) 有効かどうか
|
||||
- `number_limits` (int) カスタム数の上限。デフォルトは 3
|
||||
- `transfer_methods` (array[string]) 転送方法リスト:`remote_url`, `local_file`。いずれかを選択する必要があります。
|
||||
- `system_parameters` (object) システムパラメータ
|
||||
- `file_size_limit` (int) ドキュメントアップロードサイズ制限(MB)
|
||||
- `image_file_size_limit` (int) 画像ファイルアップロードサイズ制限(MB)
|
||||
|
||||
@@ -678,11 +678,30 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
|
||||
- `default` (string) 默认值
|
||||
- `options` (array[string]) 选项值
|
||||
- `file_upload` (object) 文件上传配置
|
||||
- `image` (object) 图片设置
|
||||
当前仅支持图片类型:`png`, `jpg`, `jpeg`, `webp`, `gif`
|
||||
- `enabled` (bool) 是否开启
|
||||
- `number_limits` (int) 图片数量限制,默认 3
|
||||
- `transfer_methods` (array[string]) 传递方式列表,remote_url , local_file,必选一个
|
||||
- `document` (object) 文档设置
|
||||
当前仅支持文档类型:`txt`, `md`, `markdown`, `pdf`, `html`, `xlsx`, `xls`, `docx`, `csv`, `eml`, `msg`, `pptx`, `ppt`, `xml`, `epub`。
|
||||
- `enabled` (bool) 是否启用
|
||||
- `number_limits` (int) 文档数量限制,默认为 3
|
||||
- `transfer_methods` (array[string]) 传输方式列表:`remote_url`, `local_file`,必须选择一个。
|
||||
- `image` (object) 图片设置
|
||||
当前仅支持图片类型:`png`, `jpg`, `jpeg`, `webp`, `gif`。
|
||||
- `enabled` (bool) 是否启用
|
||||
- `number_limits` (int) 图片数量限制,默认为 3
|
||||
- `transfer_methods` (array[string]) 传输方式列表:`remote_url`, `local_file`,必须选择一个。
|
||||
- `audio` (object) 音频设置
|
||||
当前仅支持音频类型:`mp3`, `m4a`, `wav`, `webm`, `amr`。
|
||||
- `enabled` (bool) 是否启用
|
||||
- `number_limits` (int) 音频数量限制,默认为 3
|
||||
- `transfer_methods` (array[string]) 传输方式列表:`remote_url`, `local_file`,必须选择一个。
|
||||
- `video` (object) 视频设置
|
||||
当前仅支持视频类型:`mp4`, `mov`, `mpeg`, `mpga`。
|
||||
- `enabled` (bool) 是否启用
|
||||
- `number_limits` (int) 视频数量限制,默认为 3
|
||||
- `transfer_methods` (array[string]) 传输方式列表:`remote_url`, `local_file`,必须选择一个。
|
||||
- `custom` (object) 自定义设置
|
||||
- `enabled` (bool) 是否启用
|
||||
- `number_limits` (int) 自定义数量限制,默认为 3
|
||||
- `transfer_methods` (array[string]) 传输方式列表:`remote_url`, `local_file`,必须选择一个。
|
||||
- `system_parameters` (object) 系统参数
|
||||
- `file_size_limit` (int) 文档上传大小限制 (MB)
|
||||
- `image_file_size_limit` (int) 图片文件上传大小限制(MB)
|
||||
|
||||
105
web/app/components/plugins/base/deprecation-notice.tsx
Normal file
105
web/app/components/plugins/base/deprecation-notice.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import type { FC } from 'react'
|
||||
import Link from 'next/link'
|
||||
import cn from '@/utils/classnames'
|
||||
import { RiAlertFill } from '@remixicon/react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { snakeCase2CamelCase } from '@/utils/format'
|
||||
import { useMixedTranslation } from '../marketplace/hooks'
|
||||
|
||||
type DeprecationNoticeProps = {
|
||||
status: 'deleted' | 'active'
|
||||
deprecatedReason: string
|
||||
alternativePluginId: string
|
||||
alternativePluginURL: string
|
||||
locale?: string
|
||||
className?: string
|
||||
innerWrapperClassName?: string
|
||||
iconWrapperClassName?: string
|
||||
textClassName?: string
|
||||
}
|
||||
|
||||
const i18nPrefix = 'plugin.detailPanel.deprecation'
|
||||
|
||||
const DeprecationNotice: FC<DeprecationNoticeProps> = ({
|
||||
status,
|
||||
deprecatedReason,
|
||||
alternativePluginId,
|
||||
alternativePluginURL,
|
||||
locale,
|
||||
className,
|
||||
innerWrapperClassName,
|
||||
iconWrapperClassName,
|
||||
textClassName,
|
||||
}) => {
|
||||
const { t } = useMixedTranslation(locale)
|
||||
|
||||
const deprecatedReasonKey = useMemo(() => {
|
||||
if (!deprecatedReason) return ''
|
||||
return snakeCase2CamelCase(deprecatedReason)
|
||||
}, [deprecatedReason])
|
||||
|
||||
// Check if the deprecatedReasonKey exists in i18n
|
||||
const hasValidDeprecatedReason = useMemo(() => {
|
||||
if (!deprecatedReason || !deprecatedReasonKey) return false
|
||||
|
||||
// Define valid reason keys that exist in i18n
|
||||
const validReasonKeys = ['businessAdjustments', 'ownershipTransferred', 'noMaintainer']
|
||||
return validReasonKeys.includes(deprecatedReasonKey)
|
||||
}, [deprecatedReason, deprecatedReasonKey])
|
||||
|
||||
if (status !== 'deleted')
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className={cn('w-full', className)}>
|
||||
<div className={cn(
|
||||
'relative flex items-start gap-x-0.5 overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 shadow-xs shadow-shadow-shadow-3 backdrop-blur-[5px]',
|
||||
innerWrapperClassName,
|
||||
)}>
|
||||
<div className='absolute left-0 top-0 -z-10 h-full w-full bg-toast-warning-bg opacity-40' />
|
||||
<div className={cn('flex size-6 shrink-0 items-center justify-center', iconWrapperClassName)}>
|
||||
<RiAlertFill className='size-4 text-text-warning-secondary' />
|
||||
</div>
|
||||
<div className={cn('system-xs-regular grow py-1 text-text-primary', textClassName)}>
|
||||
{
|
||||
hasValidDeprecatedReason && alternativePluginId && (
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey={`${i18nPrefix}.fullMessage`}
|
||||
components={{
|
||||
CustomLink: (
|
||||
<Link
|
||||
href={alternativePluginURL}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='underline'
|
||||
/>
|
||||
),
|
||||
}}
|
||||
values={{
|
||||
deprecatedReason: t(`${i18nPrefix}.reason.${deprecatedReasonKey}`),
|
||||
alternativePluginId,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
hasValidDeprecatedReason && !alternativePluginId && (
|
||||
<span>
|
||||
{t(`${i18nPrefix}.onlyReason`, { deprecatedReason: t(`${i18nPrefix}.reason.${deprecatedReasonKey}`) })}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
{
|
||||
!hasValidDeprecatedReason && (
|
||||
<span>{t(`${i18nPrefix}.noReason`)}</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(DeprecationNotice)
|
||||
@@ -6,7 +6,7 @@ import PluginTypeSwitch from './plugin-type-switch'
|
||||
import ListWrapper from './list/list-wrapper'
|
||||
import type { SearchParams } from './types'
|
||||
import { getMarketplaceCollectionsAndPlugins } from './utils'
|
||||
import { TanstackQueryIniter } from '@/context/query-client'
|
||||
import { TanstackQueryInitializer } from '@/context/query-client'
|
||||
|
||||
type MarketplaceProps = {
|
||||
locale: string
|
||||
@@ -39,7 +39,7 @@ const Marketplace = async ({
|
||||
}
|
||||
|
||||
return (
|
||||
<TanstackQueryIniter>
|
||||
<TanstackQueryInitializer>
|
||||
<MarketplaceContextProvider
|
||||
searchParams={searchParams}
|
||||
shouldExclude={shouldExclude}
|
||||
@@ -65,7 +65,7 @@ const Marketplace = async ({
|
||||
showInstallButton={showInstallButton}
|
||||
/>
|
||||
</MarketplaceContextProvider>
|
||||
</TanstackQueryIniter>
|
||||
</TanstackQueryInitializer>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import Toast from '@/app/components/base/toast'
|
||||
import { BoxSparkleFill } from '@/app/components/base/icons/src/vender/plugin'
|
||||
import { Github } from '@/app/components/base/icons/src/public/common'
|
||||
import { uninstallPlugin } from '@/service/plugins'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import { useGetLanguage, useI18N } from '@/context/i18n'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useInvalidateAllToolProviders } from '@/service/use-tools'
|
||||
@@ -39,6 +39,7 @@ import { getMarketplaceUrl } from '@/utils/var'
|
||||
import { PluginAuth } from '@/app/components/plugins/plugin-auth'
|
||||
import { AuthCategory } from '@/app/components/plugins/plugin-auth'
|
||||
import { useAllToolProviders } from '@/service/use-tools'
|
||||
import DeprecationNotice from '../base/deprecation-notice'
|
||||
|
||||
const i18nPrefix = 'plugin.action'
|
||||
|
||||
@@ -56,6 +57,7 @@ const DetailHeader = ({
|
||||
const { t } = useTranslation()
|
||||
const { theme } = useTheme()
|
||||
const locale = useGetLanguage()
|
||||
const { locale: currentLocale } = useI18N()
|
||||
const { checkForUpdates, fetchReleases } = useGitHubReleases()
|
||||
const { setShowUpdatePluginModal } = useModalContext()
|
||||
const { refreshModelProviders } = useProviderContext()
|
||||
@@ -70,6 +72,9 @@ const DetailHeader = ({
|
||||
latest_version,
|
||||
meta,
|
||||
plugin_id,
|
||||
status,
|
||||
deprecated_reason,
|
||||
alternative_plugin_id,
|
||||
} = detail
|
||||
const { author, category, name, label, description, icon, verified, tool } = detail.declaration
|
||||
const isTool = category === PluginType.tool
|
||||
@@ -98,7 +103,7 @@ const DetailHeader = ({
|
||||
if (isFromGitHub)
|
||||
return `https://github.com/${meta!.repo}`
|
||||
if (isFromMarketplace)
|
||||
return getMarketplaceUrl(`/plugins/${author}/${name}`, { theme })
|
||||
return getMarketplaceUrl(`/plugins/${author}/${name}`, { language: currentLocale, theme })
|
||||
return ''
|
||||
}, [author, isFromGitHub, isFromMarketplace, meta, name, theme])
|
||||
|
||||
@@ -272,6 +277,15 @@ const DetailHeader = ({
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
{isFromMarketplace && (
|
||||
<DeprecationNotice
|
||||
status={status}
|
||||
deprecatedReason={deprecated_reason}
|
||||
alternativePluginId={alternative_plugin_id}
|
||||
alternativePluginURL={getMarketplaceUrl(`/plugins/${alternative_plugin_id}`, { language: currentLocale, theme })}
|
||||
className='mt-3'
|
||||
/>
|
||||
)}
|
||||
<Description className='mb-2 mt-3 h-auto' text={description[locale]} descriptionLineRows={2}></Description>
|
||||
{
|
||||
category === PluginType.tool && (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useMemo } from 'react'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import {
|
||||
RiArrowRightUpLine,
|
||||
@@ -55,6 +55,8 @@ const PluginItem: FC<Props> = ({
|
||||
endpoints_active,
|
||||
meta,
|
||||
plugin_id,
|
||||
status,
|
||||
deprecated_reason,
|
||||
} = plugin
|
||||
const { category, author, name, label, description, icon, verified, meta: declarationMeta } = plugin.declaration
|
||||
|
||||
@@ -70,9 +72,14 @@ const PluginItem: FC<Props> = ({
|
||||
return gte(langGeniusVersionInfo.current_version, declarationMeta.minimum_dify_version ?? '0.0.0')
|
||||
}, [declarationMeta.minimum_dify_version, langGeniusVersionInfo.current_version])
|
||||
|
||||
const handleDelete = () => {
|
||||
const isDeprecated = useMemo(() => {
|
||||
return status === 'deleted' && !!deprecated_reason
|
||||
}, [status, deprecated_reason])
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
refreshPluginList({ category } as any)
|
||||
}
|
||||
}, [category, refreshPluginList])
|
||||
|
||||
const getValueFromI18nObject = useRenderI18nObject()
|
||||
const title = getValueFromI18nObject(label)
|
||||
const descriptionText = getValueFromI18nObject(description)
|
||||
@@ -81,7 +88,7 @@ const PluginItem: FC<Props> = ({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-xl border-[1.5px] border-background-section-burn p-1',
|
||||
'relative overflow-hidden rounded-xl border-[1.5px] border-background-section-burn p-1',
|
||||
currentPluginID === plugin_id && 'border-components-option-card-option-selected-border',
|
||||
source === PluginSource.debugging
|
||||
? 'bg-[repeating-linear-gradient(-45deg,rgba(16,24,40,0.04),rgba(16,24,40,0.04)_5px,rgba(0,0,0,0.02)_5px,rgba(0,0,0,0.02)_10px)]'
|
||||
@@ -91,10 +98,10 @@ const PluginItem: FC<Props> = ({
|
||||
setCurrentPluginID(plugin.plugin_id)
|
||||
}}
|
||||
>
|
||||
<div className={cn('hover-bg-components-panel-on-panel-item-bg relative rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-4 pb-3 shadow-xs', className)}>
|
||||
<div className={cn('hover-bg-components-panel-on-panel-item-bg relative z-10 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-4 pb-3 shadow-xs', className)}>
|
||||
<CornerMark text={categoriesMap[category].label} />
|
||||
{/* Header */}
|
||||
<div className="flex">
|
||||
<div className='flex'>
|
||||
<div className='flex h-10 w-10 items-center justify-center overflow-hidden rounded-xl border-[1px] border-components-panel-border-subtle'>
|
||||
<img
|
||||
className='h-full w-full'
|
||||
@@ -102,13 +109,13 @@ const PluginItem: FC<Props> = ({
|
||||
alt={`plugin-${plugin_unique_identifier}-logo`}
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3 w-0 grow">
|
||||
<div className="flex h-5 items-center">
|
||||
<div className='ml-3 w-0 grow'>
|
||||
<div className='flex h-5 items-center'>
|
||||
<Title title={title} />
|
||||
{verified && <RiVerifiedBadgeLine className="ml-0.5 h-4 w-4 shrink-0 text-text-accent" />}
|
||||
{verified && <RiVerifiedBadgeLine className='ml-0.5 h-4 w-4 shrink-0 text-text-accent' />}
|
||||
{!isDifyVersionCompatible && <Tooltip popupContent={
|
||||
t('plugin.difyVersionNotCompatible', { minimalDifyVersion: declarationMeta.minimum_dify_version })
|
||||
}><RiErrorWarningLine color='red' className="ml-0.5 h-4 w-4 shrink-0 text-text-accent" /></Tooltip>}
|
||||
}><RiErrorWarningLine color='red' className='ml-0.5 h-4 w-4 shrink-0 text-text-accent' /></Tooltip>}
|
||||
<Badge className='ml-1 shrink-0'
|
||||
text={source === PluginSource.github ? plugin.meta!.version : plugin.version}
|
||||
hasRedCornerMark={(source === PluginSource.marketplace) && !!plugin.latest_version && plugin.latest_version !== plugin.version}
|
||||
@@ -135,10 +142,11 @@ const PluginItem: FC<Props> = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mb-1 mt-1.5 flex h-4 items-center justify-between px-4'>
|
||||
<div className='flex items-center'>
|
||||
<div className='mb-1 mt-1.5 flex h-4 items-center gap-x-2 px-4'>
|
||||
{/* Organization & Name */}
|
||||
<div className='flex grow items-center overflow-hidden'>
|
||||
<OrgInfo
|
||||
className="mt-0.5"
|
||||
className='mt-0.5'
|
||||
orgName={orgName}
|
||||
packageName={name}
|
||||
packageNameClassName='w-auto max-w-[150px]'
|
||||
@@ -146,15 +154,20 @@ const PluginItem: FC<Props> = ({
|
||||
{category === PluginType.extension && (
|
||||
<>
|
||||
<div className='system-xs-regular mx-2 text-text-quaternary'>·</div>
|
||||
<div className='system-xs-regular flex space-x-1 text-text-tertiary'>
|
||||
<RiLoginCircleLine className='h-4 w-4' />
|
||||
<span>{t('plugin.endpointsEnabled', { num: endpoints_active })}</span>
|
||||
<div className='system-xs-regular flex space-x-1 overflow-hidden text-text-tertiary'>
|
||||
<RiLoginCircleLine className='h-4 w-4 shrink-0' />
|
||||
<span
|
||||
className='truncate'
|
||||
title={t('plugin.endpointsEnabled', { num: endpoints_active })}
|
||||
>
|
||||
{t('plugin.endpointsEnabled', { num: endpoints_active })}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='flex items-center'>
|
||||
{/* Source */}
|
||||
<div className='flex shrink-0 items-center'>
|
||||
{source === PluginSource.github
|
||||
&& <>
|
||||
<a href={`https://github.com/${meta!.repo}`} target='_blank' className='flex items-center gap-1'>
|
||||
@@ -192,7 +205,20 @@ const PluginItem: FC<Props> = ({
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
{/* Deprecated */}
|
||||
{source === PluginSource.marketplace && enable_marketplace && isDeprecated && (
|
||||
<div className='system-2xs-medium-uppercase flex shrink-0 items-center gap-x-2'>
|
||||
<span className='text-text-tertiary'>·</span>
|
||||
<span className='text-text-warning'>
|
||||
{t('plugin.deprecated')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* BG Effect for Deprecated Plugin */}
|
||||
{source === PluginSource.marketplace && enable_marketplace && isDeprecated && (
|
||||
<div className='absolute bottom-[-71px] right-[-45px] z-0 size-40 bg-components-badge-status-light-warning-halo opacity-60 blur-[120px]' />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ const PluginsPanel = () => {
|
||||
...plugin,
|
||||
latest_version: installedLatestVersion?.versions[plugin.plugin_id]?.version ?? '',
|
||||
latest_unique_identifier: installedLatestVersion?.versions[plugin.plugin_id]?.unique_identifier ?? '',
|
||||
status: installedLatestVersion?.versions[plugin.plugin_id]?.status ?? 'active',
|
||||
deprecated_reason: installedLatestVersion?.versions[plugin.plugin_id]?.deprecated_reason ?? '',
|
||||
alternative_plugin_id: installedLatestVersion?.versions[plugin.plugin_id]?.alternative_plugin_id ?? '',
|
||||
})) || []
|
||||
}, [pluginList, installedLatestVersion])
|
||||
|
||||
@@ -66,20 +69,25 @@ const PluginsPanel = () => {
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
</div>
|
||||
{isPluginListLoading ? <Loading type='app' /> : (filteredList?.length ?? 0) > 0 ? (
|
||||
<div className='flex grow flex-wrap content-start items-start justify-center gap-2 self-stretch px-12'>
|
||||
<div className='w-full'>
|
||||
<List pluginList={filteredList || []} />
|
||||
</div>
|
||||
{!isLastPage && !isFetching && (
|
||||
<Button onClick={loadNextPage}>
|
||||
{t('workflow.common.loadMore')}
|
||||
</Button>
|
||||
{isPluginListLoading && <Loading type='app' />}
|
||||
{!isPluginListLoading && (
|
||||
<>
|
||||
{(filteredList?.length ?? 0) > 0 ? (
|
||||
<div className='flex grow flex-wrap content-start items-start justify-center gap-2 self-stretch px-12'>
|
||||
<div className='w-full'>
|
||||
<List pluginList={filteredList || []} />
|
||||
</div>
|
||||
{!isLastPage && !isFetching && (
|
||||
<Button onClick={loadNextPage}>
|
||||
{t('workflow.common.loadMore')}
|
||||
</Button>
|
||||
)}
|
||||
{isFetching && <div className='system-md-semibold text-text-secondary'>{t('appLog.detail.loading')}</div>}
|
||||
</div>
|
||||
) : (
|
||||
<Empty />
|
||||
)}
|
||||
{isFetching && <div className='system-md-semibold text-text-secondary'>{t('appLog.detail.loading')}</div>}
|
||||
</div>
|
||||
) : (
|
||||
<Empty />
|
||||
</>
|
||||
)}
|
||||
<PluginDetailPanel
|
||||
detail={currentPluginDetail}
|
||||
|
||||
@@ -120,6 +120,9 @@ export type PluginDetail = {
|
||||
latest_unique_identifier: string
|
||||
source: PluginSource
|
||||
meta?: MetaData
|
||||
status: 'active' | 'deleted'
|
||||
deprecated_reason: string
|
||||
alternative_plugin_id: string
|
||||
}
|
||||
|
||||
export type PluginInfoFromMarketPlace = {
|
||||
@@ -345,6 +348,9 @@ export type InstalledLatestVersionResponse = {
|
||||
[plugin_id: string]: {
|
||||
unique_identifier: string
|
||||
version: string
|
||||
status: 'active' | 'deleted'
|
||||
deprecated_reason: string
|
||||
alternative_plugin_id: string
|
||||
} | null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ const Field: FC<Props> = ({
|
||||
<Tooltip popupContent={t('app.structOutput.moreFillTip')} disabled={depth !== MAX_DEPTH + 1}>
|
||||
<div
|
||||
className={cn('flex items-center justify-between rounded-md pr-2', !readonly && 'hover:bg-state-base-hover', depth !== MAX_DEPTH + 1 && 'cursor-pointer')}
|
||||
onClick={() => !readonly && onSelect?.([...valueSelector, name])}
|
||||
onMouseDown={() => !readonly && onSelect?.([...valueSelector, name])}
|
||||
>
|
||||
<div className='flex grow items-stretch'>
|
||||
<TreeIndentLine depth={depth} />
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* Workflow Panel Width Persistence Tests
|
||||
* Tests for GitHub issue #22745: Panel width persistence bug fix
|
||||
*/
|
||||
|
||||
import '@testing-library/jest-dom'
|
||||
|
||||
type PanelWidthSource = 'user' | 'system'
|
||||
|
||||
// Mock localStorage for testing
|
||||
const createMockLocalStorage = () => {
|
||||
const storage: Record<string, string> = {}
|
||||
return {
|
||||
getItem: jest.fn((key: string) => storage[key] || null),
|
||||
setItem: jest.fn((key: string, value: string) => {
|
||||
storage[key] = value
|
||||
}),
|
||||
removeItem: jest.fn((key: string) => {
|
||||
delete storage[key]
|
||||
}),
|
||||
clear: jest.fn(() => {
|
||||
Object.keys(storage).forEach(key => delete storage[key])
|
||||
}),
|
||||
get storage() { return { ...storage } },
|
||||
}
|
||||
}
|
||||
|
||||
// Core panel width logic extracted from the component
|
||||
const createPanelWidthManager = (storageKey: string) => {
|
||||
return {
|
||||
updateWidth: (width: number, source: PanelWidthSource = 'user') => {
|
||||
const newValue = Math.max(400, Math.min(width, 800))
|
||||
if (source === 'user')
|
||||
localStorage.setItem(storageKey, `${newValue}`)
|
||||
|
||||
return newValue
|
||||
},
|
||||
getStoredWidth: () => {
|
||||
const stored = localStorage.getItem(storageKey)
|
||||
return stored ? Number.parseFloat(stored) : 400
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe('Workflow Panel Width Persistence', () => {
|
||||
let mockLocalStorage: ReturnType<typeof createMockLocalStorage>
|
||||
|
||||
beforeEach(() => {
|
||||
mockLocalStorage = createMockLocalStorage()
|
||||
Object.defineProperty(globalThis, 'localStorage', {
|
||||
value: mockLocalStorage,
|
||||
writable: true,
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Node Panel Width Management', () => {
|
||||
const storageKey = 'workflow-node-panel-width'
|
||||
|
||||
it('should save user resize to localStorage', () => {
|
||||
const manager = createPanelWidthManager(storageKey)
|
||||
|
||||
const result = manager.updateWidth(500, 'user')
|
||||
|
||||
expect(result).toBe(500)
|
||||
expect(localStorage.setItem).toHaveBeenCalledWith(storageKey, '500')
|
||||
})
|
||||
|
||||
it('should not save system compression to localStorage', () => {
|
||||
const manager = createPanelWidthManager(storageKey)
|
||||
|
||||
const result = manager.updateWidth(200, 'system')
|
||||
|
||||
expect(result).toBe(400) // Respects minimum width
|
||||
expect(localStorage.setItem).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should enforce minimum width of 400px', () => {
|
||||
const manager = createPanelWidthManager(storageKey)
|
||||
|
||||
// User tries to set below minimum
|
||||
const userResult = manager.updateWidth(300, 'user')
|
||||
expect(userResult).toBe(400)
|
||||
expect(localStorage.setItem).toHaveBeenCalledWith(storageKey, '400')
|
||||
|
||||
// System compression below minimum
|
||||
const systemResult = manager.updateWidth(150, 'system')
|
||||
expect(systemResult).toBe(400)
|
||||
expect(localStorage.setItem).toHaveBeenCalledTimes(1) // Only user call
|
||||
})
|
||||
|
||||
it('should preserve user preferences during system compression', () => {
|
||||
localStorage.setItem(storageKey, '600')
|
||||
const manager = createPanelWidthManager(storageKey)
|
||||
|
||||
// System compresses panel
|
||||
manager.updateWidth(200, 'system')
|
||||
|
||||
// User preference should remain unchanged
|
||||
expect(localStorage.getItem(storageKey)).toBe('600')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Bug Scenario Reproduction', () => {
|
||||
it('should reproduce original bug behavior (for comparison)', () => {
|
||||
const storageKey = 'workflow-node-panel-width'
|
||||
|
||||
// Original buggy behavior - always saves regardless of source
|
||||
const buggyUpdate = (width: number) => {
|
||||
localStorage.setItem(storageKey, `${width}`)
|
||||
return Math.max(400, width)
|
||||
}
|
||||
|
||||
localStorage.setItem(storageKey, '500') // User preference
|
||||
buggyUpdate(200) // System compression pollutes localStorage
|
||||
|
||||
expect(localStorage.getItem(storageKey)).toBe('200') // Bug: corrupted state
|
||||
})
|
||||
|
||||
it('should verify fix prevents localStorage pollution', () => {
|
||||
const storageKey = 'workflow-node-panel-width'
|
||||
const manager = createPanelWidthManager(storageKey)
|
||||
|
||||
localStorage.setItem(storageKey, '500') // User preference
|
||||
manager.updateWidth(200, 'system') // System compression
|
||||
|
||||
expect(localStorage.getItem(storageKey)).toBe('500') // Fix: preserved state
|
||||
})
|
||||
})
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle multiple rapid operations correctly', () => {
|
||||
const manager = createPanelWidthManager('workflow-node-panel-width')
|
||||
|
||||
// Rapid system adjustments
|
||||
manager.updateWidth(300, 'system')
|
||||
manager.updateWidth(250, 'system')
|
||||
manager.updateWidth(180, 'system')
|
||||
|
||||
// Single user adjustment
|
||||
manager.updateWidth(550, 'user')
|
||||
|
||||
expect(localStorage.setItem).toHaveBeenCalledTimes(1)
|
||||
expect(localStorage.setItem).toHaveBeenCalledWith('workflow-node-panel-width', '550')
|
||||
})
|
||||
|
||||
it('should handle corrupted localStorage gracefully', () => {
|
||||
localStorage.setItem('workflow-node-panel-width', '150') // Below minimum
|
||||
const manager = createPanelWidthManager('workflow-node-panel-width')
|
||||
|
||||
const storedWidth = manager.getStoredWidth()
|
||||
expect(storedWidth).toBe(150) // Returns raw value
|
||||
|
||||
// User can correct the preference
|
||||
const correctedWidth = manager.updateWidth(500, 'user')
|
||||
expect(correctedWidth).toBe(500)
|
||||
expect(localStorage.getItem('workflow-node-panel-width')).toBe('500')
|
||||
})
|
||||
})
|
||||
|
||||
describe('TypeScript Type Safety', () => {
|
||||
it('should enforce source parameter type', () => {
|
||||
const manager = createPanelWidthManager('workflow-node-panel-width')
|
||||
|
||||
// Valid source values
|
||||
manager.updateWidth(500, 'user')
|
||||
manager.updateWidth(500, 'system')
|
||||
|
||||
// Default to 'user'
|
||||
manager.updateWidth(500)
|
||||
|
||||
expect(localStorage.setItem).toHaveBeenCalledTimes(2) // user + default
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -105,15 +105,18 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
return Math.max(available, 400)
|
||||
}, [workflowCanvasWidth, otherPanelWidth])
|
||||
|
||||
const updateNodePanelWidth = useCallback((width: number) => {
|
||||
const updateNodePanelWidth = useCallback((width: number, source: 'user' | 'system' = 'user') => {
|
||||
// Ensure the width is within the min and max range
|
||||
const newValue = Math.max(400, Math.min(width, maxNodePanelWidth))
|
||||
localStorage.setItem('workflow-node-panel-width', `${newValue}`)
|
||||
|
||||
if (source === 'user')
|
||||
localStorage.setItem('workflow-node-panel-width', `${newValue}`)
|
||||
|
||||
setNodePanelWidth(newValue)
|
||||
}, [maxNodePanelWidth, setNodePanelWidth])
|
||||
|
||||
const handleResize = useCallback((width: number) => {
|
||||
updateNodePanelWidth(width)
|
||||
updateNodePanelWidth(width, 'user')
|
||||
}, [updateNodePanelWidth])
|
||||
|
||||
const {
|
||||
@@ -127,7 +130,10 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
onResize: debounce(handleResize),
|
||||
})
|
||||
|
||||
const debounceUpdate = debounce(updateNodePanelWidth)
|
||||
const debounceUpdate = debounce((width: number) => {
|
||||
updateNodePanelWidth(width, 'system')
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!workflowCanvasWidth)
|
||||
return
|
||||
@@ -138,7 +144,7 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
const target = Math.max(workflowCanvasWidth - otherPanelWidth - reservedCanvasWidth, 400)
|
||||
debounceUpdate(target)
|
||||
}
|
||||
}, [nodePanelWidth, otherPanelWidth, workflowCanvasWidth, updateNodePanelWidth])
|
||||
}, [nodePanelWidth, otherPanelWidth, workflowCanvasWidth, debounceUpdate])
|
||||
|
||||
const { handleNodeSelect } = useNodesInteractions()
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Debug and Preview Panel Width Persistence Tests
|
||||
* Tests for GitHub issue #22745: Panel width persistence bug fix
|
||||
*/
|
||||
|
||||
import '@testing-library/jest-dom'
|
||||
|
||||
type PanelWidthSource = 'user' | 'system'
|
||||
|
||||
// Mock localStorage for testing
|
||||
const createMockLocalStorage = () => {
|
||||
const storage: Record<string, string> = {}
|
||||
return {
|
||||
getItem: jest.fn((key: string) => storage[key] || null),
|
||||
setItem: jest.fn((key: string, value: string) => {
|
||||
storage[key] = value
|
||||
}),
|
||||
removeItem: jest.fn((key: string) => {
|
||||
delete storage[key]
|
||||
}),
|
||||
clear: jest.fn(() => {
|
||||
Object.keys(storage).forEach(key => delete storage[key])
|
||||
}),
|
||||
get storage() { return { ...storage } },
|
||||
}
|
||||
}
|
||||
|
||||
// Preview panel width logic
|
||||
const createPreviewPanelManager = () => {
|
||||
const storageKey = 'debug-and-preview-panel-width'
|
||||
|
||||
return {
|
||||
updateWidth: (width: number, source: PanelWidthSource = 'user') => {
|
||||
const newValue = Math.max(400, Math.min(width, 800))
|
||||
if (source === 'user')
|
||||
localStorage.setItem(storageKey, `${newValue}`)
|
||||
|
||||
return newValue
|
||||
},
|
||||
getStoredWidth: () => {
|
||||
const stored = localStorage.getItem(storageKey)
|
||||
return stored ? Number.parseFloat(stored) : 400
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe('Debug and Preview Panel Width Persistence', () => {
|
||||
let mockLocalStorage: ReturnType<typeof createMockLocalStorage>
|
||||
|
||||
beforeEach(() => {
|
||||
mockLocalStorage = createMockLocalStorage()
|
||||
Object.defineProperty(globalThis, 'localStorage', {
|
||||
value: mockLocalStorage,
|
||||
writable: true,
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Preview Panel Width Management', () => {
|
||||
it('should save user resize to localStorage', () => {
|
||||
const manager = createPreviewPanelManager()
|
||||
|
||||
const result = manager.updateWidth(450, 'user')
|
||||
|
||||
expect(result).toBe(450)
|
||||
expect(localStorage.setItem).toHaveBeenCalledWith('debug-and-preview-panel-width', '450')
|
||||
})
|
||||
|
||||
it('should not save system compression to localStorage', () => {
|
||||
const manager = createPreviewPanelManager()
|
||||
|
||||
const result = manager.updateWidth(300, 'system')
|
||||
|
||||
expect(result).toBe(400) // Respects minimum width
|
||||
expect(localStorage.setItem).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should behave identically to Node Panel', () => {
|
||||
const manager = createPreviewPanelManager()
|
||||
|
||||
// Both user and system operations should behave consistently
|
||||
manager.updateWidth(500, 'user')
|
||||
expect(localStorage.setItem).toHaveBeenCalledWith('debug-and-preview-panel-width', '500')
|
||||
|
||||
manager.updateWidth(200, 'system')
|
||||
expect(localStorage.getItem('debug-and-preview-panel-width')).toBe('500')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Dual Panel Scenario', () => {
|
||||
it('should maintain independence from Node Panel', () => {
|
||||
localStorage.setItem('workflow-node-panel-width', '600')
|
||||
localStorage.setItem('debug-and-preview-panel-width', '450')
|
||||
|
||||
const manager = createPreviewPanelManager()
|
||||
|
||||
// System compresses preview panel
|
||||
manager.updateWidth(200, 'system')
|
||||
|
||||
// Only preview panel storage key should be unaffected
|
||||
expect(localStorage.getItem('debug-and-preview-panel-width')).toBe('450')
|
||||
expect(localStorage.getItem('workflow-node-panel-width')).toBe('600')
|
||||
})
|
||||
|
||||
it('should handle F12 scenario consistently', () => {
|
||||
const manager = createPreviewPanelManager()
|
||||
|
||||
// User sets preference
|
||||
manager.updateWidth(500, 'user')
|
||||
expect(localStorage.getItem('debug-and-preview-panel-width')).toBe('500')
|
||||
|
||||
// F12 opens causing viewport compression
|
||||
manager.updateWidth(180, 'system')
|
||||
|
||||
// User preference preserved
|
||||
expect(localStorage.getItem('debug-and-preview-panel-width')).toBe('500')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Consistency with Node Panel', () => {
|
||||
it('should enforce same minimum width rules', () => {
|
||||
const manager = createPreviewPanelManager()
|
||||
|
||||
// Same 400px minimum as Node Panel
|
||||
const result = manager.updateWidth(300, 'user')
|
||||
expect(result).toBe(400)
|
||||
expect(localStorage.setItem).toHaveBeenCalledWith('debug-and-preview-panel-width', '400')
|
||||
})
|
||||
|
||||
it('should use same source parameter pattern', () => {
|
||||
const manager = createPreviewPanelManager()
|
||||
|
||||
// Default to 'user' when source not specified
|
||||
manager.updateWidth(500)
|
||||
expect(localStorage.setItem).toHaveBeenCalledWith('debug-and-preview-panel-width', '500')
|
||||
|
||||
// Explicit 'system' source
|
||||
manager.updateWidth(300, 'system')
|
||||
expect(localStorage.setItem).toHaveBeenCalledTimes(1) // Only user call
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -53,8 +53,9 @@ const DebugAndPreview = () => {
|
||||
const nodePanelWidth = useStore(s => s.nodePanelWidth)
|
||||
const panelWidth = useStore(s => s.previewPanelWidth)
|
||||
const setPanelWidth = useStore(s => s.setPreviewPanelWidth)
|
||||
const handleResize = useCallback((width: number) => {
|
||||
localStorage.setItem('debug-and-preview-panel-width', `${width}`)
|
||||
const handleResize = useCallback((width: number, source: 'user' | 'system' = 'user') => {
|
||||
if (source === 'user')
|
||||
localStorage.setItem('debug-and-preview-panel-width', `${width}`)
|
||||
setPanelWidth(width)
|
||||
}, [setPanelWidth])
|
||||
const maxPanelWidth = useMemo(() => {
|
||||
@@ -74,7 +75,9 @@ const DebugAndPreview = () => {
|
||||
triggerDirection: 'left',
|
||||
minWidth: 400,
|
||||
maxWidth: maxPanelWidth,
|
||||
onResize: debounce(handleResize),
|
||||
onResize: debounce((width: number) => {
|
||||
handleResize(width, 'user')
|
||||
}),
|
||||
})
|
||||
|
||||
return (
|
||||
|
||||
@@ -31,6 +31,7 @@ const translation = {
|
||||
searchTools: 'Search tools...',
|
||||
installPlugin: 'Install plugin',
|
||||
installFrom: 'INSTALL FROM',
|
||||
deprecated: 'Deprecated',
|
||||
list: {
|
||||
noInstalled: 'No plugins installed',
|
||||
notFound: 'No plugins found',
|
||||
@@ -101,6 +102,16 @@ const translation = {
|
||||
configureApp: 'Configure App',
|
||||
configureModel: 'Configure model',
|
||||
configureTool: 'Configure tool',
|
||||
deprecation: {
|
||||
fullMessage: 'This plugin has been deprecated due to {{deprecatedReason}}, and will no longer be updated. Please use <CustomLink href=\'https://example.com/\'>{{-alternativePluginId}}</CustomLink> instead.',
|
||||
onlyReason: 'This plugin has been deprecated due to {{deprecatedReason}} and will no longer be updated.',
|
||||
noReason: 'This plugin has been deprecated and will no longer be updated.',
|
||||
reason: {
|
||||
businessAdjustments: 'business adjustments',
|
||||
ownershipTransferred: 'ownership transferred',
|
||||
noMaintainer: 'no maintainer',
|
||||
},
|
||||
},
|
||||
},
|
||||
install: '{{num}} installs',
|
||||
installAction: 'Install',
|
||||
|
||||
@@ -84,6 +84,16 @@ const translation = {
|
||||
actionNum: '{{num}} {{action}} が含まれています',
|
||||
endpointsDocLink: 'ドキュメントを表示する',
|
||||
switchVersion: 'バージョンの切り替え',
|
||||
deprecation: {
|
||||
fullMessage: 'このプラグインは{{deprecatedReason}}のため非推奨となり、新しいバージョンはリリースされません。代わりに<CustomLink href=\'https://example.com/\'>{{-alternativePluginId}}</CustomLink>をご利用ください。',
|
||||
onlyReason: 'このプラグインは{{deprecatedReason}}のため非推奨となり、新しいバージョンはリリースされません。',
|
||||
noReason: 'このプラグインは廃止されており、今後更新されることはありません。',
|
||||
reason: {
|
||||
businessAdjustments: '事業調整',
|
||||
ownershipTransferred: '所有権移転',
|
||||
noMaintainer: 'メンテナーの不足',
|
||||
},
|
||||
},
|
||||
},
|
||||
debugInfo: {
|
||||
title: 'デバッグ',
|
||||
@@ -198,6 +208,7 @@ const translation = {
|
||||
install: '{{num}} インストール',
|
||||
installAction: 'インストール',
|
||||
installFrom: 'インストール元',
|
||||
deprecated: '非推奨',
|
||||
searchPlugins: '検索プラグイン',
|
||||
search: '検索',
|
||||
endpointsEnabled: '{{num}} セットのエンドポイントが有効になりました',
|
||||
|
||||
@@ -31,6 +31,7 @@ const translation = {
|
||||
searchTools: '搜索工具...',
|
||||
installPlugin: '安装插件',
|
||||
installFrom: '安装源',
|
||||
deprecated: '已弃用',
|
||||
list: {
|
||||
noInstalled: '无已安装的插件',
|
||||
notFound: '未找到插件',
|
||||
@@ -101,6 +102,16 @@ const translation = {
|
||||
configureApp: '应用设置',
|
||||
configureModel: '模型设置',
|
||||
configureTool: '工具设置',
|
||||
deprecation: {
|
||||
fullMessage: '由于{{deprecatedReason}},此插件已被弃用,将不再发布新版本。请使用<CustomLink href=\'https://example.com/\'>{{-alternativePluginId}}</CustomLink>替代。',
|
||||
onlyReason: '由于{{deprecatedReason}},此插件已被弃用,将不再发布新版本。',
|
||||
noReason: '此插件已被弃用,将不再发布新版本。',
|
||||
reason: {
|
||||
businessAdjustments: '业务调整',
|
||||
ownershipTransferred: '所有权转移',
|
||||
noMaintainer: '无人维护',
|
||||
},
|
||||
},
|
||||
},
|
||||
install: '{{num}} 次安装',
|
||||
installAction: '安装',
|
||||
|
||||
@@ -90,3 +90,7 @@ export const formatNumberAbbreviated = (num: number) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const snakeCase2CamelCase = (input: string): string => {
|
||||
return input.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user