merge main

This commit is contained in:
zxhlyh
2025-07-22 13:52:24 +08:00
40 changed files with 1066 additions and 158 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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