From 88b20bc6d00cc668d733c69e0316af8c8f71e734 Mon Sep 17 00:00:00 2001 From: Jyong <76649700+JohnJyong@users.noreply.github.com> Date: Wed, 10 Dec 2025 15:18:38 +0800 Subject: [PATCH 01/28] fix dataset multimodal field not update (#29403) --- api/controllers/console/datasets/datasets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/controllers/console/datasets/datasets.py b/api/controllers/console/datasets/datasets.py index c0422ef6f4..70b6e932e9 100644 --- a/api/controllers/console/datasets/datasets.py +++ b/api/controllers/console/datasets/datasets.py @@ -422,7 +422,6 @@ class DatasetApi(Resource): raise NotFound("Dataset not found.") payload = DatasetUpdatePayload.model_validate(console_ns.payload or {}) - payload_data = payload.model_dump(exclude_unset=True) current_user, current_tenant_id = current_account_with_tenant() # check embedding model setting if ( @@ -434,6 +433,7 @@ class DatasetApi(Resource): dataset.tenant_id, payload.embedding_model_provider, payload.embedding_model ) payload.is_multimodal = is_multimodal + payload_data = payload.model_dump(exclude_unset=True) # The role of the current user in the ta table must be admin, owner, editor, or dataset_operator DatasetPermissionService.check_permission( current_user, dataset, payload.permission, payload.partial_member_list From bafd093fa98b8ecfd69cddba5bebfd83412e5f84 Mon Sep 17 00:00:00 2001 From: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:41:05 +0800 Subject: [PATCH 02/28] fix: Add dataset file upload restrictions (#29397) Co-authored-by: kurokobo Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com> --- .../base/notion-page-selector/base.tsx | 5 +- .../credential-selector/index.tsx | 19 ++++---- .../page-selector/index.tsx | 48 +++++++++++++------ .../datasets/common/credential-icon.tsx | 17 ++++--- .../datasets/create/file-uploader/index.tsx | 22 ++++----- .../datasets/create/step-one/index.tsx | 5 +- .../website/base/crawled-result-item.tsx | 19 +++++++- .../create/website/base/crawled-result.tsx | 29 +++++++---- .../create/website/firecrawl/index.tsx | 7 ++- .../datasets/create/website/index.tsx | 5 ++ .../create/website/jina-reader/index.tsx | 9 ++-- .../datasets/create/website/preview.tsx | 2 +- .../create/website/watercrawl/index.tsx | 9 ++-- .../base/credential-selector/index.tsx | 4 -- .../base/credential-selector/item.tsx | 11 +---- .../base/credential-selector/list.tsx | 3 -- .../base/credential-selector/trigger.tsx | 12 +---- .../data-source/base/header.tsx | 4 +- .../data-source/local-file/index.tsx | 24 +++++----- .../data-source/online-documents/index.tsx | 6 ++- .../online-drive/file-list/index.tsx | 4 +- .../online-drive/file-list/list/index.tsx | 6 +-- .../data-source/online-drive/index.tsx | 13 +++-- .../base/crawled-result-item.tsx | 1 + .../data-source/website-crawl/index.tsx | 12 +++-- .../documents/create-from-pipeline/index.tsx | 20 ++++---- .../preview/web-preview.tsx | 4 +- .../detail/settings/document-settings.tsx | 2 +- .../settings/pipeline-settings/index.tsx | 4 +- .../panel/test-run/preparation/index.tsx | 11 +++-- .../nodes/data-source/before-run-form.tsx | 9 ++-- web/i18n/en-US/dataset-pipeline.ts | 3 -- web/i18n/ja-JP/dataset-pipeline.ts | 3 -- web/i18n/zh-Hans/dataset-pipeline.ts | 3 -- web/models/datasets.ts | 2 +- 35 files changed, 206 insertions(+), 151 deletions(-) diff --git a/web/app/components/base/notion-page-selector/base.tsx b/web/app/components/base/notion-page-selector/base.tsx index 1f9ddeaebd..9315605cdf 100644 --- a/web/app/components/base/notion-page-selector/base.tsx +++ b/web/app/components/base/notion-page-selector/base.tsx @@ -21,6 +21,7 @@ type NotionPageSelectorProps = { datasetId?: string credentialList: DataSourceCredential[] onSelectCredential?: (credentialId: string) => void + supportBatchUpload?: boolean } const NotionPageSelector = ({ @@ -32,6 +33,7 @@ const NotionPageSelector = ({ datasetId = '', credentialList, onSelectCredential, + supportBatchUpload = false, }: NotionPageSelectorProps) => { const [searchValue, setSearchValue] = useState('') const setShowAccountSettingModal = useModalContextSelector(s => s.setShowAccountSettingModal) @@ -110,7 +112,7 @@ const NotionPageSelector = ({ setCurrentCredential(credential) onSelect([]) // Clear selected pages when changing credential onSelectCredential?.(credential.credentialId) - }, [invalidPreImportNotionPages, onSelect, onSelectCredential]) + }, [datasetId, invalidPreImportNotionPages, notionCredentials, onSelect, onSelectCredential]) const handleSelectPages = useCallback((newSelectedPagesId: Set) => { const selectedPages = Array.from(newSelectedPagesId).map(pageId => pagesMapAndSelectedPagesId[0][pageId]) @@ -175,6 +177,7 @@ const NotionPageSelector = ({ canPreview={canPreview} previewPageId={previewPageId} onPreview={handlePreviewPage} + isMultipleChoice={supportBatchUpload} /> )} diff --git a/web/app/components/base/notion-page-selector/credential-selector/index.tsx b/web/app/components/base/notion-page-selector/credential-selector/index.tsx index f0ec399544..360a38ba8f 100644 --- a/web/app/components/base/notion-page-selector/credential-selector/index.tsx +++ b/web/app/components/base/notion-page-selector/credential-selector/index.tsx @@ -1,9 +1,8 @@ 'use client' -import { useTranslation } from 'react-i18next' import React, { Fragment, useMemo } from 'react' import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react' import { RiArrowDownSLine } from '@remixicon/react' -import NotionIcon from '../../notion-icon' +import { CredentialIcon } from '@/app/components/datasets/common/credential-icon' export type NotionCredential = { credentialId: string @@ -23,14 +22,10 @@ const CredentialSelector = ({ items, onSelect, }: CredentialSelectorProps) => { - const { t } = useTranslation() const currentCredential = items.find(item => item.credentialId === value)! const getDisplayName = (item: NotionCredential) => { - return item.workspaceName || t('datasetPipeline.credentialSelector.name', { - credentialName: item.credentialName, - pluginName: 'Notion', - }) + return item.workspaceName || item.credentialName } const currentDisplayName = useMemo(() => { @@ -43,10 +38,11 @@ const CredentialSelector = ({ ({ open }) => ( <> -
onSelect(item.credentialId)} > -
@@ -18,6 +19,7 @@ type PageSelectorProps = { canPreview?: boolean previewPageId?: string onPreview?: (selectedPageId: string) => void + isMultipleChoice?: boolean } type NotionPageTreeItem = { children: Set @@ -80,6 +82,7 @@ const ItemComponent = ({ index, style, data }: ListChildComponentProps<{ searchValue: string previewPageId: string pagesMap: DataSourceNotionPageMap + isMultipleChoice?: boolean }>) => { const { t } = useTranslation() const { @@ -94,6 +97,7 @@ const ItemComponent = ({ index, style, data }: ListChildComponentProps<{ searchValue, previewPageId, pagesMap, + isMultipleChoice, } = data const current = dataList[index] const currentWithChildrenAndDescendants = listMapWithChildrenAndDescendants[current.page_id] @@ -134,16 +138,24 @@ const ItemComponent = ({ index, style, data }: ListChildComponentProps<{ previewPageId === current.page_id && 'bg-state-base-hover')} style={{ ...style, top: style.top as number + 8, left: 8, right: 8, width: 'calc(100% - 16px)' }} > - { - if (disabled) - return - handleCheck(index) - }} - /> + {isMultipleChoice ? ( + { + handleCheck(index) + }} + />) : ( + { + handleCheck(index) + }} + /> + )} {!searchValue && renderArrow()} { const { t } = useTranslation() const [dataList, setDataList] = useState([]) @@ -265,7 +278,7 @@ const PageSelector = ({ const currentWithChildrenAndDescendants = listMapWithChildrenAndDescendants[pageId] if (copyValue.has(pageId)) { - if (!searchValue) { + if (!searchValue && isMultipleChoice) { for (const item of currentWithChildrenAndDescendants.descendants) copyValue.delete(item) } @@ -273,12 +286,18 @@ const PageSelector = ({ copyValue.delete(pageId) } else { - if (!searchValue) { + if (!searchValue && isMultipleChoice) { for (const item of currentWithChildrenAndDescendants.descendants) copyValue.add(item) } - - copyValue.add(pageId) + // Single choice mode, clear previous selection + if (!isMultipleChoice && copyValue.size > 0) { + copyValue.clear() + copyValue.add(pageId) + } + else { + copyValue.add(pageId) + } } onSelect(new Set(copyValue)) @@ -322,6 +341,7 @@ const PageSelector = ({ searchValue, previewPageId: currentPreviewPageId, pagesMap, + isMultipleChoice, }} > {Item} diff --git a/web/app/components/datasets/common/credential-icon.tsx b/web/app/components/datasets/common/credential-icon.tsx index 5a25963f3b..d4e6fd69ac 100644 --- a/web/app/components/datasets/common/credential-icon.tsx +++ b/web/app/components/datasets/common/credential-icon.tsx @@ -2,7 +2,7 @@ import cn from '@/utils/classnames' import React, { useCallback, useMemo, useState } from 'react' type CredentialIconProps = { - avatar_url?: string + avatarUrl?: string name: string size?: number className?: string @@ -16,12 +16,12 @@ const ICON_BG_COLORS = [ ] export const CredentialIcon: React.FC = ({ - avatar_url, + avatarUrl, name, size = 20, className = '', }) => { - const [showAvatar, setShowAvatar] = useState(!!avatar_url && avatar_url !== 'default') + const [showAvatar, setShowAvatar] = useState(!!avatarUrl && avatarUrl !== 'default') const firstLetter = useMemo(() => name.charAt(0).toUpperCase(), [name]) const bgColor = useMemo(() => ICON_BG_COLORS[firstLetter.charCodeAt(0) % ICON_BG_COLORS.length], [firstLetter]) @@ -29,17 +29,20 @@ export const CredentialIcon: React.FC = ({ setShowAvatar(false) }, []) - if (avatar_url && avatar_url !== 'default' && showAvatar) { + if (avatarUrl && avatarUrl !== 'default' && showAvatar) { return (
diff --git a/web/app/components/datasets/create/file-uploader/index.tsx b/web/app/components/datasets/create/file-uploader/index.tsx index abe2564ad2..700a5f7680 100644 --- a/web/app/components/datasets/create/file-uploader/index.tsx +++ b/web/app/components/datasets/create/file-uploader/index.tsx @@ -25,7 +25,7 @@ type IFileUploaderProps = { onFileUpdate: (fileItem: FileItem, progress: number, list: FileItem[]) => void onFileListUpdate?: (files: FileItem[]) => void onPreview: (file: File) => void - notSupportBatchUpload?: boolean + supportBatchUpload?: boolean } const FileUploader = ({ @@ -35,7 +35,7 @@ const FileUploader = ({ onFileUpdate, onFileListUpdate, onPreview, - notSupportBatchUpload, + supportBatchUpload = false, }: IFileUploaderProps) => { const { t } = useTranslation() const { notify } = useContext(ToastContext) @@ -44,7 +44,7 @@ const FileUploader = ({ const dropRef = useRef(null) const dragRef = useRef(null) const fileUploader = useRef(null) - const hideUpload = notSupportBatchUpload && fileList.length > 0 + const hideUpload = !supportBatchUpload && fileList.length > 0 const { data: fileUploadConfigResponse } = useFileUploadConfig() const { data: supportFileTypesResponse } = useFileSupportTypes() @@ -68,9 +68,9 @@ const FileUploader = ({ const ACCEPTS = supportTypes.map((ext: string) => `.${ext}`) const fileUploadConfig = useMemo(() => ({ file_size_limit: fileUploadConfigResponse?.file_size_limit ?? 15, - batch_count_limit: fileUploadConfigResponse?.batch_count_limit ?? 5, - file_upload_limit: fileUploadConfigResponse?.file_upload_limit ?? 5, - }), [fileUploadConfigResponse]) + batch_count_limit: supportBatchUpload ? (fileUploadConfigResponse?.batch_count_limit ?? 5) : 1, + file_upload_limit: supportBatchUpload ? (fileUploadConfigResponse?.file_upload_limit ?? 5) : 1, + }), [fileUploadConfigResponse, supportBatchUpload]) const fileListRef = useRef([]) @@ -254,12 +254,12 @@ const FileUploader = ({ }), ) let files = nested.flat() - if (notSupportBatchUpload) files = files.slice(0, 1) + if (!supportBatchUpload) files = files.slice(0, 1) files = files.slice(0, fileUploadConfig.batch_count_limit) const valid = files.filter(isValid) initialUpload(valid) }, - [initialUpload, isValid, notSupportBatchUpload, traverseFileEntry, fileUploadConfig], + [initialUpload, isValid, supportBatchUpload, traverseFileEntry, fileUploadConfig], ) const selectHandle = () => { if (fileUploader.current) @@ -303,7 +303,7 @@ const FileUploader = ({ id="fileUploader" className="hidden" type="file" - multiple={!notSupportBatchUpload} + multiple={supportBatchUpload} accept={ACCEPTS.join(',')} onChange={fileChangeHandle} /> @@ -317,7 +317,7 @@ const FileUploader = ({ - {notSupportBatchUpload ? t('datasetCreation.stepOne.uploader.buttonSingleFile') : t('datasetCreation.stepOne.uploader.button')} + {supportBatchUpload ? t('datasetCreation.stepOne.uploader.button') : t('datasetCreation.stepOne.uploader.buttonSingleFile')} {supportTypes.length > 0 && ( )} @@ -326,7 +326,7 @@ const FileUploader = ({
{t('datasetCreation.stepOne.uploader.tip', { size: fileUploadConfig.file_size_limit, supportTypes: supportTypesShowNames, - batchCount: notSupportBatchUpload ? 1 : fileUploadConfig.batch_count_limit, + batchCount: fileUploadConfig.batch_count_limit, totalCount: fileUploadConfig.file_upload_limit, })}
{dragging &&
} diff --git a/web/app/components/datasets/create/step-one/index.tsx b/web/app/components/datasets/create/step-one/index.tsx index cab1637661..f2768be470 100644 --- a/web/app/components/datasets/create/step-one/index.tsx +++ b/web/app/components/datasets/create/step-one/index.tsx @@ -110,7 +110,7 @@ const StepOne = ({ const hasNotin = notionPages.length > 0 const isVectorSpaceFull = plan.usage.vectorSpace >= plan.total.vectorSpace const isShowVectorSpaceFull = (allFileLoaded || hasNotin) && isVectorSpaceFull && enableBilling - const notSupportBatchUpload = enableBilling && plan.type === 'sandbox' + const supportBatchUpload = !enableBilling || plan.type !== 'sandbox' const nextDisabled = useMemo(() => { if (!files.length) return true @@ -229,7 +229,7 @@ const StepOne = ({ onFileListUpdate={updateFileList} onFileUpdate={updateFile} onPreview={updateCurrentFile} - notSupportBatchUpload={notSupportBatchUpload} + supportBatchUpload={supportBatchUpload} /> {isShowVectorSpaceFull && (
@@ -259,6 +259,7 @@ const StepOne = ({ credentialList={notionCredentialList} onSelectCredential={updateNotionCredentialId} datasetId={datasetId} + supportBatchUpload={supportBatchUpload} />
{isShowVectorSpaceFull && ( diff --git a/web/app/components/datasets/create/website/base/crawled-result-item.tsx b/web/app/components/datasets/create/website/base/crawled-result-item.tsx index 8ea316f62a..51e043c35a 100644 --- a/web/app/components/datasets/create/website/base/crawled-result-item.tsx +++ b/web/app/components/datasets/create/website/base/crawled-result-item.tsx @@ -6,6 +6,7 @@ import cn from '@/utils/classnames' import type { CrawlResultItem as CrawlResultItemType } from '@/models/datasets' import Checkbox from '@/app/components/base/checkbox' import Button from '@/app/components/base/button' +import Radio from '@/app/components/base/radio/ui' type Props = { payload: CrawlResultItemType @@ -13,6 +14,7 @@ type Props = { isPreview: boolean onCheckChange: (checked: boolean) => void onPreview: () => void + isMultipleChoice: boolean } const CrawledResultItem: FC = ({ @@ -21,6 +23,7 @@ const CrawledResultItem: FC = ({ isChecked, onCheckChange, onPreview, + isMultipleChoice, }) => { const { t } = useTranslation() @@ -31,7 +34,21 @@ const CrawledResultItem: FC = ({
- + { + isMultipleChoice ? ( + + ) : ( + + ) + }
void onPreview: (payload: CrawlResultItem) => void usedTime: number + isMultipleChoice: boolean } const CrawledResult: FC = ({ @@ -25,6 +26,7 @@ const CrawledResult: FC = ({ onSelectedChange, onPreview, usedTime, + isMultipleChoice, }) => { const { t } = useTranslation() @@ -40,13 +42,17 @@ const CrawledResult: FC = ({ const handleItemCheckChange = useCallback((item: CrawlResultItem) => { return (checked: boolean) => { - if (checked) - onSelectedChange([...checkedList, item]) - - else + if (checked) { + if (isMultipleChoice) + onSelectedChange([...checkedList, item]) + else + onSelectedChange([item]) + } + else { onSelectedChange(checkedList.filter(checkedItem => checkedItem.source_url !== item.source_url)) + } } - }, [checkedList, onSelectedChange]) + }, [checkedList, isMultipleChoice, onSelectedChange]) const [previewIndex, setPreviewIndex] = React.useState(-1) const handlePreview = useCallback((index: number) => { @@ -59,11 +65,13 @@ const CrawledResult: FC = ({ return (
- + {isMultipleChoice && ( + + )}
{t(`${I18N_PREFIX}.scrapTimeInfo`, { total: list.length, @@ -80,6 +88,7 @@ const CrawledResult: FC = ({ payload={item} isChecked={checkedList.some(checkedItem => checkedItem.source_url === item.source_url)} onCheckChange={handleItemCheckChange(item)} + isMultipleChoice={isMultipleChoice} /> ))}
diff --git a/web/app/components/datasets/create/website/firecrawl/index.tsx b/web/app/components/datasets/create/website/firecrawl/index.tsx index 51c2c7d505..1ef934308a 100644 --- a/web/app/components/datasets/create/website/firecrawl/index.tsx +++ b/web/app/components/datasets/create/website/firecrawl/index.tsx @@ -26,6 +26,7 @@ type Props = { onJobIdChange: (jobId: string) => void crawlOptions: CrawlOptions onCrawlOptionsChange: (payload: CrawlOptions) => void + supportBatchUpload: boolean } enum Step { @@ -41,6 +42,7 @@ const FireCrawl: FC = ({ onJobIdChange, crawlOptions, onCrawlOptionsChange, + supportBatchUpload, }) => { const { t } = useTranslation() const [step, setStep] = useState(Step.init) @@ -171,7 +173,7 @@ const FireCrawl: FC = ({ content: item.markdown, })) setCrawlResult(data) - onCheckedCrawlResultChange(data.data || []) // default select the crawl result + onCheckedCrawlResultChange(supportBatchUpload ? (data.data || []) : (data.data?.slice(0, 1) || [])) // default select the crawl result setCrawlErrorMessage('') } } @@ -182,7 +184,7 @@ const FireCrawl: FC = ({ finally { setStep(Step.finished) } - }, [checkValid, crawlOptions, onJobIdChange, t, waitForCrawlFinished, onCheckedCrawlResultChange]) + }, [checkValid, crawlOptions, onJobIdChange, waitForCrawlFinished, t, onCheckedCrawlResultChange, supportBatchUpload]) return (
@@ -221,6 +223,7 @@ const FireCrawl: FC = ({ onSelectedChange={onCheckedCrawlResultChange} onPreview={onPreview} usedTime={Number.parseFloat(crawlResult?.time_consuming as string) || 0} + isMultipleChoice={supportBatchUpload} /> }
diff --git a/web/app/components/datasets/create/website/index.tsx b/web/app/components/datasets/create/website/index.tsx index ee7ace6815..15324f642e 100644 --- a/web/app/components/datasets/create/website/index.tsx +++ b/web/app/components/datasets/create/website/index.tsx @@ -24,6 +24,7 @@ type Props = { crawlOptions: CrawlOptions onCrawlOptionsChange: (payload: CrawlOptions) => void authedDataSourceList: DataSourceAuth[] + supportBatchUpload?: boolean } const Website: FC = ({ @@ -35,6 +36,7 @@ const Website: FC = ({ crawlOptions, onCrawlOptionsChange, authedDataSourceList, + supportBatchUpload = false, }) => { const { t } = useTranslation() const { setShowAccountSettingModal } = useModalContext() @@ -116,6 +118,7 @@ const Website: FC = ({ onJobIdChange={onJobIdChange} crawlOptions={crawlOptions} onCrawlOptionsChange={onCrawlOptionsChange} + supportBatchUpload={supportBatchUpload} /> )} {source && selectedProvider === DataSourceProvider.waterCrawl && ( @@ -126,6 +129,7 @@ const Website: FC = ({ onJobIdChange={onJobIdChange} crawlOptions={crawlOptions} onCrawlOptionsChange={onCrawlOptionsChange} + supportBatchUpload={supportBatchUpload} /> )} {source && selectedProvider === DataSourceProvider.jinaReader && ( @@ -136,6 +140,7 @@ const Website: FC = ({ onJobIdChange={onJobIdChange} crawlOptions={crawlOptions} onCrawlOptionsChange={onCrawlOptionsChange} + supportBatchUpload={supportBatchUpload} /> )} {!source && ( diff --git a/web/app/components/datasets/create/website/jina-reader/index.tsx b/web/app/components/datasets/create/website/jina-reader/index.tsx index b6e6177af2..b2189b3e5c 100644 --- a/web/app/components/datasets/create/website/jina-reader/index.tsx +++ b/web/app/components/datasets/create/website/jina-reader/index.tsx @@ -26,6 +26,7 @@ type Props = { onJobIdChange: (jobId: string) => void crawlOptions: CrawlOptions onCrawlOptionsChange: (payload: CrawlOptions) => void + supportBatchUpload: boolean } enum Step { @@ -41,6 +42,7 @@ const JinaReader: FC = ({ onJobIdChange, crawlOptions, onCrawlOptionsChange, + supportBatchUpload, }) => { const { t } = useTranslation() const [step, setStep] = useState(Step.init) @@ -157,7 +159,7 @@ const JinaReader: FC = ({ total: 1, data: [{ title, - content, + markdown: content, description, source_url: url, }], @@ -176,7 +178,7 @@ const JinaReader: FC = ({ } else { setCrawlResult(data) - onCheckedCrawlResultChange(data.data || []) // default select the crawl result + onCheckedCrawlResultChange(supportBatchUpload ? (data.data || []) : (data.data?.slice(0, 1) || [])) // default select the crawl result setCrawlErrorMessage('') } } @@ -188,7 +190,7 @@ const JinaReader: FC = ({ finally { setStep(Step.finished) } - }, [checkValid, crawlOptions, onCheckedCrawlResultChange, onJobIdChange, t, waitForCrawlFinished]) + }, [checkValid, crawlOptions, onCheckedCrawlResultChange, onJobIdChange, supportBatchUpload, t, waitForCrawlFinished]) return (
@@ -227,6 +229,7 @@ const JinaReader: FC = ({ onSelectedChange={onCheckedCrawlResultChange} onPreview={onPreview} usedTime={Number.parseFloat(crawlResult?.time_consuming as string) || 0} + isMultipleChoice={supportBatchUpload} /> }
diff --git a/web/app/components/datasets/create/website/preview.tsx b/web/app/components/datasets/create/website/preview.tsx index d148c87196..f43dc83589 100644 --- a/web/app/components/datasets/create/website/preview.tsx +++ b/web/app/components/datasets/create/website/preview.tsx @@ -32,7 +32,7 @@ const WebsitePreview = ({
{payload.source_url}
-
{payload.content}
+
{payload.markdown}
) diff --git a/web/app/components/datasets/create/website/watercrawl/index.tsx b/web/app/components/datasets/create/website/watercrawl/index.tsx index 67a3e53feb..bf0048b788 100644 --- a/web/app/components/datasets/create/website/watercrawl/index.tsx +++ b/web/app/components/datasets/create/website/watercrawl/index.tsx @@ -26,6 +26,7 @@ type Props = { onJobIdChange: (jobId: string) => void crawlOptions: CrawlOptions onCrawlOptionsChange: (payload: CrawlOptions) => void + supportBatchUpload: boolean } enum Step { @@ -41,6 +42,7 @@ const WaterCrawl: FC = ({ onJobIdChange, crawlOptions, onCrawlOptionsChange, + supportBatchUpload, }) => { const { t } = useTranslation() const [step, setStep] = useState(Step.init) @@ -132,7 +134,7 @@ const WaterCrawl: FC = ({ }, } } - }, [crawlOptions.limit]) + }, [crawlOptions.limit, onCheckedCrawlResultChange]) const handleRun = useCallback(async (url: string) => { const { isValid, errorMsg } = checkValid(url) @@ -163,7 +165,7 @@ const WaterCrawl: FC = ({ } else { setCrawlResult(data) - onCheckedCrawlResultChange(data.data || []) // default select the crawl result + onCheckedCrawlResultChange(supportBatchUpload ? (data.data || []) : (data.data?.slice(0, 1) || [])) // default select the crawl result setCrawlErrorMessage('') } } @@ -174,7 +176,7 @@ const WaterCrawl: FC = ({ finally { setStep(Step.finished) } - }, [checkValid, crawlOptions, onJobIdChange, t, waitForCrawlFinished]) + }, [checkValid, crawlOptions, onCheckedCrawlResultChange, onJobIdChange, supportBatchUpload, t, waitForCrawlFinished]) return (
@@ -213,6 +215,7 @@ const WaterCrawl: FC = ({ onSelectedChange={onCheckedCrawlResultChange} onPreview={onPreview} usedTime={Number.parseFloat(crawlResult?.time_consuming as string) || 0} + isMultipleChoice={supportBatchUpload} /> }
diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/index.tsx index 0de3879969..0e588e4e1d 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/index.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/index.tsx @@ -10,14 +10,12 @@ import Trigger from './trigger' import List from './list' export type CredentialSelectorProps = { - pluginName: string currentCredentialId: string onCredentialChange: (credentialId: string) => void credentials: Array } const CredentialSelector = ({ - pluginName, currentCredentialId, onCredentialChange, credentials, @@ -50,7 +48,6 @@ const CredentialSelector = ({ @@ -58,7 +55,6 @@ const CredentialSelector = ({ diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/item.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/item.tsx index ab8de51fb1..9c8368e299 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/item.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/item.tsx @@ -2,22 +2,18 @@ import { CredentialIcon } from '@/app/components/datasets/common/credential-icon import type { DataSourceCredential } from '@/types/pipeline' import { RiCheckLine } from '@remixicon/react' import React, { useCallback } from 'react' -import { useTranslation } from 'react-i18next' type ItemProps = { credential: DataSourceCredential - pluginName: string isSelected: boolean onCredentialChange: (credentialId: string) => void } const Item = ({ credential, - pluginName, isSelected, onCredentialChange, }: ItemProps) => { - const { t } = useTranslation() const { avatar_url, name } = credential const handleCredentialChange = useCallback(() => { @@ -30,15 +26,12 @@ const Item = ({ onClick={handleCredentialChange} > - {t('datasetPipeline.credentialSelector.name', { - credentialName: name, - pluginName, - })} + {name} { isSelected && ( diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/list.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/list.tsx index b161a80309..cdcb2b5af5 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/list.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/list.tsx @@ -5,14 +5,12 @@ import Item from './item' type ListProps = { currentCredentialId: string credentials: Array - pluginName: string onCredentialChange: (credentialId: string) => void } const List = ({ currentCredentialId, credentials, - pluginName, onCredentialChange, }: ListProps) => { return ( @@ -24,7 +22,6 @@ const List = ({ diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/trigger.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/trigger.tsx index 88f47384f3..dc328ef87f 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/trigger.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/trigger.tsx @@ -1,23 +1,18 @@ import React from 'react' import type { DataSourceCredential } from '@/types/pipeline' -import { useTranslation } from 'react-i18next' import { RiArrowDownSLine } from '@remixicon/react' import cn from '@/utils/classnames' import { CredentialIcon } from '@/app/components/datasets/common/credential-icon' type TriggerProps = { currentCredential: DataSourceCredential | undefined - pluginName: string isOpen: boolean } const Trigger = ({ currentCredential, - pluginName, isOpen, }: TriggerProps) => { - const { t } = useTranslation() - const { avatar_url, name = '', @@ -31,16 +26,13 @@ const Trigger = ({ )} >
- {t('datasetPipeline.credentialSelector.name', { - credentialName: name, - pluginName, - })} + {name}
diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/header.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/header.tsx index ef8932ba24..b826e53d93 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/header.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/header.tsx @@ -11,12 +11,14 @@ type HeaderProps = { docTitle: string docLink: string onClickConfiguration?: () => void + pluginName: string } & CredentialSelectorProps const Header = ({ docTitle, docLink, onClickConfiguration, + pluginName, ...rest }: HeaderProps) => { const { t } = useTranslation() @@ -29,7 +31,7 @@ const Header = ({ />
) diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/crawled-result-item.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/crawled-result-item.tsx index 753b32c396..bdfcddfd77 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/crawled-result-item.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/crawled-result-item.tsx @@ -46,6 +46,7 @@ const CrawledResultItem = ({ /> ) : ( diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/index.tsx index 648f6a5d93..513ac8edd9 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/index.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/index.tsx @@ -33,14 +33,16 @@ const I18N_PREFIX = 'datasetCreation.stepOne.website' export type WebsiteCrawlProps = { nodeId: string nodeData: DataSourceNodeType - isInPipeline?: boolean onCredentialChange: (credentialId: string) => void + isInPipeline?: boolean + supportBatchUpload?: boolean } const WebsiteCrawl = ({ nodeId, nodeData, isInPipeline = false, + supportBatchUpload = false, onCredentialChange, }: WebsiteCrawlProps) => { const { t } = useTranslation() @@ -122,7 +124,7 @@ const WebsiteCrawl = ({ time_consuming: time_consuming ?? 0, } setCrawlResult(crawlResultData) - handleCheckedCrawlResultChange(isInPipeline ? [crawlData[0]] : crawlData) // default select the crawl result + handleCheckedCrawlResultChange(supportBatchUpload ? crawlData : crawlData.slice(0, 1)) // default select the crawl result setCrawlErrorMessage('') setStep(CrawlStep.finished) }, @@ -132,7 +134,7 @@ const WebsiteCrawl = ({ }, }, ) - }, [dataSourceStore, datasourceNodeRunURL, handleCheckedCrawlResultChange, isInPipeline, t]) + }, [dataSourceStore, datasourceNodeRunURL, handleCheckedCrawlResultChange, supportBatchUpload, t]) const handleSubmit = useCallback((value: Record) => { handleRun(value) @@ -149,7 +151,7 @@ const WebsiteCrawl = ({ setTotalNum(0) setCrawlErrorMessage('') onCredentialChange(credentialId) - }, [dataSourceStore, onCredentialChange]) + }, [onCredentialChange]) return (
@@ -195,7 +197,7 @@ const WebsiteCrawl = ({ previewIndex={previewIndex} onPreview={handlePreview} showPreview={!isInPipeline} - isMultipleChoice={!isInPipeline} // only support single choice in test run + isMultipleChoice={supportBatchUpload} // only support single choice in test run /> )}
diff --git a/web/app/components/datasets/documents/create-from-pipeline/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/index.tsx index 77b77700ca..1d9232403a 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/index.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/index.tsx @@ -102,7 +102,7 @@ const CreateFormPipeline = () => { return onlineDriveFileList.length > 0 && isVectorSpaceFull && enableBilling return false }, [allFileLoaded, datasource, datasourceType, enableBilling, isVectorSpaceFull, onlineDocuments.length, onlineDriveFileList.length, websitePages.length]) - const notSupportBatchUpload = enableBilling && plan.type === 'sandbox' + const supportBatchUpload = !enableBilling || plan.type !== 'sandbox' const nextBtnDisabled = useMemo(() => { if (!datasource) return true @@ -125,15 +125,16 @@ const CreateFormPipeline = () => { const showSelect = useMemo(() => { if (datasourceType === DatasourceType.onlineDocument) { const pagesCount = currentWorkspace?.pages.length ?? 0 - return pagesCount > 0 + return supportBatchUpload && pagesCount > 0 } if (datasourceType === DatasourceType.onlineDrive) { const isBucketList = onlineDriveFileList.some(file => file.type === 'bucket') - return !isBucketList && onlineDriveFileList.filter((item) => { + return supportBatchUpload && !isBucketList && onlineDriveFileList.filter((item) => { return item.type !== 'bucket' }).length > 0 } - }, [currentWorkspace?.pages.length, datasourceType, onlineDriveFileList]) + return false + }, [currentWorkspace?.pages.length, datasourceType, supportBatchUpload, onlineDriveFileList]) const totalOptions = useMemo(() => { if (datasourceType === DatasourceType.onlineDocument) @@ -395,7 +396,7 @@ const CreateFormPipeline = () => { clearWebsiteCrawlData() else if (dataSource.nodeData.provider_type === DatasourceType.onlineDrive) clearOnlineDriveData() - }, []) + }, [clearOnlineDocumentData, clearOnlineDriveData, clearWebsiteCrawlData]) const handleSwitchDataSource = useCallback((dataSource: Datasource) => { const { @@ -406,13 +407,13 @@ const CreateFormPipeline = () => { setCurrentCredentialId('') currentNodeIdRef.current = dataSource.nodeId setDatasource(dataSource) - }, [dataSourceStore]) + }, [clearDataSourceData, dataSourceStore]) const handleCredentialChange = useCallback((credentialId: string) => { const { setCurrentCredentialId } = dataSourceStore.getState() clearDataSourceData(datasource!) setCurrentCredentialId(credentialId) - }, [dataSourceStore, datasource]) + }, [clearDataSourceData, dataSourceStore, datasource]) if (isFetchingPipelineInfo) { return ( @@ -443,7 +444,7 @@ const CreateFormPipeline = () => { {datasourceType === DatasourceType.localFile && ( )} {datasourceType === DatasourceType.onlineDocument && ( @@ -451,6 +452,7 @@ const CreateFormPipeline = () => { nodeId={datasource!.nodeId} nodeData={datasource!.nodeData} onCredentialChange={handleCredentialChange} + supportBatchUpload={supportBatchUpload} /> )} {datasourceType === DatasourceType.websiteCrawl && ( @@ -458,6 +460,7 @@ const CreateFormPipeline = () => { nodeId={datasource!.nodeId} nodeData={datasource!.nodeData} onCredentialChange={handleCredentialChange} + supportBatchUpload={supportBatchUpload} /> )} {datasourceType === DatasourceType.onlineDrive && ( @@ -465,6 +468,7 @@ const CreateFormPipeline = () => { nodeId={datasource!.nodeId} nodeData={datasource!.nodeData} onCredentialChange={handleCredentialChange} + supportBatchUpload={supportBatchUpload} /> )} {isShowVectorSpaceFull && ( diff --git a/web/app/components/datasets/documents/create-from-pipeline/preview/web-preview.tsx b/web/app/components/datasets/documents/create-from-pipeline/preview/web-preview.tsx index bae4deb86e..ce7a5da24c 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/preview/web-preview.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/preview/web-preview.tsx @@ -27,7 +27,7 @@ const WebsitePreview = ({ {currentWebsite.source_url} · · - {`${formatNumberAbbreviated(currentWebsite.content.length)} ${t('datasetPipeline.addDocuments.characters')}`} + {`${formatNumberAbbreviated(currentWebsite.markdown.length)} ${t('datasetPipeline.addDocuments.characters')}`}
- {currentWebsite.content} + {currentWebsite.markdown}
) diff --git a/web/app/components/datasets/documents/detail/settings/document-settings.tsx b/web/app/components/datasets/documents/detail/settings/document-settings.tsx index 3bcb8ef3aa..16c90c925f 100644 --- a/web/app/components/datasets/documents/detail/settings/document-settings.tsx +++ b/web/app/components/datasets/documents/detail/settings/document-settings.tsx @@ -113,7 +113,7 @@ const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => { return [{ title: websiteInfo.title, source_url: websiteInfo.source_url, - content: websiteInfo.content, + markdown: websiteInfo.content, description: websiteInfo.description, }] }, [websiteInfo]) diff --git a/web/app/components/datasets/documents/detail/settings/pipeline-settings/index.tsx b/web/app/components/datasets/documents/detail/settings/pipeline-settings/index.tsx index 1ab47be445..0381222415 100644 --- a/web/app/components/datasets/documents/detail/settings/pipeline-settings/index.tsx +++ b/web/app/components/datasets/documents/detail/settings/pipeline-settings/index.tsx @@ -55,7 +55,7 @@ const PipelineSettings = ({ if (lastRunData?.datasource_type === DatasourceType.websiteCrawl) { const { content, description, source_url, title } = lastRunData.datasource_info websitePages.push({ - content, + markdown: content, description, source_url, title, @@ -135,7 +135,7 @@ const PipelineSettings = ({ push(`/datasets/${datasetId}/documents`) }, }) - }, [datasetId, invalidDocumentDetail, invalidDocumentList, lastRunData, pipelineId, push, runPublishedPipeline]) + }, [datasetId, documentId, invalidDocumentDetail, invalidDocumentList, lastRunData, pipelineId, push, runPublishedPipeline]) const onClickProcess = useCallback(() => { isPreview.current = false diff --git a/web/app/components/rag-pipeline/components/panel/test-run/preparation/index.tsx b/web/app/components/rag-pipeline/components/panel/test-run/preparation/index.tsx index eb73599314..c659d8669a 100644 --- a/web/app/components/rag-pipeline/components/panel/test-run/preparation/index.tsx +++ b/web/app/components/rag-pipeline/components/panel/test-run/preparation/index.tsx @@ -131,7 +131,7 @@ const Preparation = () => { clearWebsiteCrawlData() else if (dataSource.nodeData.provider_type === DatasourceType.onlineDrive) clearOnlineDriveData() - }, []) + }, [clearOnlineDocumentData, clearOnlineDriveData, clearWebsiteCrawlData]) const handleSwitchDataSource = useCallback((dataSource: Datasource) => { const { @@ -142,13 +142,13 @@ const Preparation = () => { setCurrentCredentialId('') currentNodeIdRef.current = dataSource.nodeId setDatasource(dataSource) - }, [dataSourceStore]) + }, [clearDataSourceData, dataSourceStore]) const handleCredentialChange = useCallback((credentialId: string) => { const { setCurrentCredentialId } = dataSourceStore.getState() clearDataSourceData(datasource!) setCurrentCredentialId(credentialId) - }, [dataSourceStore, datasource]) + }, [clearDataSourceData, dataSourceStore, datasource]) return ( <> @@ -164,7 +164,7 @@ const Preparation = () => { {datasourceType === DatasourceType.localFile && ( )} {datasourceType === DatasourceType.onlineDocument && ( @@ -173,6 +173,7 @@ const Preparation = () => { nodeData={datasource!.nodeData} isInPipeline onCredentialChange={handleCredentialChange} + supportBatchUpload={false} /> )} {datasourceType === DatasourceType.websiteCrawl && ( @@ -181,6 +182,7 @@ const Preparation = () => { nodeData={datasource!.nodeData} isInPipeline onCredentialChange={handleCredentialChange} + supportBatchUpload={false} /> )} {datasourceType === DatasourceType.onlineDrive && ( @@ -189,6 +191,7 @@ const Preparation = () => { nodeData={datasource!.nodeData} isInPipeline onCredentialChange={handleCredentialChange} + supportBatchUpload={false} /> )}
diff --git a/web/app/components/workflow/nodes/data-source/before-run-form.tsx b/web/app/components/workflow/nodes/data-source/before-run-form.tsx index 764599b4cb..521fdfb087 100644 --- a/web/app/components/workflow/nodes/data-source/before-run-form.tsx +++ b/web/app/components/workflow/nodes/data-source/before-run-form.tsx @@ -43,13 +43,13 @@ const BeforeRunForm: FC = (props) => { clearWebsiteCrawlData() else if (datasourceType === DatasourceType.onlineDrive) clearOnlineDriveData() - }, [datasourceType]) + }, [clearOnlineDocumentData, clearOnlineDriveData, clearWebsiteCrawlData, datasourceType]) const handleCredentialChange = useCallback((credentialId: string) => { const { setCurrentCredentialId } = dataSourceStore.getState() clearDataSourceData() setCurrentCredentialId(credentialId) - }, [dataSourceStore]) + }, [clearDataSourceData, dataSourceStore]) return ( = (props) => { {datasourceType === DatasourceType.localFile && ( )} {datasourceType === DatasourceType.onlineDocument && ( @@ -69,6 +69,7 @@ const BeforeRunForm: FC = (props) => { nodeData={datasourceNodeData} isInPipeline onCredentialChange={handleCredentialChange} + supportBatchUpload={false} /> )} {datasourceType === DatasourceType.websiteCrawl && ( @@ -77,6 +78,7 @@ const BeforeRunForm: FC = (props) => { nodeData={datasourceNodeData} isInPipeline onCredentialChange={handleCredentialChange} + supportBatchUpload={false} /> )} {datasourceType === DatasourceType.onlineDrive && ( @@ -85,6 +87,7 @@ const BeforeRunForm: FC = (props) => { nodeData={datasourceNodeData} isInPipeline onCredentialChange={handleCredentialChange} + supportBatchUpload={false} /> )}
diff --git a/web/i18n/en-US/dataset-pipeline.ts b/web/i18n/en-US/dataset-pipeline.ts index c83d358eec..29237e844a 100644 --- a/web/i18n/en-US/dataset-pipeline.ts +++ b/web/i18n/en-US/dataset-pipeline.ts @@ -145,9 +145,6 @@ const translation = { emptySearchResult: 'No items were found', resetKeywords: 'Reset keywords', }, - credentialSelector: { - name: '{{credentialName}}\'s {{pluginName}}', - }, configurationTip: 'Configure {{pluginName}}', conversion: { title: 'Convert to Knowledge Pipeline', diff --git a/web/i18n/ja-JP/dataset-pipeline.ts b/web/i18n/ja-JP/dataset-pipeline.ts index 0dddb25356..5091c17807 100644 --- a/web/i18n/ja-JP/dataset-pipeline.ts +++ b/web/i18n/ja-JP/dataset-pipeline.ts @@ -137,9 +137,6 @@ const translation = { emptySearchResult: 'アイテムは見つかりませんでした', resetKeywords: 'キーワードをリセットする', }, - credentialSelector: { - name: '{{credentialName}}の{{pluginName}}', - }, configurationTip: '{{pluginName}}を設定', conversion: { confirm: { diff --git a/web/i18n/zh-Hans/dataset-pipeline.ts b/web/i18n/zh-Hans/dataset-pipeline.ts index 7fbe8a0532..0e23d7a1e0 100644 --- a/web/i18n/zh-Hans/dataset-pipeline.ts +++ b/web/i18n/zh-Hans/dataset-pipeline.ts @@ -145,9 +145,6 @@ const translation = { emptySearchResult: '未找到任何项目', resetKeywords: '重置关键词', }, - credentialSelector: { - name: '{{credentialName}} 的 {{pluginName}}', - }, configurationTip: '配置 {{pluginName}}', conversion: { title: '转换为知识流水线', diff --git a/web/models/datasets.ts b/web/models/datasets.ts index 574897a9b4..fe4c568e46 100644 --- a/web/models/datasets.ts +++ b/web/models/datasets.ts @@ -156,7 +156,7 @@ export type CrawlOptions = { export type CrawlResultItem = { title: string - content: string + markdown: string description: string source_url: string } From e477e6c9286ea68100da1bd06613f89b6ecdee57 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:46:48 +0800 Subject: [PATCH 03/28] fix: harden async window open placeholder logic (#29393) --- .../components/app/app-publisher/index.tsx | 33 +++-- web/app/components/apps/app-card.tsx | 32 ++--- .../components/billing/billing-page/index.tsx | 31 ++--- .../pricing/plans/cloud-plan-item/index.tsx | 18 +-- web/hooks/use-async-window-open.spec.ts | 116 ++++++++++++++++++ web/hooks/use-async-window-open.ts | 105 +++++++--------- 6 files changed, 213 insertions(+), 122 deletions(-) create mode 100644 web/hooks/use-async-window-open.spec.ts diff --git a/web/app/components/app/app-publisher/index.tsx b/web/app/components/app/app-publisher/index.tsx index 801345798b..2dc45e1337 100644 --- a/web/app/components/app/app-publisher/index.tsx +++ b/web/app/components/app/app-publisher/index.tsx @@ -21,6 +21,7 @@ import { import { useKeyPress } from 'ahooks' import Divider from '../../base/divider' import Loading from '../../base/loading' +import Toast from '../../base/toast' import Tooltip from '../../base/tooltip' import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '../../workflow/utils' import AccessControl from '../app-access-control' @@ -41,6 +42,7 @@ import type { InputVar, Variable } from '@/app/components/workflow/types' import { appDefaultIconBackground } from '@/config' import { useGlobalPublicStore } from '@/context/global-public-context' import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' +import { useAsyncWindowOpen } from '@/hooks/use-async-window-open' import { AccessMode } from '@/models/access-control' import { useAppWhiteListSubjects, useGetUserCanAccessApp } from '@/service/access-control' import { fetchAppDetailDirect } from '@/service/apps' @@ -49,7 +51,6 @@ import { AppModeEnum } from '@/types/app' import type { PublishWorkflowParams } from '@/types/workflow' import { basePath } from '@/utils/var' import UpgradeBtn from '@/app/components/billing/upgrade-btn' -import { useAsyncWindowOpen } from '@/hooks/use-async-window-open' const ACCESS_MODE_MAP: Record = { [AccessMode.ORGANIZATION]: { @@ -153,6 +154,7 @@ const AppPublisher = ({ const { data: userCanAccessApp, isLoading: isGettingUserCanAccessApp, refetch } = useGetUserCanAccessApp({ appId: appDetail?.id, enabled: false }) const { data: appAccessSubjects, isLoading: isGettingAppWhiteListSubjects } = useAppWhiteListSubjects(appDetail?.id, open && systemFeatures.webapp_auth.enabled && appDetail?.access_mode === AccessMode.SPECIFIC_GROUPS_MEMBERS) + const openAsyncWindow = useAsyncWindowOpen() const noAccessPermission = useMemo(() => systemFeatures.webapp_auth.enabled && appDetail && appDetail.access_mode !== AccessMode.EXTERNAL_MEMBERS && !userCanAccessApp?.result, [systemFeatures, appDetail, userCanAccessApp]) const disabledFunctionButton = useMemo(() => (!publishedAt || missingStartNode || noAccessPermission), [publishedAt, missingStartNode, noAccessPermission]) @@ -216,23 +218,20 @@ const AppPublisher = ({ setPublished(false) }, [disabled, onToggle, open]) - const { openAsync } = useAsyncWindowOpen() - - const handleOpenInExplore = useCallback(() => { - if (!appDetail?.id) return - - openAsync( - async () => { - const { installed_apps }: { installed_apps?: { id: string }[] } = await fetchInstalledAppList(appDetail.id) || {} - if (installed_apps && installed_apps.length > 0) - return `${basePath}/explore/installed/${installed_apps[0].id}` - throw new Error('No app found in Explore') + const handleOpenInExplore = useCallback(async () => { + await openAsyncWindow(async () => { + if (!appDetail?.id) + throw new Error('App not found') + const { installed_apps }: any = await fetchInstalledAppList(appDetail?.id) || {} + if (installed_apps?.length > 0) + return `${basePath}/explore/installed/${installed_apps[0].id}` + throw new Error('No app found in Explore') + }, { + onError: (err) => { + Toast.notify({ type: 'error', message: `${err.message || err}` }) }, - { - errorMessage: 'Failed to open app in Explore', - }, - ) - }, [appDetail?.id, openAsync]) + }) + }, [appDetail?.id, openAsyncWindow]) const handleAccessControlUpdate = useCallback(async () => { if (!appDetail) diff --git a/web/app/components/apps/app-card.tsx b/web/app/components/apps/app-card.tsx index 407df23913..b8da0264e4 100644 --- a/web/app/components/apps/app-card.tsx +++ b/web/app/components/apps/app-card.tsx @@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next' import { RiBuildingLine, RiGlobalLine, RiLockLine, RiMoreFill, RiVerifiedBadgeLine } from '@remixicon/react' import cn from '@/utils/classnames' import { type App, AppModeEnum } from '@/types/app' -import { ToastContext } from '@/app/components/base/toast' +import Toast, { ToastContext } from '@/app/components/base/toast' import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/apps' import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal' import AppIcon from '@/app/components/base/app-icon' @@ -27,11 +27,11 @@ import { fetchWorkflowDraft } from '@/service/workflow' import { fetchInstalledAppList } from '@/service/explore' import { AppTypeIcon } from '@/app/components/app/type-selector' import Tooltip from '@/app/components/base/tooltip' +import { useAsyncWindowOpen } from '@/hooks/use-async-window-open' import { AccessMode } from '@/models/access-control' import { useGlobalPublicStore } from '@/context/global-public-context' import { formatTime } from '@/utils/time' import { useGetUserCanAccessApp } from '@/service/access-control' -import { useAsyncWindowOpen } from '@/hooks/use-async-window-open' import dynamic from 'next/dynamic' const EditAppModal = dynamic(() => import('@/app/components/explore/create-app-modal'), { @@ -65,6 +65,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { const { isCurrentWorkspaceEditor } = useAppContext() const { onPlanInfoChanged } = useProviderContext() const { push } = useRouter() + const openAsyncWindow = useAsyncWindowOpen() const [showEditModal, setShowEditModal] = useState(false) const [showDuplicateModal, setShowDuplicateModal] = useState(false) @@ -243,24 +244,25 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { e.preventDefault() setShowAccessControl(true) } - const { openAsync } = useAsyncWindowOpen() - - const onClickInstalledApp = (e: React.MouseEvent) => { + const onClickInstalledApp = async (e: React.MouseEvent) => { e.stopPropagation() props.onClick?.() e.preventDefault() - - openAsync( - async () => { - const { installed_apps }: { installed_apps?: { id: string }[] } = await fetchInstalledAppList(app.id) || {} - if (installed_apps && installed_apps.length > 0) + try { + await openAsyncWindow(async () => { + const { installed_apps }: any = await fetchInstalledAppList(app.id) || {} + if (installed_apps?.length > 0) return `${basePath}/explore/installed/${installed_apps[0].id}` throw new Error('No app found in Explore') - }, - { - errorMessage: 'Failed to open app in Explore', - }, - ) + }, { + onError: (err) => { + Toast.notify({ type: 'error', message: `${err.message || err}` }) + }, + }) + } + catch (e: any) { + Toast.notify({ type: 'error', message: `${e.message || e}` }) + } } return (
diff --git a/web/app/components/billing/billing-page/index.tsx b/web/app/components/billing/billing-page/index.tsx index adb676cde1..590219c2d5 100644 --- a/web/app/components/billing/billing-page/index.tsx +++ b/web/app/components/billing/billing-page/index.tsx @@ -9,33 +9,28 @@ import PlanComp from '../plan' import { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' import { useBillingUrl } from '@/service/use-billing' +import { useAsyncWindowOpen } from '@/hooks/use-async-window-open' const Billing: FC = () => { const { t } = useTranslation() const { isCurrentWorkspaceManager } = useAppContext() const { enableBilling } = useProviderContext() const { data: billingUrl, isFetching, refetch } = useBillingUrl(enableBilling && isCurrentWorkspaceManager) + const openAsyncWindow = useAsyncWindowOpen() const handleOpenBilling = async () => { - // Open synchronously to preserve user gesture for popup blockers - if (billingUrl) { - window.open(billingUrl, '_blank', 'noopener,noreferrer') - return - } - - const newWindow = window.open('', '_blank', 'noopener,noreferrer') - try { + await openAsyncWindow(async () => { const url = (await refetch()).data - if (url && newWindow) { - newWindow.location.href = url - return - } - } - catch (err) { - console.error('Failed to fetch billing url', err) - } - // Close the placeholder window if we failed to fetch the URL - newWindow?.close() + if (url) + return url + return null + }, { + immediateUrl: billingUrl, + features: 'noopener,noreferrer', + onError: (err) => { + console.error('Failed to fetch billing url', err) + }, + }) } return ( diff --git a/web/app/components/billing/pricing/plans/cloud-plan-item/index.tsx b/web/app/components/billing/pricing/plans/cloud-plan-item/index.tsx index 164ad9061a..52c2883b81 100644 --- a/web/app/components/billing/pricing/plans/cloud-plan-item/index.tsx +++ b/web/app/components/billing/pricing/plans/cloud-plan-item/index.tsx @@ -43,6 +43,7 @@ const CloudPlanItem: FC = ({ const isCurrentPaidPlan = isCurrent && !isFreePlan const isPlanDisabled = isCurrentPaidPlan ? false : planInfo.level <= ALL_PLANS[currentPlan].level const { isCurrentWorkspaceManager } = useAppContext() + const openAsyncWindow = useAsyncWindowOpen() const btnText = useMemo(() => { if (isCurrent) @@ -55,8 +56,6 @@ const CloudPlanItem: FC = ({ })[plan] }, [isCurrent, plan, t]) - const { openAsync } = useAsyncWindowOpen() - const handleGetPayUrl = async () => { if (loading) return @@ -75,13 +74,16 @@ const CloudPlanItem: FC = ({ setLoading(true) try { if (isCurrentPaidPlan) { - await openAsync( - () => fetchBillingUrl().then(res => res.url), - { - errorMessage: 'Failed to open billing page', - windowFeatures: 'noopener,noreferrer', + await openAsyncWindow(async () => { + const res = await fetchBillingUrl() + if (res.url) + return res.url + throw new Error('Failed to open billing page') + }, { + onError: (err) => { + Toast.notify({ type: 'error', message: err.message || String(err) }) }, - ) + }) return } diff --git a/web/hooks/use-async-window-open.spec.ts b/web/hooks/use-async-window-open.spec.ts new file mode 100644 index 0000000000..63ec9185da --- /dev/null +++ b/web/hooks/use-async-window-open.spec.ts @@ -0,0 +1,116 @@ +import { act, renderHook } from '@testing-library/react' +import { useAsyncWindowOpen } from './use-async-window-open' + +describe('useAsyncWindowOpen', () => { + const originalOpen = window.open + + beforeEach(() => { + jest.clearAllMocks() + }) + + afterAll(() => { + window.open = originalOpen + }) + + it('opens immediate url synchronously without calling async getter', async () => { + const openSpy = jest.fn() + window.open = openSpy + const getUrl = jest.fn() + const { result } = renderHook(() => useAsyncWindowOpen()) + + await act(async () => { + await result.current(getUrl, { + immediateUrl: 'https://example.com', + target: '_blank', + features: 'noopener,noreferrer', + }) + }) + + expect(openSpy).toHaveBeenCalledWith('https://example.com', '_blank', 'noopener,noreferrer') + expect(getUrl).not.toHaveBeenCalled() + }) + + it('sets opener to null and redirects when async url resolves', async () => { + const close = jest.fn() + const mockWindow: any = { + location: { href: '' }, + close, + opener: 'should-be-cleared', + } + const openSpy = jest.fn(() => mockWindow) + window.open = openSpy + const { result } = renderHook(() => useAsyncWindowOpen()) + + await act(async () => { + await result.current(async () => 'https://example.com/path') + }) + + expect(openSpy).toHaveBeenCalledWith('about:blank', '_blank', undefined) + expect(mockWindow.opener).toBeNull() + expect(mockWindow.location.href).toBe('https://example.com/path') + expect(close).not.toHaveBeenCalled() + }) + + it('closes placeholder and forwards error when async getter throws', async () => { + const close = jest.fn() + const mockWindow: any = { + location: { href: '' }, + close, + opener: null, + } + const openSpy = jest.fn(() => mockWindow) + window.open = openSpy + const onError = jest.fn() + const { result } = renderHook(() => useAsyncWindowOpen()) + + const error = new Error('fetch failed') + await act(async () => { + await result.current(async () => { + throw error + }, { onError }) + }) + + expect(close).toHaveBeenCalled() + expect(onError).toHaveBeenCalledWith(error) + expect(mockWindow.location.href).toBe('') + }) + + it('closes placeholder and reports when no url is returned', async () => { + const close = jest.fn() + const mockWindow: any = { + location: { href: '' }, + close, + opener: null, + } + const openSpy = jest.fn(() => mockWindow) + window.open = openSpy + const onError = jest.fn() + const { result } = renderHook(() => useAsyncWindowOpen()) + + await act(async () => { + await result.current(async () => null, { onError }) + }) + + expect(close).toHaveBeenCalled() + expect(onError).toHaveBeenCalled() + const errArg = onError.mock.calls[0][0] as Error + expect(errArg.message).toBe('No url resolved for new window') + }) + + it('reports failure when window.open returns null', async () => { + const openSpy = jest.fn(() => null) + window.open = openSpy + const getUrl = jest.fn() + const onError = jest.fn() + const { result } = renderHook(() => useAsyncWindowOpen()) + + await act(async () => { + await result.current(getUrl, { onError }) + }) + + expect(onError).toHaveBeenCalled() + const errArg = onError.mock.calls[0][0] as Error + expect(errArg.message).toBe('Failed to open new window') + expect(getUrl).not.toHaveBeenCalled() + }) +}) diff --git a/web/hooks/use-async-window-open.ts b/web/hooks/use-async-window-open.ts index 582ab28be4..e3d7910217 100644 --- a/web/hooks/use-async-window-open.ts +++ b/web/hooks/use-async-window-open.ts @@ -1,72 +1,49 @@ import { useCallback } from 'react' -import Toast from '@/app/components/base/toast' -export type AsyncWindowOpenOptions = { - successMessage?: string - errorMessage?: string - windowFeatures?: string - onError?: (error: any) => void - onSuccess?: (url: string) => void +type GetUrl = () => Promise + +type AsyncWindowOpenOptions = { + immediateUrl?: string | null + target?: string + features?: string + onError?: (error: Error) => void } -export const useAsyncWindowOpen = () => { - const openAsync = useCallback(async ( - fetchUrl: () => Promise, - options: AsyncWindowOpenOptions = {}, - ) => { - const { - successMessage, - errorMessage = 'Failed to open page', - windowFeatures = 'noopener,noreferrer', - onError, - onSuccess, - } = options +export const useAsyncWindowOpen = () => useCallback(async (getUrl: GetUrl, options?: AsyncWindowOpenOptions) => { + const { + immediateUrl, + target = '_blank', + features, + onError, + } = options ?? {} - const newWindow = window.open('', '_blank', windowFeatures) + if (immediateUrl) { + window.open(immediateUrl, target, features) + return + } - if (!newWindow) { - const error = new Error('Popup blocked by browser') - onError?.(error) - Toast.notify({ - type: 'error', - message: 'Popup blocked. Please allow popups for this site.', - }) + const newWindow = window.open('about:blank', target, features) + if (!newWindow) { + onError?.(new Error('Failed to open new window')) + return + } + + try { + newWindow.opener = null + } + catch { /* noop */ } + + try { + const url = await getUrl() + if (url) { + newWindow.location.href = url return } - - try { - const url = await fetchUrl() - - if (url) { - newWindow.location.href = url - onSuccess?.(url) - - if (successMessage) { - Toast.notify({ - type: 'success', - message: successMessage, - }) - } - } - else { - newWindow.close() - const error = new Error('Invalid URL received') - onError?.(error) - Toast.notify({ - type: 'error', - message: errorMessage, - }) - } - } - catch (error) { - newWindow.close() - onError?.(error) - Toast.notify({ - type: 'error', - message: errorMessage, - }) - } - }, []) - - return { openAsync } -} + newWindow.close() + onError?.(new Error('No url resolved for new window')) + } + catch (error) { + newWindow.close() + onError?.(error instanceof Error ? error : new Error(String(error))) + } +}, []) From 0c2a3541153b1eea5c6f09116c1fc9fc004b540f Mon Sep 17 00:00:00 2001 From: Coding On Star <447357187@qq.com> Date: Wed, 10 Dec 2025 17:25:54 +0800 Subject: [PATCH 04/28] Using SonarJS to analyze components' complexity (#29412) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: CodingOnStar Co-authored-by: 姜涵煦 Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- web/package.json | 1 + web/pnpm-lock.yaml | 1466 +++++++++++++++--------------- web/testing/analyze-component.js | 399 +++----- 3 files changed, 896 insertions(+), 970 deletions(-) diff --git a/web/package.json b/web/package.json index e7d732c7b6..aba92f4891 100644 --- a/web/package.json +++ b/web/package.json @@ -184,6 +184,7 @@ "@types/semver": "^7.7.1", "@types/sortablejs": "^1.15.8", "@types/uuid": "^10.0.0", + "@typescript-eslint/parser": "^8.48.0", "@typescript/native-preview": "^7.0.0-dev", "autoprefixer": "^10.4.21", "babel-loader": "^10.0.0", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index af7856329e..0cf7524573 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -60,10 +60,10 @@ importers: dependencies: '@amplitude/analytics-browser': specifier: ^2.31.3 - version: 2.31.3 + version: 2.31.4 '@amplitude/plugin-session-replay-browser': specifier: ^1.23.6 - version: 1.23.6(@amplitude/rrweb@2.0.0-alpha.33)(rollup@2.79.2) + version: 1.24.1(@amplitude/rrweb@2.0.0-alpha.33)(rollup@2.79.2) '@emoji-mart/data': specifier: ^1.2.1 version: 1.2.1 @@ -81,7 +81,7 @@ importers: version: 2.2.0(react@19.2.1) '@hookform/resolvers': specifier: ^3.10.0 - version: 3.10.0(react-hook-form@7.67.0(react@19.2.1)) + version: 3.10.0(react-hook-form@7.68.0(react@19.2.1)) '@lexical/code': specifier: ^0.38.2 version: 0.38.2 @@ -126,13 +126,13 @@ importers: version: 0.5.19(tailwindcss@3.4.18(tsx@4.21.0)(yaml@2.8.2)) '@tanstack/react-form': specifier: ^1.23.7 - version: 1.27.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 1.27.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@tanstack/react-query': specifier: ^5.90.5 - version: 5.90.11(react@19.2.1) + version: 5.90.12(react@19.2.1) '@tanstack/react-query-devtools': specifier: ^5.90.2 - version: 5.91.1(@tanstack/react-query@5.90.11(react@19.2.1))(react@19.2.1) + version: 5.91.1(@tanstack/react-query@5.90.12(react@19.2.1))(react@19.2.1) abcjs: specifier: ^6.5.2 version: 6.5.2 @@ -162,7 +162,7 @@ importers: version: 10.6.0 dompurify: specifier: ^3.3.0 - version: 3.3.0 + version: 3.3.1 echarts: specifier: ^5.6.0 version: 5.6.0 @@ -207,10 +207,10 @@ importers: version: 1.5.0 katex: specifier: ^0.16.25 - version: 0.16.25 + version: 0.16.27 ky: specifier: ^1.12.0 - version: 1.14.0 + version: 1.14.1 lamejs: specifier: ^1.2.1 version: 1.2.1 @@ -237,10 +237,10 @@ importers: version: 1.0.0 next: specifier: ~15.5.7 - version: 15.5.7(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2) + version: 15.5.7(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.95.0) next-pwa: specifier: ^5.6.0 - version: 5.6.0(@babel/core@7.28.5)(@types/babel__core@7.20.5)(esbuild@0.25.0)(next@15.5.7(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2))(uglify-js@3.19.3)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) + version: 5.6.0(@babel/core@7.28.5)(@types/babel__core@7.20.5)(esbuild@0.25.0)(next@15.5.7(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.95.0))(uglify-js@3.19.3)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -267,7 +267,7 @@ importers: version: 5.5.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) react-hook-form: specifier: ^7.65.0 - version: 7.67.0(react@19.2.1) + version: 7.68.0(react@19.2.1) react-hotkeys-hook: specifier: ^4.6.2 version: 4.6.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -364,7 +364,7 @@ importers: version: 7.28.5 '@chromatic-com/storybook': specifier: ^4.1.1 - version: 4.1.3(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3))) + version: 4.1.3(storybook@9.1.13(@testing-library/dom@10.4.1)) '@eslint-react/eslint-plugin': specifier: ^1.53.1 version: 1.53.1(eslint@9.39.1(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.9.3))(typescript@5.9.3) @@ -391,22 +391,22 @@ importers: version: 4.2.0 '@storybook/addon-docs': specifier: 9.1.13 - version: 9.1.13(@types/react@19.2.7)(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3))) + version: 9.1.13(@types/react@19.2.7)(storybook@9.1.13(@testing-library/dom@10.4.1)) '@storybook/addon-links': specifier: 9.1.13 - version: 9.1.13(react@19.2.1)(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3))) + version: 9.1.13(react@19.2.1)(storybook@9.1.13(@testing-library/dom@10.4.1)) '@storybook/addon-onboarding': specifier: 9.1.13 - version: 9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3))) + version: 9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1)) '@storybook/addon-themes': specifier: 9.1.13 - version: 9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3))) + version: 9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1)) '@storybook/nextjs': specifier: 9.1.13 - version: 9.1.13(esbuild@0.25.0)(next@15.5.7(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2)(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)))(type-fest@4.2.0)(typescript@5.9.3)(uglify-js@3.19.3)(webpack-hot-middleware@2.26.1)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) + version: 9.1.13(esbuild@0.25.0)(next@15.5.7(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.95.0))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.95.0)(storybook@9.1.13(@testing-library/dom@10.4.1))(type-fest@4.2.0)(typescript@5.9.3)(uglify-js@3.19.3)(webpack-hot-middleware@2.26.1)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) '@storybook/react': specifier: 9.1.13 - version: 9.1.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)))(typescript@5.9.3) + version: 9.1.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@9.1.13(@testing-library/dom@10.4.1))(typescript@5.9.3) '@testing-library/dom': specifier: ^10.4.1 version: 10.4.1 @@ -464,9 +464,12 @@ importers: '@types/uuid': specifier: ^10.0.0 version: 10.0.0 + '@typescript-eslint/parser': + specifier: ^8.48.0 + version: 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) '@typescript/native-preview': specifier: ^7.0.0-dev - version: 7.0.0-dev.20251204.1 + version: 7.0.0-dev.20251209.1 autoprefixer: specifier: ^10.4.21 version: 10.4.22(postcss@8.5.6) @@ -487,7 +490,7 @@ importers: version: 9.39.1(jiti@1.21.7) eslint-plugin-oxlint: specifier: ^1.23.0 - version: 1.31.0 + version: 1.32.0 eslint-plugin-react-hooks: specifier: ^5.2.0 version: 5.2.0(eslint@9.39.1(jiti@1.21.7)) @@ -499,7 +502,7 @@ importers: version: 3.0.5(eslint@9.39.1(jiti@1.21.7)) eslint-plugin-storybook: specifier: ^9.1.13 - version: 9.1.16(eslint@9.39.1(jiti@1.21.7))(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)))(typescript@5.9.3) + version: 9.1.16(eslint@9.39.1(jiti@1.21.7))(storybook@9.1.13(@testing-library/dom@10.4.1))(typescript@5.9.3) eslint-plugin-tailwindcss: specifier: ^3.18.2 version: 3.18.2(tailwindcss@3.4.18(tsx@4.21.0)(yaml@2.8.2)) @@ -514,7 +517,7 @@ importers: version: 29.7.0(@types/node@18.15.0)(ts-node@10.9.2(@types/node@18.15.0)(typescript@5.9.3)) knip: specifier: ^5.66.1 - version: 5.71.0(@types/node@18.15.0)(typescript@5.9.3) + version: 5.72.0(@types/node@18.15.0)(typescript@5.9.3) lint-staged: specifier: ^15.5.2 version: 15.5.2 @@ -529,19 +532,19 @@ importers: version: 14.0.10 oxlint: specifier: ^1.31.0 - version: 1.31.0 + version: 1.32.0 postcss: specifier: ^8.5.6 version: 8.5.6 react-scan: specifier: ^0.4.3 - version: 0.4.3(@types/react@19.2.7)(next@15.5.7(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(rollup@2.79.2) + version: 0.4.3(@types/react@19.2.7)(next@15.5.7(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.95.0))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(rollup@2.79.2) sass: specifier: ^1.93.2 - version: 1.94.2 + version: 1.95.0 storybook: specifier: 9.1.13 - version: 9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)) + version: 9.1.13(@testing-library/dom@10.4.1) tailwindcss: specifier: ^3.4.18 version: 3.4.18(tsx@4.21.0)(yaml@2.8.2) @@ -564,8 +567,8 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@amplitude/analytics-browser@2.31.3': - resolution: {integrity: sha512-jGViok5dVYi+4y/OUpH/0+urbba7KK6lmWLJx05TW68ME7lPrZSYO2B1NPzoe6Eym1Rzz6k3njGFR7dtTxcFSQ==} + '@amplitude/analytics-browser@2.31.4': + resolution: {integrity: sha512-9O8a0SK55tQOgJJ0z9eE+q/C2xWo6a65wN4iSglxYwm1vvGJKG6Z/QV4XKQ6X0syGscRuG1XoMc0mt3xdVPtDg==} '@amplitude/analytics-client-common@2.4.16': resolution: {integrity: sha512-qF7NAl6Qr6QXcWKnldGJfO0Kp1TYoy1xsmzEDnOYzOS96qngtvsZ8MuKya1lWdVACoofwQo82V0VhNZJKk/2YA==} @@ -594,8 +597,8 @@ packages: '@amplitude/plugin-page-view-tracking-browser@2.6.3': resolution: {integrity: sha512-lLU4W2r5jXtfn/14cZKM9c9CQDxT7PVVlgm0susHJ3Kfsua9jJQuMHs4Zlg6rwByAtZi5nF4nYE5z0GF09gx0A==} - '@amplitude/plugin-session-replay-browser@1.23.6': - resolution: {integrity: sha512-MPUVbN/tBTHvqKujqIlzd5mq5d3kpovC/XEVw80dgWUYwOwU7+39vKGc2NZV8iGi3kOtOzm2XTlcGOS2Gtjw3Q==} + '@amplitude/plugin-session-replay-browser@1.24.1': + resolution: {integrity: sha512-NHePIu2Yv9ba+fOt5N33b8FFQPzyKvjs1BnWBgBCM5RECos3w6n/+zUWTnTJ4at2ipO2lz111abKDteUwbuptg==} '@amplitude/plugin-web-vitals-browser@1.1.0': resolution: {integrity: sha512-TA0X4Np4Wt5hkQ4+Ouhg6nm2xjDd9l03OV9N8Kbe1cqpr/sxvRwSpd+kp2eREbp6D7tHFFkKJA2iNtxbE5Y0cA==} @@ -632,8 +635,8 @@ packages: '@amplitude/rrweb@2.0.0-alpha.33': resolution: {integrity: sha512-vMuk/3HzDWaUzBLFxKd7IpA8TEWjyPZBuLiLexMd/mOfTt/+JkVLsfXiJOyltJfR98LpmMTp1q51dtq357Dnfg==} - '@amplitude/session-replay-browser@1.29.8': - resolution: {integrity: sha512-f/j1+xUxqK7ewz0OM04Q0m2N4Q+miCOfANe9jb9NAGfZdBu8IfNYswfjPiHdv0+ffXl5UovuyLhl1nV/znIZqA==} + '@amplitude/session-replay-browser@1.30.0': + resolution: {integrity: sha512-mLNJ5UEDuY91zRmqPiJcORMmaYkfrKjLzu52DsD/EaB+rKAxSuZbUlXlFjeaaCrNLWWV2ywn5y3Tl2xX0cQNzQ==} '@amplitude/targeting@0.2.0': resolution: {integrity: sha512-/50ywTrC4hfcfJVBbh5DFbqMPPfaIOivZeb5Gb+OGM03QrA+lsUqdvtnKLNuWtceD4H6QQ2KFzPJ5aAJLyzVDA==} @@ -1429,150 +1432,306 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.25.0': resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.25.0': resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.25.0': resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.25.0': resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.25.0': resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.25.0': resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.0': resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.25.0': resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.25.0': resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.25.0': resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.25.0': resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.25.0': resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.25.0': resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.25.0': resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.25.0': resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.25.0': resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.0': resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.0': resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.0': resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.0': resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.25.0': resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.25.0': resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.25.0': resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.25.0': resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-plugin-eslint-comments@4.5.0': resolution: {integrity: sha512-MAhuTKlr4y/CE3WYX26raZjy+I/kS2PLKSzvfmDCGrBLTFHOYwqROZdr4XwPgXwX3K9rjzMr4pSmUWGnzsUyMg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1997,41 +2156,6 @@ packages: cpu: [x64] os: [win32] - '@inquirer/ansi@1.0.2': - resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} - engines: {node: '>=18'} - - '@inquirer/confirm@5.1.21': - resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/core@10.3.2': - resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/figures@1.0.15': - resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} - engines: {node: '>=18'} - - '@inquirer/type@3.0.10': - resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - '@isaacs/balanced-match@4.0.1': resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} engines: {node: 20 || >=22} @@ -2244,10 +2368,6 @@ packages: resolution: {integrity: sha512-2+BzZbjRO7Ct61k8fMNHEtoKjeWI9pIlHFTqBwZ5icHpqszIgEZbjb1MW5Z0+bITTCTl3gk4PDBxs9tA/csXvA==} engines: {node: '>=18'} - '@mswjs/interceptors@0.40.0': - resolution: {integrity: sha512-EFd6cVbHsgLa6wa4RljGj6Wk75qoHxUSyc5asLyyPSyuhIcdS2Q3Phw6ImS1q+CkALthJRShiYfKANcQMuMqsQ==} - engines: {node: '>=18'} - '@napi-rs/wasm-runtime@1.1.0': resolution: {integrity: sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==} @@ -2435,143 +2555,143 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - '@oxc-resolver/binding-android-arm-eabi@11.14.2': - resolution: {integrity: sha512-bTrdE4Z1JcGwPxBOaGbxRbpOHL8/xPVJTTq3/bAZO2euWX0X7uZ+XxsbC+5jUDMhLenqdFokgE1akHEU4xsh6A==} + '@oxc-resolver/binding-android-arm-eabi@11.15.0': + resolution: {integrity: sha512-Q+lWuFfq7whNelNJIP1dhXaVz4zO9Tu77GcQHyxDWh3MaCoO2Bisphgzmsh4ZoUe2zIchQh6OvQL99GlWHg9Tw==} cpu: [arm] os: [android] - '@oxc-resolver/binding-android-arm64@11.14.2': - resolution: {integrity: sha512-bL7/f6YGKUvt/wzpX7ZrHCf1QerotbSG+IIb278AklXuwr6yQdfQHt7KQ8hAWqSYpB2TAbPbAa9HE4wzVyxL9Q==} + '@oxc-resolver/binding-android-arm64@11.15.0': + resolution: {integrity: sha512-vbdBttesHR0W1oJaxgWVTboyMUuu+VnPsHXJ6jrXf4czELzB6GIg5DrmlyhAmFBhjwov+yJH/DfTnHS+2sDgOw==} cpu: [arm64] os: [android] - '@oxc-resolver/binding-darwin-arm64@11.14.2': - resolution: {integrity: sha512-0zhMhqHz/kC6/UzMC4D9mVBz3/M9UTorbaULfHjAW5b8SUC08H01lZ5fR3OzfDbJI0ByLfiQZmbovuR/pJ8Wzg==} + '@oxc-resolver/binding-darwin-arm64@11.15.0': + resolution: {integrity: sha512-R67lsOe1UzNjqVBCwCZX1rlItTsj/cVtBw4Uy19CvTicqEWvwaTn8t34zLD75LQwDDPCY3C8n7NbD+LIdw+ZoA==} cpu: [arm64] os: [darwin] - '@oxc-resolver/binding-darwin-x64@11.14.2': - resolution: {integrity: sha512-kRJBTCQnrGy1mjO+658yMrlGYWEKi6j4JvKt92PRCoeDX0vW4jvzgoJXzZXNxZL1pCY6jIdwsn9u53v4jwpR6g==} + '@oxc-resolver/binding-darwin-x64@11.15.0': + resolution: {integrity: sha512-77mya5F8WV0EtCxI0MlVZcqkYlaQpfNwl/tZlfg4jRsoLpFbaTeWv75hFm6TE84WULVlJtSgvf7DhoWBxp9+ZQ==} cpu: [x64] os: [darwin] - '@oxc-resolver/binding-freebsd-x64@11.14.2': - resolution: {integrity: sha512-lpKiya7qPq5EAV5E16SJbxfhNYRCBZATGngn9mZxR2fMLDVbHISDIP2Br8eWA8M1FBJFsOGgBzxDo+42ySSNZQ==} + '@oxc-resolver/binding-freebsd-x64@11.15.0': + resolution: {integrity: sha512-X1Sz7m5PC+6D3KWIDXMUtux+0Imj6HfHGdBStSvgdI60OravzI1t83eyn6eN0LPTrynuPrUgjk7tOnOsBzSWHw==} cpu: [x64] os: [freebsd] - '@oxc-resolver/binding-linux-arm-gnueabihf@11.14.2': - resolution: {integrity: sha512-zRIf49IGs4cE9rwpVM3NxlHWquZpwQLebtc9dY9S+4+B+PSLIP95BrzdRfkspwzWC5DKZsOWpvGQjxQiLoUwGA==} + '@oxc-resolver/binding-linux-arm-gnueabihf@11.15.0': + resolution: {integrity: sha512-L1x/wCaIRre+18I4cH/lTqSAymlV0k4HqfSYNNuI9oeL28Ks86lI6O5VfYL6sxxWYgjuWB98gNGo7tq7d4GarQ==} cpu: [arm] os: [linux] - '@oxc-resolver/binding-linux-arm-musleabihf@11.14.2': - resolution: {integrity: sha512-sF1fBrcfwoRkv1pR3Kp6D5MuBeHRPxYuzk9rhaun/50vq5nAMOaomkEm4hBbTSubfU86CoBIEbLUQ+1f7NvUVA==} + '@oxc-resolver/binding-linux-arm-musleabihf@11.15.0': + resolution: {integrity: sha512-abGXd/zMGa0tH8nKlAXdOnRy4G7jZmkU0J85kMKWns161bxIgGn/j7zxqh3DKEW98wAzzU9GofZMJ0P5YCVPVw==} cpu: [arm] os: [linux] - '@oxc-resolver/binding-linux-arm64-gnu@11.14.2': - resolution: {integrity: sha512-O8iTBqz6oxf1k93Rn6WMGGQYo2jV1K81hq4N/Nke3dHE25EIEg2RKQqMz1dFrvVb2RkvD7QaUTEevbx0Lq+4wQ==} + '@oxc-resolver/binding-linux-arm64-gnu@11.15.0': + resolution: {integrity: sha512-SVjjjtMW66Mza76PBGJLqB0KKyFTBnxmtDXLJPbL6ZPGSctcXVmujz7/WAc0rb9m2oV0cHQTtVjnq6orQnI/jg==} cpu: [arm64] os: [linux] - '@oxc-resolver/binding-linux-arm64-musl@11.14.2': - resolution: {integrity: sha512-HOfzpS6eUxvdch9UlXCMx2kNJWMNBjUpVJhseqAKDB1dlrfCHgexeLyBX977GLXkq2BtNXKsY3KCryy1QhRSRw==} + '@oxc-resolver/binding-linux-arm64-musl@11.15.0': + resolution: {integrity: sha512-JDv2/AycPF2qgzEiDeMJCcSzKNDm3KxNg0KKWipoKEMDFqfM7LxNwwSVyAOGmrYlE4l3dg290hOMsr9xG7jv9g==} cpu: [arm64] os: [linux] - '@oxc-resolver/binding-linux-ppc64-gnu@11.14.2': - resolution: {integrity: sha512-0uLG6F2zljUseQAUmlpx/9IdKpiLsSirpmrr8/aGVfiEurIJzC/1lo2HQskkM7e0VVOkXg37AjHUDLE23Fi8SA==} + '@oxc-resolver/binding-linux-ppc64-gnu@11.15.0': + resolution: {integrity: sha512-zbu9FhvBLW4KJxo7ElFvZWbSt4vP685Qc/Gyk/Ns3g2gR9qh2qWXouH8PWySy+Ko/qJ42+HJCLg+ZNcxikERfg==} cpu: [ppc64] os: [linux] - '@oxc-resolver/binding-linux-riscv64-gnu@11.14.2': - resolution: {integrity: sha512-Pdh0BH/E0YIK7Qg95IsAfQyU9rAoDoFh50R19zCTNfjSnwsoDMGHjmUc82udSfPo2YMnuxA+/+aglxmLQVSu2Q==} + '@oxc-resolver/binding-linux-riscv64-gnu@11.15.0': + resolution: {integrity: sha512-Kfleehe6B09C2qCnyIU01xLFqFXCHI4ylzkicfX/89j+gNHh9xyNdpEvit88Kq6i5tTGdavVnM6DQfOE2qNtlg==} cpu: [riscv64] os: [linux] - '@oxc-resolver/binding-linux-riscv64-musl@11.14.2': - resolution: {integrity: sha512-3DLQhJ2r53rCH5cudYFqD7nh+Z6ABvld3GjbiqHhT43GMIPw3JcHekC2QunLRNjRr1G544fo1HtjTJz9rCBpyg==} + '@oxc-resolver/binding-linux-riscv64-musl@11.15.0': + resolution: {integrity: sha512-J7LPiEt27Tpm8P+qURDwNc8q45+n+mWgyys4/V6r5A8v5gDentHRGUx3iVk5NxdKhgoGulrzQocPTZVosq25Eg==} cpu: [riscv64] os: [linux] - '@oxc-resolver/binding-linux-s390x-gnu@11.14.2': - resolution: {integrity: sha512-G5BnAOQ5f+RUG1cvlJ4BvV+P7iKLYBv67snqgcfwD5b2N4UwJj32bt4H5JfolocWy4x3qUjEDWTIjHdE+2uZ9w==} + '@oxc-resolver/binding-linux-s390x-gnu@11.15.0': + resolution: {integrity: sha512-+8/d2tAScPjVJNyqa7GPGnqleTB/XW9dZJQ2D/oIM3wpH3TG+DaFEXBbk4QFJ9K9AUGBhvQvWU2mQyhK/yYn3Q==} cpu: [s390x] os: [linux] - '@oxc-resolver/binding-linux-x64-gnu@11.14.2': - resolution: {integrity: sha512-VirQAX2PqKrhWtQGsSDEKlPhbgh3ggjT1sWuxLk4iLFwtyA2tLEPXJNAsG0kfAS2+VSA8OyNq16wRpQlMPZ4yA==} + '@oxc-resolver/binding-linux-x64-gnu@11.15.0': + resolution: {integrity: sha512-xtvSzH7Nr5MCZI2FKImmOdTl9kzuQ51RPyLh451tvD2qnkg3BaqI9Ox78bTk57YJhlXPuxWSOL5aZhKAc9J6qg==} cpu: [x64] os: [linux] - '@oxc-resolver/binding-linux-x64-musl@11.14.2': - resolution: {integrity: sha512-q4ORcwMkpzu4EhZyka/s2TuH2QklEHAr/mIQBXzu5BACeBJZIFkICp8qrq4XVnkEZ+XhSFTvBECqfMTT/4LSkA==} + '@oxc-resolver/binding-linux-x64-musl@11.15.0': + resolution: {integrity: sha512-14YL1zuXj06+/tqsuUZuzL0T425WA/I4nSVN1kBXeC5WHxem6lQ+2HGvG+crjeJEqHgZUT62YIgj88W+8E7eyg==} cpu: [x64] os: [linux] - '@oxc-resolver/binding-openharmony-arm64@11.14.2': - resolution: {integrity: sha512-ZsMIpDCxSFpUM/TwOovX5vZUkV0IukPFnrKTGaeJRuTKXMcJxMiQGCYTwd6y684Y3j55QZqIMkVM9NdCGUX6Kw==} + '@oxc-resolver/binding-openharmony-arm64@11.15.0': + resolution: {integrity: sha512-/7Qli+1Wk93coxnrQaU8ySlICYN8HsgyIrzqjgIkQEpI//9eUeaeIHZptNl2fMvBGeXa7k2QgLbRNaBRgpnvMw==} cpu: [arm64] os: [openharmony] - '@oxc-resolver/binding-wasm32-wasi@11.14.2': - resolution: {integrity: sha512-Lvq5ZZNvSjT3Jq/buPFMtp55eNyGlEWsq30tN+yLOfODSo6T6yAJNs6+wXtqu9PiMj4xpVtgXypHtbQ1f+t7kw==} + '@oxc-resolver/binding-wasm32-wasi@11.15.0': + resolution: {integrity: sha512-q5rn2eIMQLuc/AVGR2rQKb2EVlgreATGG8xXg8f4XbbYCVgpxaq+dgMbiPStyNywW1MH8VU2T09UEm30UtOQvg==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@oxc-resolver/binding-win32-arm64-msvc@11.14.2': - resolution: {integrity: sha512-7w7WHSLSSmkkYHH52QF7TrO0Z8eaIjRUrre5M56hSWRAZupCRzADZxBVMpDnHobZ8MAa2kvvDEfDbERuOK/avQ==} + '@oxc-resolver/binding-win32-arm64-msvc@11.15.0': + resolution: {integrity: sha512-yCAh2RWjU/8wWTxQDgGPgzV9QBv0/Ojb5ej1c/58iOjyTuy/J1ZQtYi2SpULjKmwIxLJdTiCHpMilauWimE31w==} cpu: [arm64] os: [win32] - '@oxc-resolver/binding-win32-ia32-msvc@11.14.2': - resolution: {integrity: sha512-hIrdlWa6tzqyfuWrxUetURBWHttBS+NMbBrGhCupc54NCXFy2ArB+0JOOaLYiI2ShKL5a3uqB7EWxmjzOuDdPQ==} + '@oxc-resolver/binding-win32-ia32-msvc@11.15.0': + resolution: {integrity: sha512-lmXKb6lvA6M6QIbtYfgjd+AryJqExZVSY2bfECC18OPu7Lv1mHFF171Mai5l9hG3r4IhHPPIwT10EHoilSCYeA==} cpu: [ia32] os: [win32] - '@oxc-resolver/binding-win32-x64-msvc@11.14.2': - resolution: {integrity: sha512-dP9aV6AZRRpg5mlg0eMuTROtttpQwj3AiegNJ/NNmMSjs+0+aLNcgkWRPhskK3vjTsthH4/+kKLpnQhSxdJkNg==} + '@oxc-resolver/binding-win32-x64-msvc@11.15.0': + resolution: {integrity: sha512-HZsfne0s/tGOcJK9ZdTGxsNU2P/dH0Shf0jqrPvsC6wX0Wk+6AyhSpHFLQCnLOuFQiHHU0ePfM8iYsoJb5hHpQ==} cpu: [x64] os: [win32] - '@oxlint/darwin-arm64@1.31.0': - resolution: {integrity: sha512-HqoYNH5WFZRdqGUROTFGOdBcA9y/YdHNoR/ujlyVO53it+q96dujbgKEvlff/WEuo4LbDKBrKLWKTKvOd/VYdg==} + '@oxlint/darwin-arm64@1.32.0': + resolution: {integrity: sha512-yrqPmZYu5Qb+49h0P5EXVIq8VxYkDDM6ZQrWzlh16+UGFcD8HOXs4oF3g9RyfaoAbShLCXooSQsM/Ifwx8E/eQ==} cpu: [arm64] os: [darwin] - '@oxlint/darwin-x64@1.31.0': - resolution: {integrity: sha512-gNq+JQXBCkYKQhmJEgSNjuPqmdL8yBEX3v0sueLH3g5ym4OIrNO7ml1M7xzCs0zhINQCR9MsjMJMyBNaF1ed+g==} + '@oxlint/darwin-x64@1.32.0': + resolution: {integrity: sha512-pQRZrJG/2nAKc3IuocFbaFFbTDlQsjz2WfivRsMn0hw65EEsSuM84WMFMiAfLpTGyTICeUtHZLHlrM5lzVr36A==} cpu: [x64] os: [darwin] - '@oxlint/linux-arm64-gnu@1.31.0': - resolution: {integrity: sha512-cRmttpr3yHPwbrvtPNlv+0Zw2Oeh0cU902iMI4fFW9ylbW/vUAcz6DvzGMCYZbII8VDiwQ453SV5AA8xBgMbmw==} + '@oxlint/linux-arm64-gnu@1.32.0': + resolution: {integrity: sha512-tyomSmU2DzwcTmbaWFmStHgVfRmJDDvqcIvcw4fRB1YlL2Qg/XaM4NJ0m2bdTap38gxD5FSxSgCo0DkQ8GTolg==} cpu: [arm64] os: [linux] - '@oxlint/linux-arm64-musl@1.31.0': - resolution: {integrity: sha512-0p7vn0hdMdNPIUzemw8f1zZ2rRZ/963EkK3o4P0KUXOPgleo+J9ZIPH7gcHSHtyrNaBifN03wET1rH4SuWQYnA==} + '@oxlint/linux-arm64-musl@1.32.0': + resolution: {integrity: sha512-0W46dRMaf71OGE4+Rd+GHfS1uF/UODl5Mef6871pMhN7opPGfTI2fKJxh9VzRhXeSYXW/Z1EuCq9yCfmIJq+5Q==} cpu: [arm64] os: [linux] - '@oxlint/linux-x64-gnu@1.31.0': - resolution: {integrity: sha512-vNIbpSwQ4dwN0CUmojG7Y91O3CXOf0Kno7DSTshk/JJR4+u8HNVuYVjX2qBRk0OMc4wscJbEd7wJCl0VJOoCOw==} + '@oxlint/linux-x64-gnu@1.32.0': + resolution: {integrity: sha512-5+6myVCBOMvM62rDB9T3CARXUvIwhGqte6E+HoKRwYaqsxGUZ4bh3pItSgSFwHjLGPrvADS11qJUkk39eQQBzQ==} cpu: [x64] os: [linux] - '@oxlint/linux-x64-musl@1.31.0': - resolution: {integrity: sha512-4avnH09FJRTOT2cULdDPG0s14C+Ku4cnbNye6XO7rsiX6Bprz+aQblLA+1WLOr7UfC/0zF+jnZ9K5VyBBJy9Kw==} + '@oxlint/linux-x64-musl@1.32.0': + resolution: {integrity: sha512-qwQlwYYgVIC6ScjpUwiKKNyVdUlJckrfwPVpIjC9mvglIQeIjKuuyaDxUZWIOc/rEzeCV/tW6tcbehLkfEzqsw==} cpu: [x64] os: [linux] - '@oxlint/win32-arm64@1.31.0': - resolution: {integrity: sha512-mQaD5H93OUpxiGjC518t5wLQikf0Ur5mQEKO2VoTlkp01gqmrQ+hyCLOzABlsAIAeDJD58S9JwNOw4KFFnrqdw==} + '@oxlint/win32-arm64@1.32.0': + resolution: {integrity: sha512-7qYZF9CiXGtdv8Z/fBkgB5idD2Zokht67I5DKWH0fZS/2R232sDqW2JpWVkXltk0+9yFvmvJ0ouJgQRl9M3S2g==} cpu: [arm64] os: [win32] - '@oxlint/win32-x64@1.31.0': - resolution: {integrity: sha512-AS/h58HfloccRlVs7P3zbyZfxNS62JuE8/3fYGjkiRlR1ZoDxdqmz5QgLEn+YxxFUTMmclGAPMFHg9z2Pk315A==} + '@oxlint/win32-x64@1.32.0': + resolution: {integrity: sha512-XW1xqCj34MEGJlHteqasTZ/LmBrwYIgluhNW0aP+XWkn90+stKAq3W/40dvJKbMK9F7o09LPCuMVtUW7FIUuiA==} cpu: [x64] os: [win32] @@ -2667,6 +2787,11 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@playwright/test@1.57.0': + resolution: {integrity: sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==} + engines: {node: '>=18'} + hasBin: true + '@pmmmwh/react-refresh-webpack-plugin@0.5.17': resolution: {integrity: sha512-tXDyE1/jzFsHXjhRZQ3hMl0IVhYe5qula43LDWIhVfjp9G/nT5OQY5AORVOrkEGAUltBJOfOWeETbmhm6kHhuQ==} engines: {node: '>= 10.13'} @@ -3205,21 +3330,21 @@ packages: resolution: {integrity: sha512-RL1f5ZlfZMpghrCIdzl6mLOFLTuhqmPNblZgBaeKfdtk5rfbjykurv+VfYydOFXj0vxVIoA2d/zT7xfD7Ph8fw==} engines: {node: '>=18'} - '@tanstack/form-core@1.27.0': - resolution: {integrity: sha512-QFEhg9/VcrwtpbcN7Qpl8JVVfEm2UJ+dzfDFGGMYub2J9jsgrp2HmaY7LSLlnkpTJlCIDxQiWDkiOFYQtK6yzw==} + '@tanstack/form-core@1.27.1': + resolution: {integrity: sha512-hPM+0tUnZ2C2zb2TE1lar1JJ0S0cbnQHlUwFcCnVBpMV3rjtUzkoM766gUpWrlmTGCzNad0GbJ0aTxVsjT6J8g==} '@tanstack/pacer@0.15.4': resolution: {integrity: sha512-vGY+CWsFZeac3dELgB6UZ4c7OacwsLb8hvL2gLS6hTgy8Fl0Bm/aLokHaeDIP+q9F9HUZTnp360z9uv78eg8pg==} engines: {node: '>=18'} - '@tanstack/query-core@5.90.11': - resolution: {integrity: sha512-f9z/nXhCgWDF4lHqgIE30jxLe4sYv15QodfdPDKYAk7nAEjNcndy4dHz3ezhdUaR23BpWa4I2EH4/DZ0//Uf8A==} + '@tanstack/query-core@5.90.12': + resolution: {integrity: sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg==} '@tanstack/query-devtools@5.91.1': resolution: {integrity: sha512-l8bxjk6BMsCaVQH6NzQEE/bEgFy1hAs5qbgXl0xhzezlaQbPk6Mgz9BqEg2vTLPOHD8N4k+w/gdgCbEzecGyNg==} - '@tanstack/react-form@1.27.0': - resolution: {integrity: sha512-7MBOtvjlUwkGpvA9TIOs3YdLoyfJWZYtxuAQIdkLDZ9HLrRaRbxWQIZ2H6sRVA35sPvx6uiQMunGHOPKip5AZA==} + '@tanstack/react-form@1.27.1': + resolution: {integrity: sha512-HKP0Ew2ae9AL5vU1PkJ+oAC2p+xBtA905u0fiNLzlfn1vLkBxenfg5L6TOA+rZITHpQsSo10tqwc5Yw6qn8Mpg==} peerDependencies: '@tanstack/react-start': '*' react: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -3233,8 +3358,8 @@ packages: '@tanstack/react-query': ^5.90.10 react: ^18 || ^19 - '@tanstack/react-query@5.90.11': - resolution: {integrity: sha512-3uyzz01D1fkTLXuxF3JfoJoHQMU2fxsfJwE+6N5hHy0dVNoZOvwKP8Z2k7k1KDeD54N20apcJnG75TBAStIrBA==} + '@tanstack/react-query@5.90.12': + resolution: {integrity: sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg==} peerDependencies: react: ^18 || ^19 @@ -3244,8 +3369,8 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/react-virtual@3.13.12': - resolution: {integrity: sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==} + '@tanstack/react-virtual@3.13.13': + resolution: {integrity: sha512-4o6oPMDvQv+9gMi8rE6gWmsOjtUZUYIJHv7EB+GblyYdi8U6OqLl8rhHWIUZSL1dUU2dPwTdTgybCKf9EjIrQg==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -3256,8 +3381,8 @@ packages: '@tanstack/store@0.8.0': resolution: {integrity: sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ==} - '@tanstack/virtual-core@3.13.12': - resolution: {integrity: sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==} + '@tanstack/virtual-core@3.13.13': + resolution: {integrity: sha512-uQFoSdKKf5S8k51W5t7b2qpfkyIbdHMzAn+AMQvHPxKUPeo1SsGaA4JRISQT87jm28b7z8OEqPcg1IOZagQHcA==} '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} @@ -3517,8 +3642,8 @@ packages: '@types/node@18.15.0': resolution: {integrity: sha512-z6nr0TTEOBGkzLGmbypWOGnpSpSIBorEhC4L+4HeQ2iezKCi4f77kyslRwvHeNitymGQ+oFyIWGP96l/DPSV9w==} - '@types/node@20.19.25': - resolution: {integrity: sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==} + '@types/node@20.19.26': + resolution: {integrity: sha512-0l6cjgF0XnihUpndDhk+nyD3exio3iKaYROSgvh/qSevPXax3L8p5DBRFjbvalnwatGgHEQn2R88y2fA3g4irg==} '@types/papaparse@5.5.1': resolution: {integrity: sha512-esEO+VISsLIyE+JZBmb89NzsYYbpwV8lmv2rPo6oX5y9KhBaIP7hhHgjuTut54qjdKVMufTEcrh5fUl9+58huw==} @@ -3569,9 +3694,6 @@ packages: '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - '@types/statuses@2.0.6': - resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} - '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -3596,109 +3718,109 @@ packages: '@types/zen-observable@0.8.3': resolution: {integrity: sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==} - '@typescript-eslint/eslint-plugin@8.48.1': - resolution: {integrity: sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==} + '@typescript-eslint/eslint-plugin@8.49.0': + resolution: {integrity: sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.48.1 + '@typescript-eslint/parser': ^8.49.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.48.1': - resolution: {integrity: sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==} + '@typescript-eslint/parser@8.49.0': + resolution: {integrity: sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.48.1': - resolution: {integrity: sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==} + '@typescript-eslint/project-service@8.49.0': + resolution: {integrity: sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.48.1': - resolution: {integrity: sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==} + '@typescript-eslint/scope-manager@8.49.0': + resolution: {integrity: sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.48.1': - resolution: {integrity: sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==} + '@typescript-eslint/tsconfig-utils@8.49.0': + resolution: {integrity: sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.48.1': - resolution: {integrity: sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==} + '@typescript-eslint/type-utils@8.49.0': + resolution: {integrity: sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.48.1': - resolution: {integrity: sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==} + '@typescript-eslint/types@8.49.0': + resolution: {integrity: sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.48.1': - resolution: {integrity: sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==} + '@typescript-eslint/typescript-estree@8.49.0': + resolution: {integrity: sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.48.1': - resolution: {integrity: sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==} + '@typescript-eslint/utils@8.49.0': + resolution: {integrity: sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.48.1': - resolution: {integrity: sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==} + '@typescript-eslint/visitor-keys@8.49.0': + resolution: {integrity: sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20251204.1': - resolution: {integrity: sha512-CgIzuO/LFRufdVjJmll6x7jnejYqqLo4kJwrsUxQipJ/dcGeP0q2XMcxNBzT7F9L4Sd5dphRPOZFXES4kS0lig==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20251209.1': + resolution: {integrity: sha512-F1cnYi+ZeinYQnaTQKKIsbuoq8vip5iepBkSZXlB8PjbG62LW1edUdktd/nVEc+Q+SEysSQ3jRdk9eU766s5iw==} cpu: [arm64] os: [darwin] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20251204.1': - resolution: {integrity: sha512-X76oQeDMQHJiukkPPbk7STrfu97pfPe5ixwiN6nXzSGXLE+tzrXRecNkYhz4XWeAW2ASNmGwDJJ2RAU5l8MbgQ==} + '@typescript/native-preview-darwin-x64@7.0.0-dev.20251209.1': + resolution: {integrity: sha512-Ta6XKdAxEMBzd1xS4eQKXmlUkml+kMf23A9qFoegOxmyCdHJPak2gLH9ON5/C6js0ibZm1kdqwbcA0/INrcThg==} cpu: [x64] os: [darwin] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20251204.1': - resolution: {integrity: sha512-+1as+h6ZNpc9TqlHwvDkBP7jg0FoCMUf6Rrc9/Mkllau6etznfVsWMADWT4t76gkGZKUIXOZqsl2Ya3uaBrCBQ==} + '@typescript/native-preview-linux-arm64@7.0.0-dev.20251209.1': + resolution: {integrity: sha512-kdiPMvs1hwi76hgvZjz4XQVNYTV+MAbJKnHXz6eL6aVXoTYzNtan5vWywKOHv9rV4jBMyVlZqtKbeG/XVV9WdQ==} cpu: [arm64] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20251204.1': - resolution: {integrity: sha512-3zl/Jj5rzkK9Oo5KVSIW+6bzRligoI+ZnA1xLpg0BBH2sk27a8Vasj7ZaGPlFvlSegvcaJdIjSt7Z8nBtiF9Ww==} + '@typescript/native-preview-linux-arm@7.0.0-dev.20251209.1': + resolution: {integrity: sha512-4e7WSBLLdmfJUGzm9Id4WA2fDZ2sY3Q6iudyZPNSb5AFsCmqQksM/JGAlNROHpi/tIqo95e3ckbjmrZTmH60EA==} cpu: [arm] os: [linux] - '@typescript/native-preview-linux-x64@7.0.0-dev.20251204.1': - resolution: {integrity: sha512-YD//l6yv7iPNlKn9OZDzBxrI+QGLN6d4RV3dSucsyq/YNZUulcywGztbZiaQxdUzKPwj70G+LVb9WCgf5ITOIQ==} + '@typescript/native-preview-linux-x64@7.0.0-dev.20251209.1': + resolution: {integrity: sha512-dH/Z50Xb52N4Csd0BXptmjuMN+87AhUAjM9Y5rNU8VwcUJJDFpKM6aKUhd4Q+XEVJWPFPlKDLx3pVhnO31CBhQ==} cpu: [x64] os: [linux] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20251204.1': - resolution: {integrity: sha512-eDXYR5qfPFA8EfQ0d9SbWGLn02VbAaeTM9jQ5VeLlPLcBP81nGRaGQ9Quta5zeEHev1S9iCdyRj5BqCRtl0ohw==} + '@typescript/native-preview-win32-arm64@7.0.0-dev.20251209.1': + resolution: {integrity: sha512-vW7IGRNIUhhQ0vzFY3sRNxvYavNGum2OWgW1Bwc05yhg9AexBlRjdhsUSTLQ2dUeaDm2nx4i38LhXIVgLzMNeA==} cpu: [arm64] os: [win32] - '@typescript/native-preview-win32-x64@7.0.0-dev.20251204.1': - resolution: {integrity: sha512-CRWI2OPdqXbzOU52R2abWMb3Ie2Wp6VPrCFzR3pzP53JabTAe8+XoBWlont9bw/NsqbPKp2aQbdfbLQX5RI44g==} + '@typescript/native-preview-win32-x64@7.0.0-dev.20251209.1': + resolution: {integrity: sha512-jKT6npBrhRX/84LWSy9PbOWx2USTZhq9SOkvH2mcnU/+uqyNxZIMMVnW5exIyzcnWSPly3jK2qpfiHNjdrDaAA==} cpu: [x64] os: [win32] - '@typescript/native-preview@7.0.0-dev.20251204.1': - resolution: {integrity: sha512-nyMp0ybgJVZFtDOWmcKDqaRqtj8dOg65+fDxbjIrnZuMWIqlOUGH+imFwofqlW+KndAA7KtAio2YSZMMZB25WA==} + '@typescript/native-preview@7.0.0-dev.20251209.1': + resolution: {integrity: sha512-xnx3A1S1TTx+mx8FfP1UwkNTwPBmhGCbOh4PDNRUV5gDZkVuDDN3y1F7NPGSMg6MXE1KKPSLNM+PQMN33ZAL2Q==} hasBin: true '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - '@vitest/eslint-plugin@1.5.1': - resolution: {integrity: sha512-t49CNERe/YadnLn90NTTKJLKzs99xBkXElcoUTLodG6j1G0Q7jy3mXqqiHd3N5aryG2KkgOg4UAoGwgwSrZqKQ==} + '@vitest/eslint-plugin@1.5.2': + resolution: {integrity: sha512-2t1F2iecXB/b1Ox4U137lhD3chihEE3dRVtu3qMD35tc6UqUjg1VGRJoS1AkFKwpT8zv8OQInzPQO06hrRkeqw==} engines: {node: '>=18'} peerDependencies: eslint: '>=8.57.0' @@ -4054,8 +4176,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.8.32: - resolution: {integrity: sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==} + baseline-browser-mapping@2.9.5: + resolution: {integrity: sha512-D5vIoztZOq1XM54LUdttJVc96ggEsIfju2JBvht06pSzpckp3C7HReun67Bghzrtdsq9XdMGbSSB3v3GhMNmAA==} hasBin: true before-after-hook@3.0.2: @@ -4125,8 +4247,8 @@ packages: browserify-zlib@0.2.0: resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==} - browserslist@4.28.0: - resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -4191,11 +4313,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001757: - resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==} - - caniuse-lite@1.0.30001759: - resolution: {integrity: sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==} + caniuse-lite@1.0.30001760: + resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} canvas@3.2.0: resolution: {integrity: sha512-jk0GxrLtUEmW/TmFsk2WghvgHe8B0pxGilqCL21y8lHkPUGa6FTsnCNtHPOzT8O3y+N+m3espawV80bbBlgfTA==} @@ -4340,10 +4459,6 @@ packages: resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} engines: {node: '>=18'} - cli-width@4.1.0: - resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} - engines: {node: '>= 12'} - client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -4458,10 +4573,6 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie@1.1.1: - resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} - engines: {node: '>=18'} - copy-to-clipboard@3.3.3: resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} @@ -4867,8 +4978,8 @@ packages: dompurify@3.2.7: resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==} - dompurify@3.3.0: - resolution: {integrity: sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==} + dompurify@3.3.1: + resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} @@ -4897,8 +5008,8 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - electron-to-chromium@1.5.263: - resolution: {integrity: sha512-DrqJ11Knd+lo+dv+lltvfMDLU27g14LMdH2b0O3Pio4uk0x+z7OR+JrmyacTPN2M8w3BrZ7/RTwG3R9B7irPlg==} + electron-to-chromium@1.5.267: + resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} elkjs@0.9.3: resolution: {integrity: sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==} @@ -4978,6 +5089,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -5082,8 +5198,8 @@ packages: resolution: {integrity: sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==} engines: {node: '>=5.0.0'} - eslint-plugin-oxlint@1.31.0: - resolution: {integrity: sha512-yIUkBg9qZCL9DZVSvH3FklF5urG7LRboZD0/YLf/CvihPpcfBeMyH1onaG3+iKMCIRa/uwXgdRjB5MSOplFTVw==} + eslint-plugin-oxlint@1.32.0: + resolution: {integrity: sha512-CodKgz/9q3euGbCYrXVRyFxHfnrxn9Q4EywqE4V/VYegry2pJ9/hPQ0OUDTRzbl3/pPbVndkrUUm5tK8NTSgeg==} eslint-plugin-perfectionist@4.15.1: resolution: {integrity: sha512-MHF0cBoOG0XyBf7G0EAFCuJJu4I18wy0zAoT1OHfx2o6EOx1EFTIzr2HGeuZa1kDcusoX0xJ9V7oZmaeFd773Q==} @@ -5091,8 +5207,8 @@ packages: peerDependencies: eslint: '>=8.45.0' - eslint-plugin-pnpm@1.3.0: - resolution: {integrity: sha512-Lkdnj3afoeUIkDUu8X74z60nrzjQ2U55EbOeI+qz7H1He4IO4gmUKT2KQIl0It52iMHJeuyLDWWNgjr6UIK8nw==} + eslint-plugin-pnpm@1.4.2: + resolution: {integrity: sha512-em/HEUlud5G3G4VZe2dhgsLm2ey6CG+Y+Lq3fS/RsbnmKhi+D+LcLz31GphTJhizCoKl2oAVndMltOHbuBYe+A==} peerDependencies: eslint: ^9.0.0 @@ -5619,10 +5735,6 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - graphql@16.12.0: - resolution: {integrity: sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==} - engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - gzip-size@6.0.0: resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} engines: {node: '>=10'} @@ -5682,8 +5794,8 @@ packages: hast-util-to-jsx-runtime@2.3.6: resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} - hast-util-to-parse5@8.0.0: - resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==} + hast-util-to-parse5@8.0.1: + resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} hast-util-to-text@4.0.2: resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} @@ -5701,9 +5813,6 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true - headers-polyfill@4.0.3: - resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} - highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} @@ -6241,8 +6350,8 @@ packages: engines: {node: '>=6'} hasBin: true - jsonc-eslint-parser@2.4.1: - resolution: {integrity: sha512-uuPNLJkKN8NXAlZlQ6kmUF9qO+T6Kyd7oV4+/7yy8Jz6+MZNyhPq8EdLpdfnPVzUC8qSf1b4j1azKaGnFsjmsw==} + jsonc-eslint-parser@2.4.2: + resolution: {integrity: sha512-1e4qoRgnn448pRuMvKGsFFymUCquZV0mpGgOyIKNgD3JVDTsVJyRBGH/Fm0tBb8WsWGgmB1mDe6/yJMQM37DUA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} jsonc-parser@3.3.1: @@ -6262,8 +6371,8 @@ packages: resolution: {integrity: sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - katex@0.16.25: - resolution: {integrity: sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q==} + katex@0.16.27: + resolution: {integrity: sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw==} hasBin: true keyv@4.5.4: @@ -6280,16 +6389,16 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} - knip@5.71.0: - resolution: {integrity: sha512-hwgdqEJ+7DNJ5jE8BCPu7b57TY7vUwP6MzWYgCgPpg6iPCee/jKPShDNIlFER2koti4oz5xF88VJbKCb4Wl71g==} + knip@5.72.0: + resolution: {integrity: sha512-rlyoXI8FcggNtM/QXd/GW0sbsYvNuA/zPXt7bsuVi6kVQogY2PDCr81bPpzNnl0CP8AkFm2Z2plVeL5QQSis2w==} engines: {node: '>=18.18.0'} hasBin: true peerDependencies: '@types/node': '>=18' typescript: '>=5.0.4 <7' - ky@1.14.0: - resolution: {integrity: sha512-Rczb6FMM6JT0lvrOlP5WUOCB7s9XKxzwgErzhKlKde1bEV90FXplV1o87fpt4PU/asJFiqjYJxAJyzJhcrxOsQ==} + ky@1.14.1: + resolution: {integrity: sha512-hYje4L9JCmpEQBtudo+v52X5X8tgWXUYyPcxKSuxQNboqufecl9VMWjGiucAFH060AwPXHZuH+WB2rrqfkmafw==} engines: {node: '>=18'} lamejs@1.2.1: @@ -6741,20 +6850,6 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - msw@2.12.4: - resolution: {integrity: sha512-rHNiVfTyKhzc0EjoXUBVGteNKBevdjOlVC6GlIRXpy+/3LHEIGRovnB5WPjcvmNODVQ1TNFnoa7wsGbd0V3epg==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - typescript: '>= 4.8.x' - peerDependenciesMeta: - typescript: - optional: true - - mute-stream@2.0.0: - resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} - engines: {node: ^18.17.0 || >=20.5.0} - mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -6914,11 +7009,11 @@ packages: outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} - oxc-resolver@11.14.2: - resolution: {integrity: sha512-M5fERQKcrCngMZNnk1gRaBbYcqpqXLgMcoqAo7Wpty+KH0I18i03oiy2peUsGJwFaKAEbmo+CtAyhXh08RZ1RA==} + oxc-resolver@11.15.0: + resolution: {integrity: sha512-Hk2J8QMYwmIO9XTCUiOH00+Xk2/+aBxRUnhrSlANDyCnLYc32R1WSIq1sU2yEdlqd53FfMpPEpnBYIKQMzliJw==} - oxlint@1.31.0: - resolution: {integrity: sha512-U+Z3VShi1zuLF2Hz/pm4vWJUBm5sDHjwSzj340tz4tS2yXg9H5PTipsZv+Yu/alg6Z7EM2cZPKGNBZAvmdfkQg==} + oxlint@1.32.0: + resolution: {integrity: sha512-HYDQCga7flsdyLMUIxTgSnEx5KBxpP9VINB8NgO+UjV80xBiTQXyVsvjtneMT3ZBLMbL0SlG/Dm03XQAsEshMA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -7041,9 +7136,6 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-to-regexp@6.3.0: - resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} - path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -7134,8 +7226,8 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} - pnpm-workspace-yaml@1.3.0: - resolution: {integrity: sha512-Krb5q8Totd5mVuLx7we+EFHq/AfxA75nbfTm25Q1pIf606+RlaKUG+PXH8SDihfe5b5k4H09gE+sL47L1t5lbw==} + pnpm-workspace-yaml@1.4.2: + resolution: {integrity: sha512-L2EKuOeV8aSt3z0RNtdwkg96BHV4WRN9pN2oTHKkMQQRxVEHFXPTbB+nly6ip1qV+JQM6qBebSiMgPRBx8S0Vw==} points-on-curve@0.2.0: resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} @@ -7295,9 +7387,6 @@ packages: property-information@5.6.0: resolution: {integrity: sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==} - property-information@6.5.0: - resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} - property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} @@ -7399,8 +7488,8 @@ packages: react-fast-compare@3.2.2: resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} - react-hook-form@7.67.0: - resolution: {integrity: sha512-E55EOwKJHHIT/I6J9DmQbCWToAYSw9nN5R57MZw9rMtjh+YQreMDxRLfdjfxQbiJ3/qbg3Z02wGzBX4M+5fMtQ==} + react-hook-form@7.68.0: + resolution: {integrity: sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q==} engines: {node: '>=18.0.0'} peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 @@ -7729,9 +7818,6 @@ packages: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} - rettime@0.7.0: - resolution: {integrity: sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==} - reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -7806,8 +7892,8 @@ packages: webpack: optional: true - sass@1.94.2: - resolution: {integrity: sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==} + sass@1.95.0: + resolution: {integrity: sha512-9QMjhLq+UkOg/4bb8Lt8A+hJZvY3t+9xeZMKSBtBEgxrXA3ed5Ts4NDreUkYgJP1BTmrscQE/xYhf7iShow6lw==} engines: {node: '>=14.0.0'} hasBin: true @@ -7984,10 +8070,6 @@ packages: state-local@1.0.7: resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} - statuses@2.0.2: - resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} - engines: {node: '>= 0.8'} - storybook@9.1.13: resolution: {integrity: sha512-G3KZ36EVzXyHds72B/qtWiJnhUpM0xOUeYlDcO9DSHL1bDTv15cW4+upBl+mcBZrDvU838cn7Bv4GpF+O5MCfw==} hasBin: true @@ -8152,10 +8234,6 @@ packages: tabbable@6.3.0: resolution: {integrity: sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ==} - tagged-tag@1.0.0: - resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} - engines: {node: '>=20'} - tailwind-merge@2.6.0: resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==} @@ -8183,8 +8261,8 @@ packages: resolution: {integrity: sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==} engines: {node: '>=10'} - terser-webpack-plugin@5.3.14: - resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==} + terser-webpack-plugin@5.3.15: + resolution: {integrity: sha512-PGkOdpRFK+rb1TzVz+msVhw4YMRT9txLF4kRqvJhGhCM324xuR3REBSHALN+l+sAhKUmz0aotnjp5D+P83mLhQ==} engines: {node: '>= 10.13.0'} peerDependencies: '@swc/core': '*' @@ -8262,18 +8340,14 @@ packages: toggle-selection@1.0.6: resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} - toml-eslint-parser@0.10.0: - resolution: {integrity: sha512-khrZo4buq4qVmsGzS5yQjKe/WsFvV8fGfOjDQN0q4iy9FjRfPWRgTFrU8u1R2iu/SfWLhY9WnCi4Jhdrcbtg+g==} + toml-eslint-parser@0.10.1: + resolution: {integrity: sha512-9mjy3frhioGIVGcwamlVlUyJ9x+WHw/TXiz9R4YOlmsIuBN43r9Dp8HZ35SF9EKjHrn3BUZj04CF+YqZ2oJ+7w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} - tough-cookie@6.0.0: - resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} - engines: {node: '>=16'} - tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} @@ -8373,10 +8447,6 @@ packages: resolution: {integrity: sha512-5zknd7Dss75pMSED270A1RQS3KloqRJA9XbXLe0eCxyw7xXFb3rd+9B0UQ/0E+LQT6lnrLviEolYORlRWamn4w==} engines: {node: '>=16'} - type-fest@5.3.1: - resolution: {integrity: sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==} - engines: {node: '>=20'} - typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -8459,15 +8529,12 @@ packages: resolution: {integrity: sha512-us4j03/499KhbGP8BU7Hrzrgseo+KdfJYWcbcajCOqsAyb8Gk0Yn2kiUIcZISYCb1JFaZfIuG3b42HmguVOKCQ==} engines: {node: '>=18.12.0'} - until-async@3.0.2: - resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} - upath@1.2.0: resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==} engines: {node: '>=4'} - update-browserslist-db@1.1.4: - resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} + update-browserslist-db@1.2.2: + resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -8736,10 +8803,6 @@ packages: workbox-window@6.6.0: resolution: {integrity: sha512-L4N9+vka17d16geaJXXRjENLFldvkWy7JyGxElRD0JvBxvFEd8LOhr+uXCcar/NzAmIBRv9EZ+M+Qr4mOoBITw==} - wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -8794,8 +8857,8 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yaml-eslint-parser@1.3.1: - resolution: {integrity: sha512-MdSgP9YA9QjtAO2+lt4O7V2bnH22LPnfeVLiQqjY3cOyn8dy/Ief8otjIe6SPPTK03nM7O3Yl0LTfWuF7l+9yw==} + yaml-eslint-parser@1.3.2: + resolution: {integrity: sha512-odxVsHAkZYYglR30aPYRY4nUGJnoJ2y1ww2HDvZALo0BDETv9kWbi16J52eHs+PWRNmF4ub6nZqfVOeesOvntg==} engines: {node: ^14.17.0 || >=16.0.0} yaml@1.10.2: @@ -8831,10 +8894,6 @@ packages: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} - yoctocolors-cjs@2.1.3: - resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} - engines: {node: '>=18'} - zen-observable-ts@1.1.0: resolution: {integrity: sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==} @@ -8897,7 +8956,7 @@ snapshots: '@alloc/quick-lru@5.2.0': {} - '@amplitude/analytics-browser@2.31.3': + '@amplitude/analytics-browser@2.31.4': dependencies: '@amplitude/analytics-core': 2.33.0 '@amplitude/plugin-autocapture-browser': 1.18.0 @@ -8949,12 +9008,12 @@ snapshots: '@amplitude/analytics-core': 2.33.0 tslib: 2.8.1 - '@amplitude/plugin-session-replay-browser@1.23.6(@amplitude/rrweb@2.0.0-alpha.33)(rollup@2.79.2)': + '@amplitude/plugin-session-replay-browser@1.24.1(@amplitude/rrweb@2.0.0-alpha.33)(rollup@2.79.2)': dependencies: '@amplitude/analytics-client-common': 2.4.16 '@amplitude/analytics-core': 2.33.0 '@amplitude/analytics-types': 2.11.0 - '@amplitude/session-replay-browser': 1.29.8(@amplitude/rrweb@2.0.0-alpha.33)(rollup@2.79.2) + '@amplitude/session-replay-browser': 1.30.0(@amplitude/rrweb@2.0.0-alpha.33)(rollup@2.79.2) idb-keyval: 6.2.2 tslib: 2.8.1 transitivePeerDependencies: @@ -9008,7 +9067,7 @@ snapshots: base64-arraybuffer: 1.0.2 mitt: 3.0.1 - '@amplitude/session-replay-browser@1.29.8(@amplitude/rrweb@2.0.0-alpha.33)(rollup@2.79.2)': + '@amplitude/session-replay-browser@1.30.0(@amplitude/rrweb@2.0.0-alpha.33)(rollup@2.79.2)': dependencies: '@amplitude/analytics-client-common': 2.4.16 '@amplitude/analytics-core': 2.33.0 @@ -9042,9 +9101,9 @@ snapshots: '@eslint-community/eslint-plugin-eslint-comments': 4.5.0(eslint@9.39.1(jiti@1.21.7)) '@eslint/markdown': 7.5.1 '@stylistic/eslint-plugin': 5.6.1(eslint@9.39.1(jiti@1.21.7)) - '@typescript-eslint/eslint-plugin': 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@vitest/eslint-plugin': 1.5.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@vitest/eslint-plugin': 1.5.2(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) ansis: 4.2.0 cac: 6.7.14 eslint: 9.39.1(jiti@1.21.7) @@ -9059,21 +9118,21 @@ snapshots: eslint-plugin-n: 17.23.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) eslint-plugin-no-only-tests: 3.3.0 eslint-plugin-perfectionist: 4.15.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - eslint-plugin-pnpm: 1.3.0(eslint@9.39.1(jiti@1.21.7)) + eslint-plugin-pnpm: 1.4.2(eslint@9.39.1(jiti@1.21.7)) eslint-plugin-regexp: 2.10.0(eslint@9.39.1(jiti@1.21.7)) eslint-plugin-toml: 0.12.0(eslint@9.39.1(jiti@1.21.7)) eslint-plugin-unicorn: 61.0.2(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-unused-imports: 4.3.0(@typescript-eslint/eslint-plugin@8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-vue: 10.6.2(@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@1.21.7)))(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@1.21.7))) + eslint-plugin-unused-imports: 4.3.0(@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7)) + eslint-plugin-vue: 10.6.2(@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@1.21.7)))(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@1.21.7))) eslint-plugin-yml: 1.19.0(eslint@9.39.1(jiti@1.21.7)) eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.25)(eslint@9.39.1(jiti@1.21.7)) globals: 16.5.0 - jsonc-eslint-parser: 2.4.1 + jsonc-eslint-parser: 2.4.2 local-pkg: 1.1.2 parse-gitignore: 2.0.0 - toml-eslint-parser: 0.10.0 + toml-eslint-parser: 0.10.1 vue-eslint-parser: 10.2.0(eslint@9.39.1(jiti@1.21.7)) - yaml-eslint-parser: 1.3.1 + yaml-eslint-parser: 1.3.2 optionalDependencies: '@eslint-react/eslint-plugin': 1.53.1(eslint@9.39.1(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.9.3))(typescript@5.9.3) '@next/eslint-plugin-next': 15.5.7 @@ -9142,7 +9201,7 @@ snapshots: dependencies: '@babel/compat-data': 7.28.5 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.0 + browserslist: 4.28.1 lru-cache: 5.1.1 semver: 6.3.1 @@ -9933,13 +9992,13 @@ snapshots: '@chevrotain/utils@11.0.3': {} - '@chromatic-com/storybook@4.1.3(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)))': + '@chromatic-com/storybook@4.1.3(storybook@9.1.13(@testing-library/dom@10.4.1))': dependencies: '@neoconfetti/react': 1.0.0 chromatic: 13.3.4 filesize: 10.1.6 jsonfile: 6.2.0 - storybook: 9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)) + storybook: 9.1.13(@testing-library/dom@10.4.1) strip-ansi: 7.1.2 transitivePeerDependencies: - '@chromatic-com/cypress' @@ -10038,7 +10097,7 @@ snapshots: '@es-joy/jsdoccomment@0.50.2': dependencies: '@types/estree': 1.0.8 - '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/types': 8.49.0 comment-parser: 1.4.1 esquery: 1.6.0 jsdoc-type-pratt-parser: 4.1.0 @@ -10046,7 +10105,7 @@ snapshots: '@es-joy/jsdoccomment@0.58.0': dependencies: '@types/estree': 1.0.8 - '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/types': 8.49.0 comment-parser: 1.4.1 esquery: 1.6.0 jsdoc-type-pratt-parser: 5.4.0 @@ -10054,78 +10113,156 @@ snapshots: '@esbuild/aix-ppc64@0.25.0': optional: true + '@esbuild/aix-ppc64@0.25.12': + optional: true + '@esbuild/android-arm64@0.25.0': optional: true + '@esbuild/android-arm64@0.25.12': + optional: true + '@esbuild/android-arm@0.25.0': optional: true + '@esbuild/android-arm@0.25.12': + optional: true + '@esbuild/android-x64@0.25.0': optional: true + '@esbuild/android-x64@0.25.12': + optional: true + '@esbuild/darwin-arm64@0.25.0': optional: true + '@esbuild/darwin-arm64@0.25.12': + optional: true + '@esbuild/darwin-x64@0.25.0': optional: true + '@esbuild/darwin-x64@0.25.12': + optional: true + '@esbuild/freebsd-arm64@0.25.0': optional: true + '@esbuild/freebsd-arm64@0.25.12': + optional: true + '@esbuild/freebsd-x64@0.25.0': optional: true + '@esbuild/freebsd-x64@0.25.12': + optional: true + '@esbuild/linux-arm64@0.25.0': optional: true + '@esbuild/linux-arm64@0.25.12': + optional: true + '@esbuild/linux-arm@0.25.0': optional: true + '@esbuild/linux-arm@0.25.12': + optional: true + '@esbuild/linux-ia32@0.25.0': optional: true + '@esbuild/linux-ia32@0.25.12': + optional: true + '@esbuild/linux-loong64@0.25.0': optional: true + '@esbuild/linux-loong64@0.25.12': + optional: true + '@esbuild/linux-mips64el@0.25.0': optional: true + '@esbuild/linux-mips64el@0.25.12': + optional: true + '@esbuild/linux-ppc64@0.25.0': optional: true + '@esbuild/linux-ppc64@0.25.12': + optional: true + '@esbuild/linux-riscv64@0.25.0': optional: true + '@esbuild/linux-riscv64@0.25.12': + optional: true + '@esbuild/linux-s390x@0.25.0': optional: true + '@esbuild/linux-s390x@0.25.12': + optional: true + '@esbuild/linux-x64@0.25.0': optional: true + '@esbuild/linux-x64@0.25.12': + optional: true + '@esbuild/netbsd-arm64@0.25.0': optional: true + '@esbuild/netbsd-arm64@0.25.12': + optional: true + '@esbuild/netbsd-x64@0.25.0': optional: true + '@esbuild/netbsd-x64@0.25.12': + optional: true + '@esbuild/openbsd-arm64@0.25.0': optional: true + '@esbuild/openbsd-arm64@0.25.12': + optional: true + '@esbuild/openbsd-x64@0.25.0': optional: true + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + '@esbuild/sunos-x64@0.25.0': optional: true + '@esbuild/sunos-x64@0.25.12': + optional: true + '@esbuild/win32-arm64@0.25.0': optional: true + '@esbuild/win32-arm64@0.25.12': + optional: true + '@esbuild/win32-ia32@0.25.0': optional: true + '@esbuild/win32-ia32@0.25.12': + optional: true + '@esbuild/win32-x64@0.25.0': optional: true + '@esbuild/win32-x64@0.25.12': + optional: true + '@eslint-community/eslint-plugin-eslint-comments@4.5.0(eslint@9.39.1(jiti@1.21.7))': dependencies: escape-string-regexp: 4.0.0 @@ -10144,9 +10281,9 @@ snapshots: '@eslint-react/ast@1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@eslint-react/eff': 1.53.1 - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) string-ts: 2.3.1 ts-pattern: 5.9.0 transitivePeerDependencies: @@ -10161,10 +10298,10 @@ snapshots: '@eslint-react/kit': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) '@eslint-react/shared': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) '@eslint-react/var': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/type-utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) birecord: 0.1.1 ts-pattern: 5.9.0 transitivePeerDependencies: @@ -10179,10 +10316,10 @@ snapshots: '@eslint-react/eff': 1.53.1 '@eslint-react/kit': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) '@eslint-react/shared': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/type-utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) eslint: 9.39.1(jiti@1.21.7) eslint-plugin-react-debug: 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) eslint-plugin-react-dom: 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) @@ -10199,7 +10336,7 @@ snapshots: '@eslint-react/kit@1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@eslint-react/eff': 1.53.1 - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) ts-pattern: 5.9.0 zod: 4.1.13 transitivePeerDependencies: @@ -10211,7 +10348,7 @@ snapshots: dependencies: '@eslint-react/eff': 1.53.1 '@eslint-react/kit': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) ts-pattern: 5.9.0 zod: 4.1.13 transitivePeerDependencies: @@ -10223,9 +10360,9 @@ snapshots: dependencies: '@eslint-react/ast': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) '@eslint-react/eff': 1.53.1 - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) string-ts: 2.3.1 ts-pattern: 5.9.0 transitivePeerDependencies: @@ -10352,7 +10489,7 @@ snapshots: '@floating-ui/react': 0.26.28(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@react-aria/focus': 3.21.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@tanstack/react-virtual': 3.13.12(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@tanstack/react-virtual': 3.13.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) @@ -10360,9 +10497,9 @@ snapshots: dependencies: react: 19.2.1 - '@hookform/resolvers@3.10.0(react-hook-form@7.67.0(react@19.2.1))': + '@hookform/resolvers@3.10.0(react-hook-form@7.68.0(react@19.2.1))': dependencies: - react-hook-form: 7.67.0(react@19.2.1) + react-hook-form: 7.68.0(react@19.2.1) '@humanfs/core@0.19.1': {} @@ -10555,39 +10692,6 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true - '@inquirer/ansi@1.0.2': - optional: true - - '@inquirer/confirm@5.1.21(@types/node@18.15.0)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@18.15.0) - '@inquirer/type': 3.0.10(@types/node@18.15.0) - optionalDependencies: - '@types/node': 18.15.0 - optional: true - - '@inquirer/core@10.3.2(@types/node@18.15.0)': - dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@18.15.0) - cli-width: 4.1.0 - mute-stream: 2.0.0 - signal-exit: 4.1.0 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 18.15.0 - optional: true - - '@inquirer/figures@1.0.15': - optional: true - - '@inquirer/type@3.0.10(@types/node@18.15.0)': - optionalDependencies: - '@types/node': 18.15.0 - optional: true - '@isaacs/balanced-match@4.0.1': {} '@isaacs/brace-expansion@5.0.0': @@ -11025,16 +11129,6 @@ snapshots: outvariant: 1.4.3 strict-event-emitter: 0.5.1 - '@mswjs/interceptors@0.40.0': - dependencies: - '@open-draft/deferred-promise': 2.2.0 - '@open-draft/logger': 0.3.0 - '@open-draft/until': 2.1.0 - is-node-process: 1.2.0 - outvariant: 1.4.3 - strict-event-emitter: 0.5.1 - optional: true - '@napi-rs/wasm-runtime@1.1.0': dependencies: '@emnapi/core': 1.7.1 @@ -11200,90 +11294,90 @@ snapshots: '@open-draft/until@2.1.0': {} - '@oxc-resolver/binding-android-arm-eabi@11.14.2': + '@oxc-resolver/binding-android-arm-eabi@11.15.0': optional: true - '@oxc-resolver/binding-android-arm64@11.14.2': + '@oxc-resolver/binding-android-arm64@11.15.0': optional: true - '@oxc-resolver/binding-darwin-arm64@11.14.2': + '@oxc-resolver/binding-darwin-arm64@11.15.0': optional: true - '@oxc-resolver/binding-darwin-x64@11.14.2': + '@oxc-resolver/binding-darwin-x64@11.15.0': optional: true - '@oxc-resolver/binding-freebsd-x64@11.14.2': + '@oxc-resolver/binding-freebsd-x64@11.15.0': optional: true - '@oxc-resolver/binding-linux-arm-gnueabihf@11.14.2': + '@oxc-resolver/binding-linux-arm-gnueabihf@11.15.0': optional: true - '@oxc-resolver/binding-linux-arm-musleabihf@11.14.2': + '@oxc-resolver/binding-linux-arm-musleabihf@11.15.0': optional: true - '@oxc-resolver/binding-linux-arm64-gnu@11.14.2': + '@oxc-resolver/binding-linux-arm64-gnu@11.15.0': optional: true - '@oxc-resolver/binding-linux-arm64-musl@11.14.2': + '@oxc-resolver/binding-linux-arm64-musl@11.15.0': optional: true - '@oxc-resolver/binding-linux-ppc64-gnu@11.14.2': + '@oxc-resolver/binding-linux-ppc64-gnu@11.15.0': optional: true - '@oxc-resolver/binding-linux-riscv64-gnu@11.14.2': + '@oxc-resolver/binding-linux-riscv64-gnu@11.15.0': optional: true - '@oxc-resolver/binding-linux-riscv64-musl@11.14.2': + '@oxc-resolver/binding-linux-riscv64-musl@11.15.0': optional: true - '@oxc-resolver/binding-linux-s390x-gnu@11.14.2': + '@oxc-resolver/binding-linux-s390x-gnu@11.15.0': optional: true - '@oxc-resolver/binding-linux-x64-gnu@11.14.2': + '@oxc-resolver/binding-linux-x64-gnu@11.15.0': optional: true - '@oxc-resolver/binding-linux-x64-musl@11.14.2': + '@oxc-resolver/binding-linux-x64-musl@11.15.0': optional: true - '@oxc-resolver/binding-openharmony-arm64@11.14.2': + '@oxc-resolver/binding-openharmony-arm64@11.15.0': optional: true - '@oxc-resolver/binding-wasm32-wasi@11.14.2': + '@oxc-resolver/binding-wasm32-wasi@11.15.0': dependencies: '@napi-rs/wasm-runtime': 1.1.0 optional: true - '@oxc-resolver/binding-win32-arm64-msvc@11.14.2': + '@oxc-resolver/binding-win32-arm64-msvc@11.15.0': optional: true - '@oxc-resolver/binding-win32-ia32-msvc@11.14.2': + '@oxc-resolver/binding-win32-ia32-msvc@11.15.0': optional: true - '@oxc-resolver/binding-win32-x64-msvc@11.14.2': + '@oxc-resolver/binding-win32-x64-msvc@11.15.0': optional: true - '@oxlint/darwin-arm64@1.31.0': + '@oxlint/darwin-arm64@1.32.0': optional: true - '@oxlint/darwin-x64@1.31.0': + '@oxlint/darwin-x64@1.32.0': optional: true - '@oxlint/linux-arm64-gnu@1.31.0': + '@oxlint/linux-arm64-gnu@1.32.0': optional: true - '@oxlint/linux-arm64-musl@1.31.0': + '@oxlint/linux-arm64-musl@1.32.0': optional: true - '@oxlint/linux-x64-gnu@1.31.0': + '@oxlint/linux-x64-gnu@1.32.0': optional: true - '@oxlint/linux-x64-musl@1.31.0': + '@oxlint/linux-x64-musl@1.32.0': optional: true - '@oxlint/win32-arm64@1.31.0': + '@oxlint/win32-arm64@1.32.0': optional: true - '@oxlint/win32-x64@1.31.0': + '@oxlint/win32-x64@1.32.0': optional: true '@parcel/watcher-android-arm64@2.5.1': @@ -11354,6 +11448,11 @@ snapshots: '@pkgr/core@0.2.9': {} + '@playwright/test@1.57.0': + dependencies: + playwright: 1.57.0 + optional: true + '@pmmmwh/react-refresh-webpack-plugin@0.5.17(react-refresh@0.14.2)(type-fest@4.2.0)(webpack-hot-middleware@2.26.1)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3))': dependencies: ansi-html: 0.0.9 @@ -11766,38 +11865,38 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 - '@storybook/addon-docs@9.1.13(@types/react@19.2.7)(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)))': + '@storybook/addon-docs@9.1.13(@types/react@19.2.7)(storybook@9.1.13(@testing-library/dom@10.4.1))': dependencies: '@mdx-js/react': 3.1.1(@types/react@19.2.7)(react@19.2.1) - '@storybook/csf-plugin': 9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3))) + '@storybook/csf-plugin': 9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1)) '@storybook/icons': 1.6.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@storybook/react-dom-shim': 9.1.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3))) + '@storybook/react-dom-shim': 9.1.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@9.1.13(@testing-library/dom@10.4.1)) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) - storybook: 9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)) + storybook: 9.1.13(@testing-library/dom@10.4.1) ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' - '@storybook/addon-links@9.1.13(react@19.2.1)(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)))': + '@storybook/addon-links@9.1.13(react@19.2.1)(storybook@9.1.13(@testing-library/dom@10.4.1))': dependencies: '@storybook/global': 5.0.0 - storybook: 9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)) + storybook: 9.1.13(@testing-library/dom@10.4.1) optionalDependencies: react: 19.2.1 - '@storybook/addon-onboarding@9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)))': + '@storybook/addon-onboarding@9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1))': dependencies: - storybook: 9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)) + storybook: 9.1.13(@testing-library/dom@10.4.1) - '@storybook/addon-themes@9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)))': + '@storybook/addon-themes@9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1))': dependencies: - storybook: 9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)) + storybook: 9.1.13(@testing-library/dom@10.4.1) ts-dedent: 2.2.0 - '@storybook/builder-webpack5@9.1.13(esbuild@0.25.0)(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)))(typescript@5.9.3)(uglify-js@3.19.3)': + '@storybook/builder-webpack5@9.1.13(esbuild@0.25.0)(storybook@9.1.13(@testing-library/dom@10.4.1))(typescript@5.9.3)(uglify-js@3.19.3)': dependencies: - '@storybook/core-webpack': 9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3))) + '@storybook/core-webpack': 9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1)) case-sensitive-paths-webpack-plugin: 2.4.0 cjs-module-lexer: 1.4.3 css-loader: 6.11.0(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) @@ -11805,9 +11904,9 @@ snapshots: fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.9.3)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) html-webpack-plugin: 5.6.5(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) magic-string: 0.30.21 - storybook: 9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)) + storybook: 9.1.13(@testing-library/dom@10.4.1) style-loader: 3.3.4(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) - terser-webpack-plugin: 5.3.14(esbuild@0.25.0)(uglify-js@3.19.3)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) + terser-webpack-plugin: 5.3.15(esbuild@0.25.0)(uglify-js@3.19.3)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) ts-dedent: 2.2.0 webpack: 5.103.0(esbuild@0.25.0)(uglify-js@3.19.3) webpack-dev-middleware: 6.1.3(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) @@ -11822,14 +11921,14 @@ snapshots: - uglify-js - webpack-cli - '@storybook/core-webpack@9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)))': + '@storybook/core-webpack@9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1))': dependencies: - storybook: 9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)) + storybook: 9.1.13(@testing-library/dom@10.4.1) ts-dedent: 2.2.0 - '@storybook/csf-plugin@9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)))': + '@storybook/csf-plugin@9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1))': dependencies: - storybook: 9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)) + storybook: 9.1.13(@testing-library/dom@10.4.1) unplugin: 1.16.1 '@storybook/global@5.0.0': {} @@ -11839,7 +11938,7 @@ snapshots: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) - '@storybook/nextjs@9.1.13(esbuild@0.25.0)(next@15.5.7(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2)(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)))(type-fest@4.2.0)(typescript@5.9.3)(uglify-js@3.19.3)(webpack-hot-middleware@2.26.1)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3))': + '@storybook/nextjs@9.1.13(esbuild@0.25.0)(next@15.5.7(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.95.0))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.95.0)(storybook@9.1.13(@testing-library/dom@10.4.1))(type-fest@4.2.0)(typescript@5.9.3)(uglify-js@3.19.3)(webpack-hot-middleware@2.26.1)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.5) @@ -11855,15 +11954,15 @@ snapshots: '@babel/preset-typescript': 7.28.5(@babel/core@7.28.5) '@babel/runtime': 7.28.4 '@pmmmwh/react-refresh-webpack-plugin': 0.5.17(react-refresh@0.14.2)(type-fest@4.2.0)(webpack-hot-middleware@2.26.1)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) - '@storybook/builder-webpack5': 9.1.13(esbuild@0.25.0)(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)))(typescript@5.9.3)(uglify-js@3.19.3) - '@storybook/preset-react-webpack': 9.1.13(esbuild@0.25.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)))(typescript@5.9.3)(uglify-js@3.19.3) - '@storybook/react': 9.1.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)))(typescript@5.9.3) + '@storybook/builder-webpack5': 9.1.13(esbuild@0.25.0)(storybook@9.1.13(@testing-library/dom@10.4.1))(typescript@5.9.3)(uglify-js@3.19.3) + '@storybook/preset-react-webpack': 9.1.13(esbuild@0.25.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@9.1.13(@testing-library/dom@10.4.1))(typescript@5.9.3)(uglify-js@3.19.3) + '@storybook/react': 9.1.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@9.1.13(@testing-library/dom@10.4.1))(typescript@5.9.3) '@types/semver': 7.7.1 babel-loader: 9.2.1(@babel/core@7.28.5)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) css-loader: 6.11.0(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) image-size: 2.0.2 loader-utils: 3.3.1 - next: 15.5.7(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2) + next: 15.5.7(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.95.0) node-polyfill-webpack-plugin: 2.0.1(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) postcss: 8.5.6 postcss-loader: 8.2.0(postcss@8.5.6)(typescript@5.9.3)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) @@ -11871,9 +11970,9 @@ snapshots: react-dom: 19.2.1(react@19.2.1) react-refresh: 0.14.2 resolve-url-loader: 5.0.0 - sass-loader: 16.0.6(sass@1.94.2)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) + sass-loader: 16.0.6(sass@1.95.0)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) semver: 7.7.3 - storybook: 9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)) + storybook: 9.1.13(@testing-library/dom@10.4.1) style-loader: 3.3.4(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) styled-jsx: 5.1.7(@babel/core@7.28.5)(react@19.2.1) tsconfig-paths: 4.2.0 @@ -11899,9 +11998,9 @@ snapshots: - webpack-hot-middleware - webpack-plugin-serve - '@storybook/preset-react-webpack@9.1.13(esbuild@0.25.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)))(typescript@5.9.3)(uglify-js@3.19.3)': + '@storybook/preset-react-webpack@9.1.13(esbuild@0.25.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@9.1.13(@testing-library/dom@10.4.1))(typescript@5.9.3)(uglify-js@3.19.3)': dependencies: - '@storybook/core-webpack': 9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3))) + '@storybook/core-webpack': 9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1)) '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.9.3)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) '@types/semver': 7.7.1 find-up: 7.0.0 @@ -11911,7 +12010,7 @@ snapshots: react-dom: 19.2.1(react@19.2.1) resolve: 1.22.11 semver: 7.7.3 - storybook: 9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)) + storybook: 9.1.13(@testing-library/dom@10.4.1) tsconfig-paths: 4.2.0 webpack: 5.103.0(esbuild@0.25.0)(uglify-js@3.19.3) optionalDependencies: @@ -11937,26 +12036,26 @@ snapshots: transitivePeerDependencies: - supports-color - '@storybook/react-dom-shim@9.1.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)))': + '@storybook/react-dom-shim@9.1.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@9.1.13(@testing-library/dom@10.4.1))': dependencies: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) - storybook: 9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)) + storybook: 9.1.13(@testing-library/dom@10.4.1) - '@storybook/react@9.1.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)))(typescript@5.9.3)': + '@storybook/react@9.1.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@9.1.13(@testing-library/dom@10.4.1))(typescript@5.9.3)': dependencies: '@storybook/global': 5.0.0 - '@storybook/react-dom-shim': 9.1.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3))) + '@storybook/react-dom-shim': 9.1.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@9.1.13(@testing-library/dom@10.4.1)) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) - storybook: 9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)) + storybook: 9.1.13(@testing-library/dom@10.4.1) optionalDependencies: typescript: 5.9.3 '@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@1.21.7))': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) - '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/types': 8.49.0 eslint: 9.39.1(jiti@1.21.7) eslint-visitor-keys: 4.2.1 espree: 10.4.0 @@ -11991,7 +12090,7 @@ snapshots: '@tanstack/devtools-event-client@0.3.5': {} - '@tanstack/form-core@1.27.0': + '@tanstack/form-core@1.27.1': dependencies: '@tanstack/devtools-event-client': 0.3.5 '@tanstack/pacer': 0.15.4 @@ -12002,27 +12101,27 @@ snapshots: '@tanstack/devtools-event-client': 0.3.5 '@tanstack/store': 0.7.7 - '@tanstack/query-core@5.90.11': {} + '@tanstack/query-core@5.90.12': {} '@tanstack/query-devtools@5.91.1': {} - '@tanstack/react-form@1.27.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@tanstack/react-form@1.27.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: - '@tanstack/form-core': 1.27.0 + '@tanstack/form-core': 1.27.1 '@tanstack/react-store': 0.8.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) react: 19.2.1 transitivePeerDependencies: - react-dom - '@tanstack/react-query-devtools@5.91.1(@tanstack/react-query@5.90.11(react@19.2.1))(react@19.2.1)': + '@tanstack/react-query-devtools@5.91.1(@tanstack/react-query@5.90.12(react@19.2.1))(react@19.2.1)': dependencies: '@tanstack/query-devtools': 5.91.1 - '@tanstack/react-query': 5.90.11(react@19.2.1) + '@tanstack/react-query': 5.90.12(react@19.2.1) react: 19.2.1 - '@tanstack/react-query@5.90.11(react@19.2.1)': + '@tanstack/react-query@5.90.12(react@19.2.1)': dependencies: - '@tanstack/query-core': 5.90.11 + '@tanstack/query-core': 5.90.12 react: 19.2.1 '@tanstack/react-store@0.8.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': @@ -12032,9 +12131,9 @@ snapshots: react-dom: 19.2.1(react@19.2.1) use-sync-external-store: 1.6.0(react@19.2.1) - '@tanstack/react-virtual@3.13.12(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@tanstack/react-virtual@3.13.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: - '@tanstack/virtual-core': 3.13.12 + '@tanstack/virtual-core': 3.13.13 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) @@ -12042,7 +12141,7 @@ snapshots: '@tanstack/store@0.8.0': {} - '@tanstack/virtual-core@3.13.12': {} + '@tanstack/virtual-core@3.13.13': {} '@testing-library/dom@10.4.1': dependencies: @@ -12343,7 +12442,7 @@ snapshots: '@types/node@18.15.0': {} - '@types/node@20.19.25': + '@types/node@20.19.26': dependencies: undici-types: 6.21.0 @@ -12395,9 +12494,6 @@ snapshots: '@types/stack-utils@2.0.3': {} - '@types/statuses@2.0.6': - optional: true - '@types/trusted-types@2.0.7': {} '@types/unist@2.0.11': {} @@ -12416,16 +12512,15 @@ snapshots: '@types/zen-observable@0.8.3': {} - '@typescript-eslint/eslint-plugin@8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/type-utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/parser': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.49.0 eslint: 9.39.1(jiti@1.21.7) - graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.1.0(typescript@5.9.3) @@ -12433,41 +12528,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.49.0 debug: 4.4.3 eslint: 9.39.1(jiti@1.21.7) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.48.1(typescript@5.9.3)': + '@typescript-eslint/project-service@8.49.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) - '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.48.1': + '@typescript-eslint/scope-manager@8.49.0': dependencies: - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/visitor-keys': 8.49.0 - '@typescript-eslint/tsconfig-utils@8.48.1(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.49.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.1(jiti@1.21.7) ts-api-utils: 2.1.0(typescript@5.9.3) @@ -12475,14 +12570,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.48.1': {} + '@typescript-eslint/types@8.49.0': {} - '@typescript-eslint/typescript-estree@8.48.1(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.49.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.48.1(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/project-service': 8.49.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/visitor-keys': 8.49.0 debug: 4.4.3 minimatch: 9.0.5 semver: 7.7.3 @@ -12492,59 +12587,59 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/utils@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) eslint: 9.39.1(jiti@1.21.7) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.48.1': + '@typescript-eslint/visitor-keys@8.49.0': dependencies: - '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/types': 8.49.0 eslint-visitor-keys: 4.2.1 - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20251204.1': + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20251209.1': optional: true - '@typescript/native-preview-darwin-x64@7.0.0-dev.20251204.1': + '@typescript/native-preview-darwin-x64@7.0.0-dev.20251209.1': optional: true - '@typescript/native-preview-linux-arm64@7.0.0-dev.20251204.1': + '@typescript/native-preview-linux-arm64@7.0.0-dev.20251209.1': optional: true - '@typescript/native-preview-linux-arm@7.0.0-dev.20251204.1': + '@typescript/native-preview-linux-arm@7.0.0-dev.20251209.1': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20251204.1': + '@typescript/native-preview-linux-x64@7.0.0-dev.20251209.1': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20251204.1': + '@typescript/native-preview-win32-arm64@7.0.0-dev.20251209.1': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20251204.1': + '@typescript/native-preview-win32-x64@7.0.0-dev.20251209.1': optional: true - '@typescript/native-preview@7.0.0-dev.20251204.1': + '@typescript/native-preview@7.0.0-dev.20251209.1': optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20251204.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20251204.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20251204.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20251204.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20251204.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20251204.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20251204.1 + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20251209.1 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20251209.1 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20251209.1 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20251209.1 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20251209.1 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20251209.1 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20251209.1 '@ungap/structured-clone@1.3.0': {} - '@vitest/eslint-plugin@1.5.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': + '@vitest/eslint-plugin@1.5.2(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) eslint: 9.39.1(jiti@1.21.7) optionalDependencies: typescript: 5.9.3 @@ -12559,13 +12654,11 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3))': + '@vitest/mocker@3.2.4': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 - optionalDependencies: - msw: 2.12.4(@types/node@18.15.0)(typescript@5.9.3) '@vitest/pretty-format@3.2.4': dependencies: @@ -12845,8 +12938,8 @@ snapshots: autoprefixer@10.4.22(postcss@8.5.6): dependencies: - browserslist: 4.28.0 - caniuse-lite: 1.0.30001757 + browserslist: 4.28.1 + caniuse-lite: 1.0.30001760 fraction.js: 5.3.4 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -12962,7 +13055,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.8.32: {} + baseline-browser-mapping@2.9.5: {} before-after-hook@3.0.2: {} @@ -13054,13 +13147,13 @@ snapshots: dependencies: pako: 1.0.11 - browserslist@4.28.0: + browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.8.32 - caniuse-lite: 1.0.30001757 - electron-to-chromium: 1.5.263 + baseline-browser-mapping: 2.9.5 + caniuse-lite: 1.0.30001760 + electron-to-chromium: 1.5.267 node-releases: 2.0.27 - update-browserslist-db: 1.1.4(browserslist@4.28.0) + update-browserslist-db: 1.2.2(browserslist@4.28.1) bser@2.1.1: dependencies: @@ -13116,9 +13209,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001757: {} - - caniuse-lite@1.0.30001759: {} + caniuse-lite@1.0.30001760: {} canvas@3.2.0: dependencies: @@ -13251,9 +13342,6 @@ snapshots: slice-ansi: 5.0.0 string-width: 4.2.3 - cli-width@4.1.0: - optional: true - client-only@0.0.1: {} cliui@8.0.1: @@ -13354,16 +13442,13 @@ snapshots: convert-source-map@2.0.0: {} - cookie@1.1.1: - optional: true - copy-to-clipboard@3.3.3: dependencies: toggle-selection: 1.0.6 core-js-compat@3.47.0: dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 core-js-pure@3.47.0: {} @@ -13804,7 +13889,7 @@ snapshots: optionalDependencies: '@types/trusted-types': 2.0.7 - dompurify@3.3.0: + dompurify@3.3.1: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -13839,7 +13924,7 @@ snapshots: dependencies: jake: 10.9.4 - electron-to-chromium@1.5.263: {} + electron-to-chromium@1.5.267: {} elkjs@0.9.3: {} @@ -13947,6 +14032,35 @@ snapshots: '@esbuild/win32-ia32': 0.25.0 '@esbuild/win32-x64': 0.25.0 + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + escalade@3.2.0: {} escape-string-regexp@1.0.5: {} @@ -13976,11 +14090,11 @@ snapshots: dependencies: pathe: 2.0.3 - eslint-json-compat-utils@0.2.1(eslint@9.39.1(jiti@1.21.7))(jsonc-eslint-parser@2.4.1): + eslint-json-compat-utils@0.2.1(eslint@9.39.1(jiti@1.21.7))(jsonc-eslint-parser@2.4.2): dependencies: eslint: 9.39.1(jiti@1.21.7) esquery: 1.6.0 - jsonc-eslint-parser: 2.4.1 + jsonc-eslint-parser: 2.4.2 eslint-merge-processors@2.0.0(eslint@9.39.1(jiti@1.21.7)): dependencies: @@ -14005,7 +14119,7 @@ snapshots: eslint-plugin-import-lite@0.3.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3): dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) - '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/types': 8.49.0 eslint: 9.39.1(jiti@1.21.7) optionalDependencies: typescript: 5.9.3 @@ -14033,10 +14147,10 @@ snapshots: diff-sequences: 27.5.1 eslint: 9.39.1(jiti@1.21.7) eslint-compat-utils: 0.6.5(eslint@9.39.1(jiti@1.21.7)) - eslint-json-compat-utils: 0.2.1(eslint@9.39.1(jiti@1.21.7))(jsonc-eslint-parser@2.4.1) + eslint-json-compat-utils: 0.2.1(eslint@9.39.1(jiti@1.21.7))(jsonc-eslint-parser@2.4.2) espree: 10.4.0 graphemer: 1.4.0 - jsonc-eslint-parser: 2.4.1 + jsonc-eslint-parser: 2.4.2 natural-compare: 1.4.0 synckit: 0.11.11 transitivePeerDependencies: @@ -14059,29 +14173,30 @@ snapshots: eslint-plugin-no-only-tests@3.3.0: {} - eslint-plugin-oxlint@1.31.0: + eslint-plugin-oxlint@1.32.0: dependencies: jsonc-parser: 3.3.1 eslint-plugin-perfectionist@4.15.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3): dependencies: - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) eslint: 9.39.1(jiti@1.21.7) natural-orderby: 5.0.0 transitivePeerDependencies: - supports-color - typescript - eslint-plugin-pnpm@1.3.0(eslint@9.39.1(jiti@1.21.7)): + eslint-plugin-pnpm@1.4.2(eslint@9.39.1(jiti@1.21.7)): dependencies: empathic: 2.0.0 eslint: 9.39.1(jiti@1.21.7) - jsonc-eslint-parser: 2.4.1 + jsonc-eslint-parser: 2.4.2 pathe: 2.0.3 - pnpm-workspace-yaml: 1.3.0 + pnpm-workspace-yaml: 1.4.2 tinyglobby: 0.2.15 - yaml-eslint-parser: 1.3.1 + yaml: 2.8.2 + yaml-eslint-parser: 1.3.2 eslint-plugin-react-debug@1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3): dependencies: @@ -14091,10 +14206,10 @@ snapshots: '@eslint-react/kit': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) '@eslint-react/shared': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) '@eslint-react/var': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/type-utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) eslint: 9.39.1(jiti@1.21.7) string-ts: 2.3.1 ts-pattern: 5.9.0 @@ -14111,9 +14226,9 @@ snapshots: '@eslint-react/kit': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) '@eslint-react/shared': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) '@eslint-react/var': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) compare-versions: 6.1.1 eslint: 9.39.1(jiti@1.21.7) string-ts: 2.3.1 @@ -14131,10 +14246,10 @@ snapshots: '@eslint-react/kit': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) '@eslint-react/shared': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) '@eslint-react/var': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/type-utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) eslint: 9.39.1(jiti@1.21.7) string-ts: 2.3.1 ts-pattern: 5.9.0 @@ -14155,10 +14270,10 @@ snapshots: '@eslint-react/kit': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) '@eslint-react/shared': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) '@eslint-react/var': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/type-utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) eslint: 9.39.1(jiti@1.21.7) string-ts: 2.3.1 ts-pattern: 5.9.0 @@ -14179,9 +14294,9 @@ snapshots: '@eslint-react/kit': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) '@eslint-react/shared': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) '@eslint-react/var': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) eslint: 9.39.1(jiti@1.21.7) string-ts: 2.3.1 ts-pattern: 5.9.0 @@ -14198,10 +14313,10 @@ snapshots: '@eslint-react/kit': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) '@eslint-react/shared': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) '@eslint-react/var': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/type-utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) compare-versions: 6.1.1 eslint: 9.39.1(jiti@1.21.7) is-immutable-type: 5.0.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) @@ -14238,11 +14353,11 @@ snapshots: semver: 7.7.2 typescript: 5.9.3 - eslint-plugin-storybook@9.1.16(eslint@9.39.1(jiti@1.21.7))(storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)))(typescript@5.9.3): + eslint-plugin-storybook@9.1.16(eslint@9.39.1(jiti@1.21.7))(storybook@9.1.13(@testing-library/dom@10.4.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) eslint: 9.39.1(jiti@1.21.7) - storybook: 9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)) + storybook: 9.1.13(@testing-library/dom@10.4.1) transitivePeerDependencies: - supports-color - typescript @@ -14259,7 +14374,7 @@ snapshots: eslint: 9.39.1(jiti@1.21.7) eslint-compat-utils: 0.6.5(eslint@9.39.1(jiti@1.21.7)) lodash: 4.17.21 - toml-eslint-parser: 0.10.0 + toml-eslint-parser: 0.10.1 transitivePeerDependencies: - supports-color @@ -14285,13 +14400,13 @@ snapshots: semver: 7.7.3 strip-indent: 4.1.1 - eslint-plugin-unused-imports@4.3.0(@typescript-eslint/eslint-plugin@8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7)): + eslint-plugin-unused-imports@4.3.0(@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7)): dependencies: eslint: 9.39.1(jiti@1.21.7) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - eslint-plugin-vue@10.6.2(@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@1.21.7)))(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@1.21.7))): + eslint-plugin-vue@10.6.2(@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@1.21.7)))(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@1.21.7))): dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) eslint: 9.39.1(jiti@1.21.7) @@ -14303,7 +14418,7 @@ snapshots: xml-name-validator: 4.0.0 optionalDependencies: '@stylistic/eslint-plugin': 5.6.1(eslint@9.39.1(jiti@1.21.7)) - '@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) eslint-plugin-yml@1.19.0(eslint@9.39.1(jiti@1.21.7)): dependencies: @@ -14313,7 +14428,7 @@ snapshots: eslint: 9.39.1(jiti@1.21.7) eslint-compat-utils: 0.6.5(eslint@9.39.1(jiti@1.21.7)) natural-compare: 1.4.0 - yaml-eslint-parser: 1.3.1 + yaml-eslint-parser: 1.3.2 transitivePeerDependencies: - supports-color @@ -14755,9 +14870,6 @@ snapshots: graphemer@1.4.0: {} - graphql@16.12.0: - optional: true - gzip-size@6.0.0: dependencies: duplexer: 0.1.2 @@ -14766,7 +14878,7 @@ snapshots: happy-dom@20.0.11: dependencies: - '@types/node': 20.19.25 + '@types/node': 20.19.26 '@types/whatwg-mimetype': 3.0.2 whatwg-mimetype: 3.0.0 @@ -14842,7 +14954,7 @@ snapshots: '@types/unist': 3.0.3 '@ungap/structured-clone': 1.3.0 hast-util-from-parse5: 8.0.3 - hast-util-to-parse5: 8.0.0 + hast-util-to-parse5: 8.0.1 html-void-elements: 3.0.0 mdast-util-to-hast: 13.2.1 parse5: 7.3.0 @@ -14893,12 +15005,12 @@ snapshots: transitivePeerDependencies: - supports-color - hast-util-to-parse5@8.0.0: + hast-util-to-parse5@8.0.1: dependencies: '@types/hast': 3.0.4 comma-separated-tokens: 2.0.3 devlop: 1.1.0 - property-information: 6.5.0 + property-information: 7.1.0 space-separated-tokens: 2.0.2 web-namespaces: 2.0.1 zwitch: 2.0.4 @@ -14932,9 +15044,6 @@ snapshots: he@1.2.0: {} - headers-polyfill@4.0.3: - optional: true - highlight.js@10.7.3: {} highlightjs-vue@1.0.0: {} @@ -15127,7 +15236,7 @@ snapshots: is-immutable-type@5.0.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3): dependencies: - '@typescript-eslint/type-utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) eslint: 9.39.1(jiti@1.21.7) ts-api-utils: 2.1.0(typescript@5.9.3) ts-declaration-location: 1.0.7(typescript@5.9.3) @@ -15586,7 +15695,7 @@ snapshots: json5@2.2.3: {} - jsonc-eslint-parser@2.4.1: + jsonc-eslint-parser@2.4.2: dependencies: acorn: 8.15.0 eslint-visitor-keys: 3.4.3 @@ -15607,7 +15716,7 @@ snapshots: jsx-ast-utils-x@0.1.0: {} - katex@0.16.25: + katex@0.16.27: dependencies: commander: 8.3.0 @@ -15621,7 +15730,7 @@ snapshots: kleur@4.1.5: {} - knip@5.71.0(@types/node@18.15.0)(typescript@5.9.3): + knip@5.72.0(@types/node@18.15.0)(typescript@5.9.3): dependencies: '@nodelib/fs.walk': 1.2.8 '@types/node': 18.15.0 @@ -15630,7 +15739,7 @@ snapshots: jiti: 2.6.1 js-yaml: 4.1.1 minimist: 1.2.8 - oxc-resolver: 11.14.2 + oxc-resolver: 11.15.0 picocolors: 1.1.1 picomatch: 4.0.3 smol-toml: 1.5.2 @@ -15638,7 +15747,7 @@ snapshots: typescript: 5.9.3 zod: 4.1.13 - ky@1.14.0: {} + ky@1.14.1: {} lamejs@1.2.1: dependencies: @@ -16033,8 +16142,8 @@ snapshots: d3-sankey: 0.12.3 dagre-d3-es: 7.0.11 dayjs: 1.11.19 - dompurify: 3.3.0 - katex: 0.16.25 + dompurify: 3.3.1 + katex: 0.16.27 khroma: 2.1.0 lodash-es: 4.17.21 marked: 15.0.12 @@ -16131,7 +16240,7 @@ snapshots: dependencies: '@types/katex': 0.16.7 devlop: 1.1.0 - katex: 0.16.25 + katex: 0.16.27 micromark-factory-space: 2.0.1 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 @@ -16399,35 +16508,6 @@ snapshots: ms@2.1.3: {} - msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3): - dependencies: - '@inquirer/confirm': 5.1.21(@types/node@18.15.0) - '@mswjs/interceptors': 0.40.0 - '@open-draft/deferred-promise': 2.2.0 - '@types/statuses': 2.0.6 - cookie: 1.1.1 - graphql: 16.12.0 - headers-polyfill: 4.0.3 - is-node-process: 1.2.0 - outvariant: 1.4.3 - path-to-regexp: 6.3.0 - picocolors: 1.1.1 - rettime: 0.7.0 - statuses: 2.0.2 - strict-event-emitter: 0.5.1 - tough-cookie: 6.0.0 - type-fest: 5.3.1 - until-async: 3.0.2 - yargs: 17.7.2 - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - '@types/node' - optional: true - - mute-stream@2.0.0: - optional: true - mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -16447,13 +16527,13 @@ snapshots: neo-async@2.6.2: {} - next-pwa@5.6.0(@babel/core@7.28.5)(@types/babel__core@7.20.5)(esbuild@0.25.0)(next@15.5.7(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2))(uglify-js@3.19.3)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)): + next-pwa@5.6.0(@babel/core@7.28.5)(@types/babel__core@7.20.5)(esbuild@0.25.0)(next@15.5.7(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.95.0))(uglify-js@3.19.3)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)): dependencies: babel-loader: 8.4.1(@babel/core@7.28.5)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) clean-webpack-plugin: 4.0.0(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) globby: 11.1.0 - next: 15.5.7(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2) - terser-webpack-plugin: 5.3.14(esbuild@0.25.0)(uglify-js@3.19.3)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) + next: 15.5.7(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.95.0) + terser-webpack-plugin: 5.3.15(esbuild@0.25.0)(uglify-js@3.19.3)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) workbox-webpack-plugin: 6.6.0(@types/babel__core@7.20.5)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) workbox-window: 6.6.0 transitivePeerDependencies: @@ -16470,11 +16550,11 @@ snapshots: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) - next@15.5.7(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2): + next@15.5.7(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.95.0): dependencies: '@next/env': 15.5.7 '@swc/helpers': 0.5.15 - caniuse-lite: 1.0.30001759 + caniuse-lite: 1.0.30001760 postcss: 8.4.31 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) @@ -16488,7 +16568,8 @@ snapshots: '@next/swc-linux-x64-musl': 15.5.7 '@next/swc-win32-arm64-msvc': 15.5.7 '@next/swc-win32-x64-msvc': 15.5.7 - sass: 1.94.2 + '@playwright/test': 1.57.0 + sass: 1.95.0 sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' @@ -16615,39 +16696,39 @@ snapshots: outvariant@1.4.3: {} - oxc-resolver@11.14.2: + oxc-resolver@11.15.0: optionalDependencies: - '@oxc-resolver/binding-android-arm-eabi': 11.14.2 - '@oxc-resolver/binding-android-arm64': 11.14.2 - '@oxc-resolver/binding-darwin-arm64': 11.14.2 - '@oxc-resolver/binding-darwin-x64': 11.14.2 - '@oxc-resolver/binding-freebsd-x64': 11.14.2 - '@oxc-resolver/binding-linux-arm-gnueabihf': 11.14.2 - '@oxc-resolver/binding-linux-arm-musleabihf': 11.14.2 - '@oxc-resolver/binding-linux-arm64-gnu': 11.14.2 - '@oxc-resolver/binding-linux-arm64-musl': 11.14.2 - '@oxc-resolver/binding-linux-ppc64-gnu': 11.14.2 - '@oxc-resolver/binding-linux-riscv64-gnu': 11.14.2 - '@oxc-resolver/binding-linux-riscv64-musl': 11.14.2 - '@oxc-resolver/binding-linux-s390x-gnu': 11.14.2 - '@oxc-resolver/binding-linux-x64-gnu': 11.14.2 - '@oxc-resolver/binding-linux-x64-musl': 11.14.2 - '@oxc-resolver/binding-openharmony-arm64': 11.14.2 - '@oxc-resolver/binding-wasm32-wasi': 11.14.2 - '@oxc-resolver/binding-win32-arm64-msvc': 11.14.2 - '@oxc-resolver/binding-win32-ia32-msvc': 11.14.2 - '@oxc-resolver/binding-win32-x64-msvc': 11.14.2 + '@oxc-resolver/binding-android-arm-eabi': 11.15.0 + '@oxc-resolver/binding-android-arm64': 11.15.0 + '@oxc-resolver/binding-darwin-arm64': 11.15.0 + '@oxc-resolver/binding-darwin-x64': 11.15.0 + '@oxc-resolver/binding-freebsd-x64': 11.15.0 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.15.0 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.15.0 + '@oxc-resolver/binding-linux-arm64-gnu': 11.15.0 + '@oxc-resolver/binding-linux-arm64-musl': 11.15.0 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.15.0 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.15.0 + '@oxc-resolver/binding-linux-riscv64-musl': 11.15.0 + '@oxc-resolver/binding-linux-s390x-gnu': 11.15.0 + '@oxc-resolver/binding-linux-x64-gnu': 11.15.0 + '@oxc-resolver/binding-linux-x64-musl': 11.15.0 + '@oxc-resolver/binding-openharmony-arm64': 11.15.0 + '@oxc-resolver/binding-wasm32-wasi': 11.15.0 + '@oxc-resolver/binding-win32-arm64-msvc': 11.15.0 + '@oxc-resolver/binding-win32-ia32-msvc': 11.15.0 + '@oxc-resolver/binding-win32-x64-msvc': 11.15.0 - oxlint@1.31.0: + oxlint@1.32.0: optionalDependencies: - '@oxlint/darwin-arm64': 1.31.0 - '@oxlint/darwin-x64': 1.31.0 - '@oxlint/linux-arm64-gnu': 1.31.0 - '@oxlint/linux-arm64-musl': 1.31.0 - '@oxlint/linux-x64-gnu': 1.31.0 - '@oxlint/linux-x64-musl': 1.31.0 - '@oxlint/win32-arm64': 1.31.0 - '@oxlint/win32-x64': 1.31.0 + '@oxlint/darwin-arm64': 1.32.0 + '@oxlint/darwin-x64': 1.32.0 + '@oxlint/linux-arm64-gnu': 1.32.0 + '@oxlint/linux-arm64-musl': 1.32.0 + '@oxlint/linux-x64-gnu': 1.32.0 + '@oxlint/linux-x64-musl': 1.32.0 + '@oxlint/win32-arm64': 1.32.0 + '@oxlint/win32-x64': 1.32.0 p-cancelable@2.1.1: {} @@ -16763,9 +16844,6 @@ snapshots: path-parse@1.0.7: {} - path-to-regexp@6.3.0: - optional: true - path-type@4.0.0: {} path2d@0.2.2: @@ -16841,7 +16919,7 @@ snapshots: pluralize@8.0.0: {} - pnpm-workspace-yaml@1.3.0: + pnpm-workspace-yaml@1.4.2: dependencies: yaml: 2.8.2 @@ -17008,8 +17086,6 @@ snapshots: dependencies: xtend: 4.0.2 - property-information@6.5.0: {} - property-information@7.1.0: {} public-encrypt@4.0.3: @@ -17122,7 +17198,7 @@ snapshots: react-fast-compare@3.2.2: {} - react-hook-form@7.67.0(react@19.2.1): + react-hook-form@7.68.0(react@19.2.1): dependencies: react: 19.2.1 @@ -17212,7 +17288,7 @@ snapshots: react-draggable: 4.4.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) tslib: 2.6.2 - react-scan@0.4.3(@types/react@19.2.7)(next@15.5.7(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(rollup@2.79.2): + react-scan@0.4.3(@types/react@19.2.7)(next@15.5.7(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.95.0))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(rollup@2.79.2): dependencies: '@babel/core': 7.28.5 '@babel/generator': 7.28.5 @@ -17222,9 +17298,9 @@ snapshots: '@pivanov/utils': 0.0.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@preact/signals': 1.3.2(preact@10.28.0) '@rollup/pluginutils': 5.3.0(rollup@2.79.2) - '@types/node': 20.19.25 + '@types/node': 20.19.26 bippy: 0.3.34(@types/react@19.2.7)(react@19.2.1) - esbuild: 0.25.0 + esbuild: 0.25.12 estree-walker: 3.0.3 kleur: 4.1.5 mri: 1.2.0 @@ -17234,7 +17310,7 @@ snapshots: react-dom: 19.2.1(react@19.2.1) tsx: 4.21.0 optionalDependencies: - next: 15.5.7(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.94.2) + next: 15.5.7(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(sass@1.95.0) unplugin: 2.1.0 transitivePeerDependencies: - '@types/react' @@ -17431,7 +17507,7 @@ snapshots: '@types/katex': 0.16.7 hast-util-from-html-isomorphic: 2.0.0 hast-util-to-text: 4.0.2 - katex: 0.16.25 + katex: 0.16.27 unist-util-visit-parents: 6.0.2 vfile: 6.0.3 @@ -17558,9 +17634,6 @@ snapshots: onetime: 7.0.0 signal-exit: 4.1.0 - rettime@0.7.0: - optional: true - reusify@1.1.0: {} rfdc@1.4.1: {} @@ -17616,14 +17689,14 @@ snapshots: safe-buffer@5.2.1: {} - sass-loader@16.0.6(sass@1.94.2)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)): + sass-loader@16.0.6(sass@1.95.0)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)): dependencies: neo-async: 2.6.2 optionalDependencies: - sass: 1.94.2 + sass: 1.95.0 webpack: 5.103.0(esbuild@0.25.0)(uglify-js@3.19.3) - sass@1.94.2: + sass@1.95.0: dependencies: chokidar: 4.0.3 immutable: 5.1.4 @@ -17839,16 +17912,13 @@ snapshots: state-local@1.0.7: {} - statuses@2.0.2: - optional: true - - storybook@9.1.13(@testing-library/dom@10.4.1)(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)): + storybook@9.1.13(@testing-library/dom@10.4.1): dependencies: '@storybook/global': 5.0.0 '@testing-library/jest-dom': 6.9.1 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(msw@2.12.4(@types/node@18.15.0)(typescript@5.9.3)) + '@vitest/mocker': 3.2.4 '@vitest/spy': 3.2.4 better-opn: 3.0.2 esbuild: 0.25.0 @@ -18003,9 +18073,6 @@ snapshots: tabbable@6.3.0: {} - tagged-tag@1.0.0: - optional: true - tailwind-merge@2.6.0: {} tailwindcss@3.4.18(tsx@4.21.0)(yaml@2.8.2): @@ -18064,7 +18131,7 @@ snapshots: type-fest: 0.16.0 unique-string: 2.0.0 - terser-webpack-plugin@5.3.14(esbuild@0.25.0)(uglify-js@3.19.3)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)): + terser-webpack-plugin@5.3.15(esbuild@0.25.0)(uglify-js@3.19.3)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 @@ -18136,17 +18203,12 @@ snapshots: toggle-selection@1.0.6: {} - toml-eslint-parser@0.10.0: + toml-eslint-parser@0.10.1: dependencies: eslint-visitor-keys: 3.4.3 totalist@3.0.1: {} - tough-cookie@6.0.0: - dependencies: - tldts: 7.0.19 - optional: true - tr46@1.0.1: dependencies: punycode: 2.3.1 @@ -18211,7 +18273,7 @@ snapshots: tsx@4.21.0: dependencies: - esbuild: 0.25.0 + esbuild: 0.25.12 get-tsconfig: 4.13.0 optionalDependencies: fsevents: 2.3.3 @@ -18237,11 +18299,6 @@ snapshots: type-fest@4.2.0: {} - type-fest@5.3.1: - dependencies: - tagged-tag: 1.0.0 - optional: true - typescript@5.9.3: {} ufo@1.6.1: {} @@ -18329,14 +18386,11 @@ snapshots: webpack-virtual-modules: 0.6.2 optional: true - until-async@3.0.2: - optional: true - upath@1.2.0: {} - update-browserslist-db@1.1.4(browserslist@4.28.0): + update-browserslist-db@1.2.2(browserslist@4.28.1): dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 escalade: 3.2.0 picocolors: 1.1.1 @@ -18537,7 +18591,7 @@ snapshots: '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.15.0 acorn-import-phases: 1.0.4(acorn@8.15.0) - browserslist: 4.28.0 + browserslist: 4.28.1 chrome-trace-event: 1.0.4 enhanced-resolve: 5.18.3 es-module-lexer: 1.7.0 @@ -18551,7 +18605,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.0 - terser-webpack-plugin: 5.3.14(esbuild@0.25.0)(uglify-js@3.19.3)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) + terser-webpack-plugin: 5.3.15(esbuild@0.25.0)(uglify-js@3.19.3)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: @@ -18698,13 +18752,6 @@ snapshots: '@types/trusted-types': 2.0.7 workbox-core: 6.6.0 - wrap-ansi@6.2.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - optional: true - wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -18736,7 +18783,7 @@ snapshots: yallist@3.1.1: {} - yaml-eslint-parser@1.3.1: + yaml-eslint-parser@1.3.2: dependencies: eslint-visitor-keys: 3.4.3 yaml: 2.8.2 @@ -18767,9 +18814,6 @@ snapshots: yocto-queue@1.2.2: {} - yoctocolors-cjs@2.1.3: - optional: true - zen-observable-ts@1.1.0: dependencies: '@types/zen-observable': 0.8.3 diff --git a/web/testing/analyze-component.js b/web/testing/analyze-component.js index bf682ffa67..91e36af6f1 100755 --- a/web/testing/analyze-component.js +++ b/web/testing/analyze-component.js @@ -2,6 +2,9 @@ const fs = require('node:fs') const path = require('node:path') +const { Linter } = require('eslint') +const sonarPlugin = require('eslint-plugin-sonarjs') +const tsParser = require('@typescript-eslint/parser') // ============================================================================ // Simple Analyzer @@ -12,7 +15,11 @@ class ComponentAnalyzer { const resolvedPath = absolutePath ?? path.resolve(process.cwd(), filePath) const fileName = path.basename(filePath, path.extname(filePath)) const lineCount = code.split('\n').length - const complexity = this.calculateComplexity(code, lineCount) + + // Calculate complexity metrics + const { total: rawComplexity, max: rawMaxComplexity } = this.calculateCognitiveComplexity(code) + const complexity = this.normalizeComplexity(rawComplexity) + const maxComplexity = this.normalizeComplexity(rawMaxComplexity) // Count usage references (may take a few seconds) const usageCount = this.countUsageReferences(filePath, resolvedPath) @@ -41,6 +48,9 @@ class ComponentAnalyzer { hasReactQuery: code.includes('useQuery') || code.includes('useMutation'), hasAhooks: code.includes("from 'ahooks'"), complexity, + maxComplexity, + rawComplexity, + rawMaxComplexity, lineCount, usageCount, priority, @@ -64,193 +74,96 @@ class ComponentAnalyzer { } /** - * Calculate component complexity score - * Based on Cognitive Complexity + React-specific metrics + * Calculate Cognitive Complexity using SonarJS ESLint plugin + * Reference: https://www.sonarsource.com/blog/5-clean-code-tips-for-reducing-cognitive-complexity/ * - * Score Ranges: - * 0-10: 🟢 Simple (5-10 min to test) - * 11-30: 🟡 Medium (15-30 min to test) - * 31-50: 🟠 Complex (30-60 min to test) - * 51+: 🔴 Very Complex (60+ min, consider splitting) + * Returns raw (unnormalized) complexity values: + * - total: sum of all functions' complexity in the file + * - max: highest single function complexity in the file + * + * Raw Score Thresholds (per function): + * 0-15: Simple | 16-30: Medium | 31-50: Complex | 51+: Very Complex + * + * @returns {{ total: number, max: number }} raw total and max complexity */ - calculateComplexity(code, lineCount) { - let score = 0 + calculateCognitiveComplexity(code) { + const linter = new Linter() + const baseConfig = { + languageOptions: { + parser: tsParser, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { jsx: true }, + }, + }, + plugins: { sonarjs: sonarPlugin }, + } - const count = pattern => this.countMatches(code, pattern) + try { + // Get total complexity using 'metric' option (more stable) + const totalConfig = { + ...baseConfig, + rules: { 'sonarjs/cognitive-complexity': ['error', 0, 'metric'] }, + } + const totalMessages = linter.verify(code, totalConfig) + const totalMsg = totalMessages.find( + msg => msg.ruleId === 'sonarjs/cognitive-complexity' + && msg.messageId === 'fileComplexity', + ) + const total = totalMsg ? parseInt(totalMsg.message, 10) : 0 - // ===== React Hooks (State Management Complexity) ===== - const stateHooks = count(/useState/g) - const reducerHooks = count(/useReducer/g) - const effectHooks = count(/useEffect/g) - const callbackHooks = count(/useCallback/g) - const memoHooks = count(/useMemo/g) - const refHooks = count(/useRef/g) - const imperativeHandleHooks = count(/useImperativeHandle/g) + // Get max function complexity by analyzing each function + const maxConfig = { + ...baseConfig, + rules: { 'sonarjs/cognitive-complexity': ['error', 0] }, + } + const maxMessages = linter.verify(code, maxConfig) + let max = 0 + const complexityPattern = /reduce its Cognitive Complexity from (\d+)/ - const builtinHooks = stateHooks + reducerHooks + effectHooks - + callbackHooks + memoHooks + refHooks + imperativeHandleHooks - const totalHooks = count(/use[A-Z]\w+/g) - const customHooks = Math.max(0, totalHooks - builtinHooks) + maxMessages.forEach((msg) => { + if (msg.ruleId === 'sonarjs/cognitive-complexity') { + const match = msg.message.match(complexityPattern) + if (match && match[1]) + max = Math.max(max, parseInt(match[1], 10)) + } + }) - score += stateHooks * 5 // Each state +5 (need to test state changes) - score += reducerHooks * 6 // Each reducer +6 (complex state management) - score += effectHooks * 6 // Each effect +6 (need to test deps & cleanup) - score += callbackHooks * 2 // Each callback +2 - score += memoHooks * 2 // Each memo +2 - score += refHooks * 1 // Each ref +1 - score += imperativeHandleHooks * 4 // Each imperative handle +4 (exposes methods) - score += customHooks * 3 // Each custom hook +3 - - // ===== Control Flow Complexity (Cyclomatic Complexity) ===== - score += count(/if\s*\(/g) * 2 // if statement - score += count(/else\s+if/g) * 2 // else if - score += count(/\?\s*[^:]+\s*:/g) * 1 // ternary operator - score += count(/switch\s*\(/g) * 3 // switch - score += count(/case\s+/g) * 1 // case branch - score += count(/&&/g) * 1 // logical AND - score += count(/\|\|/g) * 1 // logical OR - score += count(/\?\?/g) * 1 // nullish coalescing - - // ===== Loop Complexity ===== - score += count(/\.map\(/g) * 2 // map - score += count(/\.filter\(/g) * 1 // filter - score += count(/\.reduce\(/g) * 3 // reduce (complex) - score += count(/for\s*\(/g) * 2 // for loop - score += count(/while\s*\(/g) * 3 // while loop - - // ===== Props and Events Complexity ===== - // Count unique props from interface/type definitions only (avoid duplicates) - const propsCount = this.countUniqueProps(code) - score += Math.floor(propsCount / 2) // Every 2 props +1 - - // Count unique event handler names (avoid duplicates from type defs, params, usage) - const uniqueEventHandlers = this.countUniqueEventHandlers(code) - score += uniqueEventHandlers * 2 // Each unique event handler +2 - - // ===== API Call Complexity ===== - score += count(/fetch\(/g) * 4 // fetch - score += count(/axios\./g) * 4 // axios - score += count(/useSWR/g) * 4 // SWR - score += count(/useQuery/g) * 4 // React Query - score += count(/\.then\(/g) * 2 // Promise - score += count(/await\s+/g) * 2 // async/await - - // ===== Third-party Library Integration ===== - // Only count complex UI libraries that require integration testing - // Data fetching libs (swr, react-query, ahooks) don't add complexity - // because they are already well-tested; we only need to mock them - const complexUILibs = [ - { pattern: /reactflow|ReactFlow/, weight: 15 }, - { pattern: /@monaco-editor/, weight: 12 }, - { pattern: /echarts/, weight: 8 }, - { pattern: /lexical/, weight: 10 }, - ] - - complexUILibs.forEach(({ pattern, weight }) => { - if (pattern.test(code)) score += weight - }) - - // ===== Code Size Complexity ===== - if (lineCount > 500) score += 10 - else if (lineCount > 300) score += 6 - else if (lineCount > 150) score += 3 - - // ===== Nesting Depth (deep nesting reduces readability) ===== - const maxNesting = this.calculateNestingDepth(code) - score += Math.max(0, (maxNesting - 3)) * 2 // Over 3 levels, +2 per level - - // ===== Context and Global State ===== - score += count(/useContext/g) * 3 - score += count(/useStore|useAppStore/g) * 4 - score += count(/zustand|redux/g) * 3 - - // ===== React Advanced Features ===== - score += count(/React\.memo|memo\(/g) * 2 // Component memoization - score += count(/forwardRef/g) * 3 // Ref forwarding - score += count(/Suspense/g) * 4 // Suspense boundaries - score += count(/\blazy\(/g) * 3 // Lazy loading - score += count(/createPortal/g) * 3 // Portal rendering - - return Math.min(score, 100) // Max 100 points + return { total, max } + } + catch { + return { total: 0, max: 0 } + } } /** - * Calculate maximum nesting depth + * Normalize cognitive complexity to 0-100 scale + * + * Mapping (aligned with SonarJS thresholds): + * Raw 0-15 (Simple) -> Normalized 0-25 + * Raw 16-30 (Medium) -> Normalized 25-50 + * Raw 31-50 (Complex) -> Normalized 50-75 + * Raw 51+ (Very Complex) -> Normalized 75-100 (asymptotic) */ - calculateNestingDepth(code) { - let maxDepth = 0 - let currentDepth = 0 - let inString = false - let stringChar = '' - let escapeNext = false - let inSingleLineComment = false - let inMultiLineComment = false - - for (let i = 0; i < code.length; i++) { - const char = code[i] - const nextChar = code[i + 1] - - if (inSingleLineComment) { - if (char === '\n') inSingleLineComment = false - continue - } - - if (inMultiLineComment) { - if (char === '*' && nextChar === '/') { - inMultiLineComment = false - i++ - } - continue - } - - if (inString) { - if (escapeNext) { - escapeNext = false - continue - } - - if (char === '\\') { - escapeNext = true - continue - } - - if (char === stringChar) { - inString = false - stringChar = '' - } - continue - } - - if (char === '/' && nextChar === '/') { - inSingleLineComment = true - i++ - continue - } - - if (char === '/' && nextChar === '*') { - inMultiLineComment = true - i++ - continue - } - - if (char === '"' || char === '\'' || char === '`') { - inString = true - stringChar = char - continue - } - - if (char === '{') { - currentDepth++ - maxDepth = Math.max(maxDepth, currentDepth) - continue - } - - if (char === '}') { - currentDepth = Math.max(currentDepth - 1, 0) - } + normalizeComplexity(rawComplexity) { + if (rawComplexity <= 15) { + // Linear: 0-15 -> 0-25 + return Math.round((rawComplexity / 15) * 25) + } + else if (rawComplexity <= 30) { + // Linear: 16-30 -> 25-50 + return Math.round(25 + ((rawComplexity - 15) / 15) * 25) + } + else if (rawComplexity <= 50) { + // Linear: 31-50 -> 50-75 + return Math.round(50 + ((rawComplexity - 30) / 20) * 25) + } + else { + // Asymptotic: 51+ -> 75-100 + // Formula ensures score approaches but never exceeds 100 + return Math.round(75 + 25 * (1 - 1 / (1 + (rawComplexity - 50) / 100))) } - - return maxDepth } /** @@ -379,86 +292,41 @@ class ComponentAnalyzer { return true } - countMatches(code, pattern) { - const matches = code.match(pattern) - return matches ? matches.length : 0 - } - - /** - * Count unique props from interface/type definitions - * Only counts props defined in type/interface blocks, not usage - */ - countUniqueProps(code) { - const uniqueProps = new Set() - - // Match interface or type definition blocks - const typeBlockPattern = /(?:interface|type)\s+\w*Props[^{]*\{([^}]+)\}/g - let match - - while ((match = typeBlockPattern.exec(code)) !== null) { - const blockContent = match[1] - // Match prop names (word followed by optional ? and :) - const propPattern = /(\w+)\s*\??:/g - let propMatch - while ((propMatch = propPattern.exec(blockContent)) !== null) { - uniqueProps.add(propMatch[1]) - } - } - - return Math.min(uniqueProps.size, 20) // Max 20 props - } - - /** - * Count unique event handler names (on[A-Z]...) - * Avoids counting the same handler multiple times across type defs, params, and usage - */ - countUniqueEventHandlers(code) { - const uniqueHandlers = new Set() - const pattern = /on[A-Z]\w+/g - let match - - while ((match = pattern.exec(code)) !== null) { - uniqueHandlers.add(match[0]) - } - - return uniqueHandlers.size - } - static escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') } /** - * Calculate test priority based on complexity and usage + * Calculate test priority based on cognitive complexity and usage * - * Priority Score = Complexity Score + Usage Score - * - Complexity: 0-100 - * - Usage: 0-50 - * - Total: 0-150 + * Priority Score = 0.7 * Complexity + 0.3 * Usage Score (all normalized to 0-100) + * - Complexity Score: 0-100 (normalized from SonarJS) + * - Usage Score: 0-100 (based on reference count) * - * Priority Levels: - * - 0-30: Low - * - 31-70: Medium - * - 71-100: High - * - 100+: Critical + * Priority Levels (0-100): + * - 0-25: 🟢 LOW + * - 26-50: 🟡 MEDIUM + * - 51-75: 🟠 HIGH + * - 76-100: 🔴 CRITICAL */ calculateTestPriority(complexity, usageCount) { const complexityScore = complexity - // Usage score calculation + // Normalize usage score to 0-100 let usageScore if (usageCount === 0) usageScore = 0 else if (usageCount <= 5) - usageScore = 10 - else if (usageCount <= 20) usageScore = 20 + else if (usageCount <= 20) + usageScore = 40 else if (usageCount <= 50) - usageScore = 35 + usageScore = 70 else - usageScore = 50 + usageScore = 100 - const totalScore = complexityScore + usageScore + // Weighted average: complexity (70%) + usage (30%) + const totalScore = Math.round(0.7 * complexityScore + 0.3 * usageScore) return { score: totalScore, @@ -469,12 +337,12 @@ class ComponentAnalyzer { } /** - * Get priority level based on score + * Get priority level based on score (0-100 scale) */ getPriorityLevel(score) { - if (score > 100) return '🔴 CRITICAL' - if (score > 70) return '🟠 HIGH' - if (score > 30) return '🟡 MEDIUM' + if (score > 75) return '🔴 CRITICAL' + if (score > 50) return '🟠 HIGH' + if (score > 25) return '🟡 MEDIUM' return '🟢 LOW' } } @@ -498,10 +366,11 @@ class TestPromptBuilder { 📊 Component Analysis: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -Type: ${analysis.type} -Complexity: ${analysis.complexity} ${this.getComplexityLevel(analysis.complexity)} -Lines: ${analysis.lineCount} -Usage: ${analysis.usageCount} reference${analysis.usageCount !== 1 ? 's' : ''} +Type: ${analysis.type} +Total Complexity: ${analysis.complexity}/100 ${this.getComplexityLevel(analysis.complexity)} +Max Func Complexity: ${analysis.maxComplexity}/100 ${this.getComplexityLevel(analysis.maxComplexity)} +Lines: ${analysis.lineCount} +Usage: ${analysis.usageCount} reference${analysis.usageCount !== 1 ? 's' : ''} Test Priority: ${analysis.priority.score} ${analysis.priority.level} Features Detected: @@ -549,10 +418,10 @@ Create the test file at: ${testPath} } getComplexityLevel(score) { - // Aligned with testing.md guidelines - if (score <= 10) return '🟢 Simple' - if (score <= 30) return '🟡 Medium' - if (score <= 50) return '🟠 Complex' + // Normalized complexity thresholds (0-100 scale) + if (score <= 25) return '🟢 Simple' + if (score <= 50) return '🟡 Medium' + if (score <= 75) return '🟠 Complex' return '🔴 Very Complex' } @@ -605,20 +474,31 @@ Create the test file at: ${testPath} } // ===== Complexity Warning ===== - if (analysis.complexity > 50) { - guidelines.push('🔴 VERY COMPLEX component detected. Consider:') + if (analysis.complexity > 75) { + guidelines.push(`🔴 HIGH Total Complexity (${analysis.complexity}/100). Consider:`) guidelines.push(' - Splitting component into smaller pieces before testing') guidelines.push(' - Creating integration tests for complex workflows') guidelines.push(' - Using test.each() for data-driven tests') - guidelines.push(' - Adding performance benchmarks') } - else if (analysis.complexity > 30) { - guidelines.push('⚠️ This is a COMPLEX component. Consider:') + else if (analysis.complexity > 50) { + guidelines.push(`⚠️ MODERATE Total Complexity (${analysis.complexity}/100). Consider:`) guidelines.push(' - Breaking tests into multiple describe blocks') guidelines.push(' - Testing integration scenarios') guidelines.push(' - Grouping related test cases') } + // ===== Max Function Complexity Warning ===== + if (analysis.maxComplexity > 75) { + guidelines.push(`🔴 HIGH Single Function Complexity (max: ${analysis.maxComplexity}/100). Consider:`) + guidelines.push(' - Breaking down the complex function into smaller helpers') + guidelines.push(' - Extracting logic into custom hooks or utility functions') + } + else if (analysis.maxComplexity > 50) { + guidelines.push(`⚠️ MODERATE Single Function Complexity (max: ${analysis.maxComplexity}/100). Consider:`) + guidelines.push(' - Simplifying conditional logic') + guidelines.push(' - Using early returns to reduce nesting') + } + // ===== State Management ===== if (analysis.hasState && analysis.hasEffects) { guidelines.push('🔄 State + Effects detected:') @@ -976,7 +856,7 @@ function main() { // Check if component is too complex - suggest refactoring instead of testing // Skip this check in JSON mode to always output analysis result - if (!isReviewMode && !isJsonMode && (analysis.complexity > 50 || analysis.lineCount > 300)) { + if (!isReviewMode && !isJsonMode && (analysis.complexity > 75 || analysis.lineCount > 300)) { console.log(` ╔════════════════════════════════════════════════════════════════════════════╗ ║ ⚠️ COMPONENT TOO COMPLEX TO TEST ║ @@ -987,8 +867,9 @@ function main() { 📊 Component Metrics: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -Complexity: ${analysis.complexity} ${analysis.complexity > 50 ? '🔴 TOO HIGH' : '⚠️ WARNING'} -Lines: ${analysis.lineCount} ${analysis.lineCount > 300 ? '🔴 TOO LARGE' : '⚠️ WARNING'} +Total Complexity: ${analysis.complexity}/100 ${analysis.complexity > 75 ? '🔴 TOO HIGH' : analysis.complexity > 50 ? '⚠️ WARNING' : '🟢 OK'} +Max Func Complexity: ${analysis.maxComplexity}/100 ${analysis.maxComplexity > 75 ? '🔴 TOO HIGH' : analysis.maxComplexity > 50 ? '⚠️ WARNING' : '🟢 OK'} +Lines: ${analysis.lineCount} ${analysis.lineCount > 300 ? '🔴 TOO LARGE' : '🟢 OK'} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🚫 RECOMMENDATION: REFACTOR BEFORE TESTING @@ -1017,7 +898,7 @@ This component is too complex to test effectively. Please consider: - Tests will be easier to write and maintain 💡 TIP: Aim for components with: - - Complexity score < 30 (preferably < 20) + - Cognitive Complexity < 50/100 (preferably < 25/100) - Line count < 300 (preferably < 200) - Single responsibility principle From 784008997ba3fa874e457401b761db67f223a69c Mon Sep 17 00:00:00 2001 From: Jyong <76649700+JohnJyong@users.noreply.github.com> Date: Wed, 10 Dec 2025 18:45:43 +0800 Subject: [PATCH 05/28] fix parent-child check when child chunk is not exist (#29426) --- api/core/rag/datasource/retrieval_service.py | 19 ++++++++++++++----- api/services/dataset_service.py | 2 ++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/api/core/rag/datasource/retrieval_service.py b/api/core/rag/datasource/retrieval_service.py index e4ca25b46b..a139fba4d0 100644 --- a/api/core/rag/datasource/retrieval_service.py +++ b/api/core/rag/datasource/retrieval_service.py @@ -451,12 +451,21 @@ class RetrievalService: "position": child_chunk.position, "score": document.metadata.get("score", 0.0), } - segment_child_map[segment.id]["child_chunks"].append(child_chunk_detail) - segment_child_map[segment.id]["max_score"] = max( - segment_child_map[segment.id]["max_score"], document.metadata.get("score", 0.0) - ) + if segment.id in segment_child_map: + segment_child_map[segment.id]["child_chunks"].append(child_chunk_detail) + segment_child_map[segment.id]["max_score"] = max( + segment_child_map[segment.id]["max_score"], document.metadata.get("score", 0.0) + ) + else: + segment_child_map[segment.id] = { + "max_score": document.metadata.get("score", 0.0), + "child_chunks": [child_chunk_detail], + } if attachment_info: - segment_file_map[segment.id].append(attachment_info) + if segment.id in segment_file_map: + segment_file_map[segment.id].append(attachment_info) + else: + segment_file_map[segment.id] = [attachment_info] else: # Handle normal documents segment = None diff --git a/api/services/dataset_service.py b/api/services/dataset_service.py index 00f06e9405..7841b8b33d 100644 --- a/api/services/dataset_service.py +++ b/api/services/dataset_service.py @@ -673,6 +673,8 @@ class DatasetService: Returns: str: Action to perform ('add', 'remove', 'update', or None) """ + if "indexing_technique" not in data: + return None if dataset.indexing_technique != data["indexing_technique"]: if data["indexing_technique"] == "economy": # Remove embedding model configuration for economy mode From ea063a1139061f0b9754e177cc009c70d8eefd93 Mon Sep 17 00:00:00 2001 From: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:04:34 +0800 Subject: [PATCH 06/28] fix(i18n): remove unused credentialSelector translations from dataset-pipeline files (#29423) --- web/i18n/de-DE/dataset-pipeline.ts | 3 --- web/i18n/es-ES/dataset-pipeline.ts | 3 --- web/i18n/fa-IR/dataset-pipeline.ts | 3 --- web/i18n/fr-FR/dataset-pipeline.ts | 3 --- web/i18n/hi-IN/dataset-pipeline.ts | 3 --- web/i18n/id-ID/dataset-pipeline.ts | 3 --- web/i18n/it-IT/dataset-pipeline.ts | 3 --- web/i18n/ko-KR/dataset-pipeline.ts | 3 --- web/i18n/pl-PL/dataset-pipeline.ts | 3 --- web/i18n/pt-BR/dataset-pipeline.ts | 3 --- web/i18n/ro-RO/dataset-pipeline.ts | 3 --- web/i18n/ru-RU/dataset-pipeline.ts | 3 --- web/i18n/sl-SI/dataset-pipeline.ts | 3 --- web/i18n/th-TH/dataset-pipeline.ts | 3 --- web/i18n/tr-TR/dataset-pipeline.ts | 3 --- web/i18n/uk-UA/dataset-pipeline.ts | 3 --- web/i18n/vi-VN/dataset-pipeline.ts | 3 --- web/i18n/zh-Hant/dataset-pipeline.ts | 3 --- 18 files changed, 54 deletions(-) diff --git a/web/i18n/de-DE/dataset-pipeline.ts b/web/i18n/de-DE/dataset-pipeline.ts index 4198a7435a..7ae47383cc 100644 --- a/web/i18n/de-DE/dataset-pipeline.ts +++ b/web/i18n/de-DE/dataset-pipeline.ts @@ -137,9 +137,6 @@ const translation = { notConnected: '{{name}} ist nicht verbunden', notConnectedTip: 'Um mit {{name}} zu synchronisieren, muss zuerst eine Verbindung zu {{name}} hergestellt werden.', }, - credentialSelector: { - name: '{{credentialName}}\'s {{pluginName}}', - }, conversion: { confirm: { title: 'Bestätigung', diff --git a/web/i18n/es-ES/dataset-pipeline.ts b/web/i18n/es-ES/dataset-pipeline.ts index 74c65177f2..fc182179af 100644 --- a/web/i18n/es-ES/dataset-pipeline.ts +++ b/web/i18n/es-ES/dataset-pipeline.ts @@ -137,9 +137,6 @@ const translation = { notConnected: '{{name}} no está conectado', notConnectedTip: 'Para sincronizar con {{name}}, primero se debe establecer conexión con {{name}}.', }, - credentialSelector: { - name: '{{credentialName}} de {{pluginName}}', - }, conversion: { confirm: { title: 'Confirmación', diff --git a/web/i18n/fa-IR/dataset-pipeline.ts b/web/i18n/fa-IR/dataset-pipeline.ts index 407f6d162c..709a616a75 100644 --- a/web/i18n/fa-IR/dataset-pipeline.ts +++ b/web/i18n/fa-IR/dataset-pipeline.ts @@ -137,9 +137,6 @@ const translation = { notConnected: '{{name}} متصل نیست', notConnectedTip: 'برای همگام‌سازی با {{name}}، ابتدا باید اتصال به {{name}} برقرار شود.', }, - credentialSelector: { - name: '{{pluginName}} {{credentialName}}', - }, conversion: { confirm: { title: 'تایید', diff --git a/web/i18n/fr-FR/dataset-pipeline.ts b/web/i18n/fr-FR/dataset-pipeline.ts index c206fa7430..aae98f3d80 100644 --- a/web/i18n/fr-FR/dataset-pipeline.ts +++ b/web/i18n/fr-FR/dataset-pipeline.ts @@ -137,9 +137,6 @@ const translation = { notConnected: '{{name}} n\'est pas connecté', notConnectedTip: 'Pour se synchroniser avec {{name}}, une connexion à {{name}} doit d\'abord être établie.', }, - credentialSelector: { - name: '{{credentialName}} de {{pluginName}}', - }, conversion: { confirm: { title: 'Confirmation', diff --git a/web/i18n/hi-IN/dataset-pipeline.ts b/web/i18n/hi-IN/dataset-pipeline.ts index f7f7bc42bf..c01d0174ff 100644 --- a/web/i18n/hi-IN/dataset-pipeline.ts +++ b/web/i18n/hi-IN/dataset-pipeline.ts @@ -137,9 +137,6 @@ const translation = { notConnected: '{{name}} कनेक्ट नहीं है', notConnectedTip: '{{name}} के साथ सिंक करने के लिए, पहले {{name}} से कनेक्शन स्थापित करना आवश्यक है।', }, - credentialSelector: { - name: '{{credentialName}} का {{pluginName}}', - }, conversion: { confirm: { title: 'पुष्टि', diff --git a/web/i18n/id-ID/dataset-pipeline.ts b/web/i18n/id-ID/dataset-pipeline.ts index 993bf79203..c3c2b04e15 100644 --- a/web/i18n/id-ID/dataset-pipeline.ts +++ b/web/i18n/id-ID/dataset-pipeline.ts @@ -137,9 +137,6 @@ const translation = { notConnected: '{{name}} tidak terhubung', notConnectedTip: 'Untuk menyinkronkan dengan {{name}}, koneksi ke {{name}} harus dibuat terlebih dahulu.', }, - credentialSelector: { - name: '{{credentialName}}\'s {{pluginName}}', - }, conversion: { confirm: { title: 'Konfirmasi', diff --git a/web/i18n/it-IT/dataset-pipeline.ts b/web/i18n/it-IT/dataset-pipeline.ts index acf8859db1..ec9fdf4743 100644 --- a/web/i18n/it-IT/dataset-pipeline.ts +++ b/web/i18n/it-IT/dataset-pipeline.ts @@ -137,9 +137,6 @@ const translation = { notConnected: '{{name}} non è connesso', notConnectedTip: 'Per sincronizzarsi con {{name}}, è necessario prima stabilire la connessione a {{name}}.', }, - credentialSelector: { - name: '{{credentialName}}\'s {{pluginName}}', - }, conversion: { confirm: { content: 'Questa azione è permanente. Non sarà possibile ripristinare il metodo precedente. Si prega di confermare per convertire.', diff --git a/web/i18n/ko-KR/dataset-pipeline.ts b/web/i18n/ko-KR/dataset-pipeline.ts index f6517ea192..d16e56736e 100644 --- a/web/i18n/ko-KR/dataset-pipeline.ts +++ b/web/i18n/ko-KR/dataset-pipeline.ts @@ -137,9 +137,6 @@ const translation = { notConnected: '{{name}}가 연결되어 있지 않습니다', notConnectedTip: '{{name}}와(과) 동기화하려면 먼저 {{name}}에 연결해야 합니다.', }, - credentialSelector: { - name: '{{credentialName}}의 {{pluginName}}', - }, conversion: { confirm: { title: '확인', diff --git a/web/i18n/pl-PL/dataset-pipeline.ts b/web/i18n/pl-PL/dataset-pipeline.ts index ec33211da3..b32a6e9a3d 100644 --- a/web/i18n/pl-PL/dataset-pipeline.ts +++ b/web/i18n/pl-PL/dataset-pipeline.ts @@ -137,9 +137,6 @@ const translation = { notConnected: '{{name}} nie jest połączony', notConnectedTip: 'Aby zsynchronizować się z {{name}}, najpierw należy nawiązać połączenie z {{name}}.', }, - credentialSelector: { - name: '{{credentialName}}\'s {{pluginName}}', - }, conversion: { confirm: { title: 'Potwierdzenie', diff --git a/web/i18n/pt-BR/dataset-pipeline.ts b/web/i18n/pt-BR/dataset-pipeline.ts index 0348ce70e3..c3b737644a 100644 --- a/web/i18n/pt-BR/dataset-pipeline.ts +++ b/web/i18n/pt-BR/dataset-pipeline.ts @@ -137,9 +137,6 @@ const translation = { notConnected: '{{name}} não está conectado', notConnectedTip: 'Para sincronizar com {{name}}, a conexão com {{name}} deve ser estabelecida primeiro.', }, - credentialSelector: { - name: '{{credentialName}} de {{pluginName}}', - }, conversion: { confirm: { title: 'Confirmação', diff --git a/web/i18n/ro-RO/dataset-pipeline.ts b/web/i18n/ro-RO/dataset-pipeline.ts index 947e52f2ef..3f9fe54c52 100644 --- a/web/i18n/ro-RO/dataset-pipeline.ts +++ b/web/i18n/ro-RO/dataset-pipeline.ts @@ -137,9 +137,6 @@ const translation = { notConnected: '{{name}} nu este conectat', notConnectedTip: 'Pentru a sincroniza cu {{name}}, trebuie mai întâi să se stabilească conexiunea cu {{name}}.', }, - credentialSelector: { - name: '{{pluginName}} al/a lui {{credentialName}}', - }, conversion: { confirm: { title: 'Confirmare', diff --git a/web/i18n/ru-RU/dataset-pipeline.ts b/web/i18n/ru-RU/dataset-pipeline.ts index 205de9f790..6fee138fc6 100644 --- a/web/i18n/ru-RU/dataset-pipeline.ts +++ b/web/i18n/ru-RU/dataset-pipeline.ts @@ -137,9 +137,6 @@ const translation = { notConnected: '{{name}} не подключен', notConnectedTip: 'Чтобы синхронизироваться с {{name}}, сначала необходимо установить соединение с {{name}}.', }, - credentialSelector: { - name: '{{credentialName}}\'s {{pluginName}}', - }, conversion: { confirm: { title: 'Подтверждение', diff --git a/web/i18n/sl-SI/dataset-pipeline.ts b/web/i18n/sl-SI/dataset-pipeline.ts index 25cf0d06b4..ae43d6fd2d 100644 --- a/web/i18n/sl-SI/dataset-pipeline.ts +++ b/web/i18n/sl-SI/dataset-pipeline.ts @@ -137,9 +137,6 @@ const translation = { notConnected: '{{name}} ni povezan', notConnectedTip: 'Za sinhronizacijo z {{name}} je treba najprej vzpostaviti povezavo z {{name}}.', }, - credentialSelector: { - name: '{{credentialName}}\'s {{pluginName}}', - }, conversion: { confirm: { title: 'Potrditev', diff --git a/web/i18n/th-TH/dataset-pipeline.ts b/web/i18n/th-TH/dataset-pipeline.ts index e2358aabf7..b9df16dbb9 100644 --- a/web/i18n/th-TH/dataset-pipeline.ts +++ b/web/i18n/th-TH/dataset-pipeline.ts @@ -137,9 +137,6 @@ const translation = { notConnected: '{{name}} ไม่ได้เชื่อมต่อ', notConnectedTip: 'เพื่อซิงค์กับ {{name}} ต้องสร้างการเชื่อมต่อกับ {{name}} ก่อน', }, - credentialSelector: { - name: '{{credentialName}}\'s {{pluginName}}', - }, conversion: { confirm: { title: 'การยืนยัน', diff --git a/web/i18n/tr-TR/dataset-pipeline.ts b/web/i18n/tr-TR/dataset-pipeline.ts index 030be7bec8..27433bde26 100644 --- a/web/i18n/tr-TR/dataset-pipeline.ts +++ b/web/i18n/tr-TR/dataset-pipeline.ts @@ -137,9 +137,6 @@ const translation = { notConnected: '{{name}} bağlı değil', notConnectedTip: '{{name}} ile senkronize olmak için önce {{name}} bağlantısının kurulması gerekir.', }, - credentialSelector: { - name: '{{credentialName}}\'un {{pluginName}}', - }, conversion: { confirm: { title: 'Onay', diff --git a/web/i18n/uk-UA/dataset-pipeline.ts b/web/i18n/uk-UA/dataset-pipeline.ts index 0d8473c30e..793112b2c6 100644 --- a/web/i18n/uk-UA/dataset-pipeline.ts +++ b/web/i18n/uk-UA/dataset-pipeline.ts @@ -137,9 +137,6 @@ const translation = { notConnected: '{{name}} не підключено', notConnectedTip: 'Щоб синхронізувати з {{name}}, спершу потрібно встановити з’єднання з {{name}}.', }, - credentialSelector: { - name: '{{credentialName}}\'s {{pluginName}}', - }, conversion: { confirm: { title: 'Підтвердження', diff --git a/web/i18n/vi-VN/dataset-pipeline.ts b/web/i18n/vi-VN/dataset-pipeline.ts index a785b1b7d8..9589f8a715 100644 --- a/web/i18n/vi-VN/dataset-pipeline.ts +++ b/web/i18n/vi-VN/dataset-pipeline.ts @@ -137,9 +137,6 @@ const translation = { notConnected: '{{name}} không được kết nối', notConnectedTip: 'Để đồng bộ với {{name}}, trước tiên phải thiết lập kết nối với {{name}}.', }, - credentialSelector: { - name: '{{credentialName}}\'s {{pluginName}}', - }, conversion: { confirm: { title: 'Sự xác nhận', diff --git a/web/i18n/zh-Hant/dataset-pipeline.ts b/web/i18n/zh-Hant/dataset-pipeline.ts index f1c8157c22..c396551dc6 100644 --- a/web/i18n/zh-Hant/dataset-pipeline.ts +++ b/web/i18n/zh-Hant/dataset-pipeline.ts @@ -137,9 +137,6 @@ const translation = { notConnected: '{{name}} 未連接', notConnectedTip: '要與 {{name}} 同步,必須先建立與 {{name}} 的連線。', }, - credentialSelector: { - name: '{{credentialName}}的{{pluginName}}', - }, conversion: { confirm: { title: '證實', From ec3a52f012da5bba9218f164e0270f330e67eba7 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:12:14 +0800 Subject: [PATCH 07/28] Fix immediate window open defaults and error handling (#29417) Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- web/hooks/use-async-window-open.spec.ts | 73 ++++++++++++++++++++++++- web/hooks/use-async-window-open.ts | 12 +++- 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/web/hooks/use-async-window-open.spec.ts b/web/hooks/use-async-window-open.spec.ts index 63ec9185da..5c1410b2c1 100644 --- a/web/hooks/use-async-window-open.spec.ts +++ b/web/hooks/use-async-window-open.spec.ts @@ -12,8 +12,9 @@ describe('useAsyncWindowOpen', () => { window.open = originalOpen }) - it('opens immediate url synchronously without calling async getter', async () => { - const openSpy = jest.fn() + it('opens immediate url synchronously, clears opener, without calling async getter', async () => { + const mockWindow: any = { opener: 'should-clear' } + const openSpy = jest.fn(() => mockWindow) window.open = openSpy const getUrl = jest.fn() const { result } = renderHook(() => useAsyncWindowOpen()) @@ -22,12 +23,54 @@ describe('useAsyncWindowOpen', () => { await result.current(getUrl, { immediateUrl: 'https://example.com', target: '_blank', - features: 'noopener,noreferrer', + features: undefined, }) }) expect(openSpy).toHaveBeenCalledWith('https://example.com', '_blank', 'noopener,noreferrer') expect(getUrl).not.toHaveBeenCalled() + expect(mockWindow.opener).toBeNull() + }) + + it('appends noopener,noreferrer when immediate open passes custom features', async () => { + const mockWindow: any = { opener: 'should-clear' } + const openSpy = jest.fn(() => mockWindow) + window.open = openSpy + const getUrl = jest.fn() + const { result } = renderHook(() => useAsyncWindowOpen()) + + await act(async () => { + await result.current(getUrl, { + immediateUrl: 'https://example.com', + target: '_blank', + features: 'width=500', + }) + }) + + expect(openSpy).toHaveBeenCalledWith('https://example.com', '_blank', 'width=500,noopener,noreferrer') + expect(getUrl).not.toHaveBeenCalled() + expect(mockWindow.opener).toBeNull() + }) + + it('reports error when immediate window fails to open', async () => { + const openSpy = jest.fn(() => null) + window.open = openSpy + const getUrl = jest.fn() + const onError = jest.fn() + const { result } = renderHook(() => useAsyncWindowOpen()) + + await act(async () => { + await result.current(getUrl, { + immediateUrl: 'https://example.com', + target: '_blank', + onError, + }) + }) + + expect(onError).toHaveBeenCalled() + const errArg = onError.mock.calls[0][0] as Error + expect(errArg.message).toBe('Failed to open new window') + expect(getUrl).not.toHaveBeenCalled() }) it('sets opener to null and redirects when async url resolves', async () => { @@ -75,6 +118,30 @@ describe('useAsyncWindowOpen', () => { expect(mockWindow.location.href).toBe('') }) + it('preserves custom features as-is for async open', async () => { + const close = jest.fn() + const mockWindow: any = { + location: { href: '' }, + close, + opener: 'should-be-cleared', + } + const openSpy = jest.fn(() => mockWindow) + window.open = openSpy + const { result } = renderHook(() => useAsyncWindowOpen()) + + await act(async () => { + await result.current(async () => 'https://example.com/path', { + target: '_blank', + features: 'width=500', + }) + }) + + expect(openSpy).toHaveBeenCalledWith('about:blank', '_blank', 'width=500') + expect(mockWindow.opener).toBeNull() + expect(mockWindow.location.href).toBe('https://example.com/path') + expect(close).not.toHaveBeenCalled() + }) + it('closes placeholder and reports when no url is returned', async () => { const close = jest.fn() const mockWindow: any = { diff --git a/web/hooks/use-async-window-open.ts b/web/hooks/use-async-window-open.ts index e3d7910217..b640fe430c 100644 --- a/web/hooks/use-async-window-open.ts +++ b/web/hooks/use-async-window-open.ts @@ -17,8 +17,18 @@ export const useAsyncWindowOpen = () => useCallback(async (getUrl: GetUrl, optio onError, } = options ?? {} + const secureImmediateFeatures = features ? `${features},noopener,noreferrer` : 'noopener,noreferrer' + if (immediateUrl) { - window.open(immediateUrl, target, features) + const newWindow = window.open(immediateUrl, target, secureImmediateFeatures) + if (!newWindow) { + onError?.(new Error('Failed to open new window')) + return + } + try { + newWindow.opener = null + } + catch { /* noop */ } return } From 94244ed8f6239a51e9a83f9d6fba3463cbbe8644 Mon Sep 17 00:00:00 2001 From: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:30:21 +0800 Subject: [PATCH 08/28] fix: handle potential undefined values in query_attachment_selector across multiple components (#29429) --- web/app/components/datasets/create/step-one/index.tsx | 1 + .../workflow/nodes/_base/components/variable/utils.ts | 8 ++++---- .../knowledge-retrieval/use-single-run-form-params.ts | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/web/app/components/datasets/create/step-one/index.tsx b/web/app/components/datasets/create/step-one/index.tsx index f2768be470..013ab7e934 100644 --- a/web/app/components/datasets/create/step-one/index.tsx +++ b/web/app/components/datasets/create/step-one/index.tsx @@ -291,6 +291,7 @@ const StepOne = ({ crawlOptions={crawlOptions} onCrawlOptionsChange={onCrawlOptionsChange} authedDataSourceList={authedDataSourceList} + supportBatchUpload={supportBatchUpload} />
{isShowVectorSpaceFull && ( diff --git a/web/app/components/workflow/nodes/_base/components/variable/utils.ts b/web/app/components/workflow/nodes/_base/components/variable/utils.ts index 10cb950c71..eb76021c40 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/utils.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/utils.ts @@ -70,10 +70,10 @@ export const isSystemVar = (valueSelector: ValueSelector) => { } export const isGlobalVar = (valueSelector: ValueSelector) => { - if(!isSystemVar(valueSelector)) return false + if (!isSystemVar(valueSelector)) return false const second = valueSelector[1] - if(['query', 'files'].includes(second)) + if (['query', 'files'].includes(second)) return false return true } @@ -1296,7 +1296,7 @@ export const getNodeUsedVars = (node: Node): ValueSelector[] => { case BlockEnum.KnowledgeRetrieval: { const { query_variable_selector, - query_attachment_selector, + query_attachment_selector = [], } = data as KnowledgeRetrievalNodeType res = [query_variable_selector, query_attachment_selector] break @@ -1638,7 +1638,7 @@ export const updateNodeVars = ( ) payload.query_variable_selector = newVarSelector if ( - payload.query_attachment_selector.join('.') === oldVarSelector.join('.') + payload.query_attachment_selector?.join('.') === oldVarSelector.join('.') ) payload.query_attachment_selector = newVarSelector break diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/use-single-run-form-params.ts b/web/app/components/workflow/nodes/knowledge-retrieval/use-single-run-form-params.ts index 30ac9e0142..0f079bcee8 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/use-single-run-form-params.ts @@ -80,7 +80,7 @@ const useSingleRunFormParams = ({ }, ] if (hasMultiModalDatasets) { - const currentVariable = findVariableWhenOnLLMVision(payload.query_attachment_selector, availableFileVars) + const currentVariable = findVariableWhenOnLLMVision(payload.query_attachment_selector || [], availableFileVars) inputFields.push( { inputs: [{ @@ -98,13 +98,13 @@ const useSingleRunFormParams = ({ }, [query, setQuery, t, datasetsDetail, payload.dataset_ids, payload.query_attachment_selector, availableFileVars, queryAttachment, setQueryAttachment]) const getDependentVars = () => { - return [payload.query_variable_selector, payload.query_attachment_selector] + return [payload.query_variable_selector, payload.query_attachment_selector || []] } const getDependentVar = (variable: string) => { if (variable === 'query') return payload.query_variable_selector if (variable === 'queryAttachment') - return payload.query_attachment_selector + return payload.query_attachment_selector || [] } return { From 813a734f27e9940ca25af7ac3a4f6897c880b33d Mon Sep 17 00:00:00 2001 From: -LAN- Date: Wed, 10 Dec 2025 19:54:25 +0800 Subject: [PATCH 09/28] chore: bump dify release to 1.11.0 (#29355) --- api/pyproject.toml | 2 +- api/uv.lock | 2 +- docker/docker-compose-template.yaml | 10 +++++----- docker/docker-compose.middleware.yaml | 2 +- docker/docker-compose.yaml | 10 +++++----- web/package.json | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/api/pyproject.toml b/api/pyproject.toml index 4f400129c1..cabba92036 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dify-api" -version = "1.10.1" +version = "1.11.0" requires-python = ">=3.11,<3.13" dependencies = [ diff --git a/api/uv.lock b/api/uv.lock index b6a554ec4d..e46fb77596 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1337,7 +1337,7 @@ wheels = [ [[package]] name = "dify-api" -version = "1.10.1" +version = "1.11.0" source = { virtual = "." } dependencies = [ { name = "apscheduler" }, diff --git a/docker/docker-compose-template.yaml b/docker/docker-compose-template.yaml index 3c01274ce8..b3d5cca245 100644 --- a/docker/docker-compose-template.yaml +++ b/docker/docker-compose-template.yaml @@ -21,7 +21,7 @@ services: # API service api: - image: langgenius/dify-api:1.10.1-fix.1 + image: langgenius/dify-api:1.11.0 restart: always environment: # Use the shared environment variables. @@ -62,7 +62,7 @@ services: # worker service # The Celery worker for processing all queues (dataset, workflow, mail, etc.) worker: - image: langgenius/dify-api:1.10.1-fix.1 + image: langgenius/dify-api:1.11.0 restart: always environment: # Use the shared environment variables. @@ -101,7 +101,7 @@ services: # worker_beat service # Celery beat for scheduling periodic tasks. worker_beat: - image: langgenius/dify-api:1.10.1-fix.1 + image: langgenius/dify-api:1.11.0 restart: always environment: # Use the shared environment variables. @@ -131,7 +131,7 @@ services: # Frontend web application. web: - image: langgenius/dify-web:1.10.1-fix.1 + image: langgenius/dify-web:1.11.0 restart: always environment: CONSOLE_API_URL: ${CONSOLE_API_URL:-} @@ -268,7 +268,7 @@ services: # plugin daemon plugin_daemon: - image: langgenius/dify-plugin-daemon:0.4.1-local + image: langgenius/dify-plugin-daemon:0.5.1-local restart: always environment: # Use the shared environment variables. diff --git a/docker/docker-compose.middleware.yaml b/docker/docker-compose.middleware.yaml index f446e385b3..68ef217bbd 100644 --- a/docker/docker-compose.middleware.yaml +++ b/docker/docker-compose.middleware.yaml @@ -123,7 +123,7 @@ services: # plugin daemon plugin_daemon: - image: langgenius/dify-plugin-daemon:0.4.1-local + image: langgenius/dify-plugin-daemon:0.5.1-local restart: always env_file: - ./middleware.env diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 809aa1f841..b961f6b216 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -658,7 +658,7 @@ services: # API service api: - image: langgenius/dify-api:1.10.1-fix.1 + image: langgenius/dify-api:1.11.0 restart: always environment: # Use the shared environment variables. @@ -699,7 +699,7 @@ services: # worker service # The Celery worker for processing all queues (dataset, workflow, mail, etc.) worker: - image: langgenius/dify-api:1.10.1-fix.1 + image: langgenius/dify-api:1.11.0 restart: always environment: # Use the shared environment variables. @@ -738,7 +738,7 @@ services: # worker_beat service # Celery beat for scheduling periodic tasks. worker_beat: - image: langgenius/dify-api:1.10.1-fix.1 + image: langgenius/dify-api:1.11.0 restart: always environment: # Use the shared environment variables. @@ -768,7 +768,7 @@ services: # Frontend web application. web: - image: langgenius/dify-web:1.10.1-fix.1 + image: langgenius/dify-web:1.11.0 restart: always environment: CONSOLE_API_URL: ${CONSOLE_API_URL:-} @@ -905,7 +905,7 @@ services: # plugin daemon plugin_daemon: - image: langgenius/dify-plugin-daemon:0.4.1-local + image: langgenius/dify-plugin-daemon:0.5.1-local restart: always environment: # Use the shared environment variables. diff --git a/web/package.json b/web/package.json index aba92f4891..46f8c77e2a 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "dify-web", - "version": "1.10.1", + "version": "1.11.0", "private": true, "packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a", "engines": { From 18082752a00db4c6ba1de5d4edd36910c64f9697 Mon Sep 17 00:00:00 2001 From: Jyong <76649700+JohnJyong@users.noreply.github.com> Date: Wed, 10 Dec 2025 20:42:51 +0800 Subject: [PATCH 10/28] fix knowledge pipeline run multimodal document failed (#29431) --- .../rag/index_processor/processor/paragraph_index_processor.py | 2 +- .../index_processor/processor/parent_child_index_processor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/core/rag/index_processor/processor/paragraph_index_processor.py b/api/core/rag/index_processor/processor/paragraph_index_processor.py index a7c879f2c4..cf68cff7dc 100644 --- a/api/core/rag/index_processor/processor/paragraph_index_processor.py +++ b/api/core/rag/index_processor/processor/paragraph_index_processor.py @@ -209,7 +209,7 @@ class ParagraphIndexProcessor(BaseIndexProcessor): if dataset.indexing_technique == "high_quality": vector = Vector(dataset) vector.create(documents) - if all_multimodal_documents: + if all_multimodal_documents and dataset.is_multimodal: vector.create_multimodal(all_multimodal_documents) elif dataset.indexing_technique == "economy": keyword = Keyword(dataset) diff --git a/api/core/rag/index_processor/processor/parent_child_index_processor.py b/api/core/rag/index_processor/processor/parent_child_index_processor.py index ee29d2fd65..0366f3259f 100644 --- a/api/core/rag/index_processor/processor/parent_child_index_processor.py +++ b/api/core/rag/index_processor/processor/parent_child_index_processor.py @@ -312,7 +312,7 @@ class ParentChildIndexProcessor(BaseIndexProcessor): vector = Vector(dataset) if all_child_documents: vector.create(all_child_documents) - if all_multimodal_documents: + if all_multimodal_documents and dataset.is_multimodal: vector.create_multimodal(all_multimodal_documents) def format_preview(self, chunks: Any) -> Mapping[str, Any]: From 8cab3e5a1e3bf89c120f2bddb2b5b0b6b9897e97 Mon Sep 17 00:00:00 2001 From: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com> Date: Thu, 11 Dec 2025 02:33:14 +0800 Subject: [PATCH 11/28] minor fix: get_tools wrong condition (#27253) Signed-off-by: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: -LAN- --- api/core/tools/workflow_as_tool/provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/core/tools/workflow_as_tool/provider.py b/api/core/tools/workflow_as_tool/provider.py index 4852e9d2d8..0439fb1d60 100644 --- a/api/core/tools/workflow_as_tool/provider.py +++ b/api/core/tools/workflow_as_tool/provider.py @@ -221,7 +221,7 @@ class WorkflowToolProviderController(ToolProviderController): session.query(WorkflowToolProvider) .where( WorkflowToolProvider.tenant_id == tenant_id, - WorkflowToolProvider.app_id == self.provider_id, + WorkflowToolProvider.id == self.provider_id, ) .first() ) From 693877e5e48dcc0f3a707d56838c4d84b2a0628b Mon Sep 17 00:00:00 2001 From: AuditAIH <145266260+AuditAIH@users.noreply.github.com> Date: Thu, 11 Dec 2025 02:52:40 +0800 Subject: [PATCH 12/28] Fix: Prevent binary content from being stored in process_data for HTTP nodes (#27532) Signed-off-by: -LAN- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: -LAN- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- api/core/workflow/nodes/http_request/executor.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/api/core/workflow/nodes/http_request/executor.py b/api/core/workflow/nodes/http_request/executor.py index 7b5b9c9e86..f0c84872fb 100644 --- a/api/core/workflow/nodes/http_request/executor.py +++ b/api/core/workflow/nodes/http_request/executor.py @@ -412,16 +412,20 @@ class Executor: body_string += f"--{boundary}\r\n" body_string += f'Content-Disposition: form-data; name="{key}"\r\n\r\n' # decode content safely - try: - body_string += content.decode("utf-8") - except UnicodeDecodeError: - body_string += content.decode("utf-8", errors="replace") - body_string += "\r\n" + # Do not decode binary content; use a placeholder with file metadata instead. + # Includes filename, size, and MIME type for better logging context. + body_string += ( + f"\r\n" + ) body_string += f"--{boundary}--\r\n" elif self.node_data.body: if self.content: + # If content is bytes, do not decode it; show a placeholder with size. + # Provides content size information for binary data without exposing the raw bytes. if isinstance(self.content, bytes): - body_string = self.content.decode("utf-8", errors="replace") + body_string = f"" else: body_string = self.content elif self.data and self.node_data.body.type == "x-www-form-urlencoded": From 2d496e7e08f7ceaf685eee10dbbf541481b7d013 Mon Sep 17 00:00:00 2001 From: -LAN- Date: Thu, 11 Dec 2025 09:45:55 +0800 Subject: [PATCH 13/28] ci: enforce semantic pull request titles (#29438) Signed-off-by: -LAN- --- .github/workflows/semantic-pull-request.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/semantic-pull-request.yml diff --git a/.github/workflows/semantic-pull-request.yml b/.github/workflows/semantic-pull-request.yml new file mode 100644 index 0000000000..b15c26a096 --- /dev/null +++ b/.github/workflows/semantic-pull-request.yml @@ -0,0 +1,21 @@ +name: Semantic Pull Request + +on: + pull_request: + types: + - opened + - edited + - reopened + - synchronize + +jobs: + lint: + name: Validate PR title + permissions: + pull-requests: read + runs-on: ubuntu-latest + steps: + - name: Check title + uses: amannn/action-semantic-pull-request@v6.1.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From b4afc7e4359473db8781ee94326341b7e21407e5 Mon Sep 17 00:00:00 2001 From: -LAN- Date: Thu, 11 Dec 2025 09:47:10 +0800 Subject: [PATCH 14/28] fix: Can not blank conversation ID validation in chat payloads (#29436) Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- api/controllers/service_api/app/completion.py | 16 ++++++++++-- api/libs/helper.py | 2 +- .../app/test_chat_request_payload.py | 25 +++++++++++++++++++ 3 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 api/tests/unit_tests/controllers/service_api/app/test_chat_request_payload.py diff --git a/api/controllers/service_api/app/completion.py b/api/controllers/service_api/app/completion.py index a037fe9254..b7fb01c6fe 100644 --- a/api/controllers/service_api/app/completion.py +++ b/api/controllers/service_api/app/completion.py @@ -4,7 +4,7 @@ from uuid import UUID from flask import request from flask_restx import Resource -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, field_validator from werkzeug.exceptions import BadRequest, InternalServerError, NotFound import services @@ -52,11 +52,23 @@ class ChatRequestPayload(BaseModel): query: str files: list[dict[str, Any]] | None = None response_mode: Literal["blocking", "streaming"] | None = None - conversation_id: UUID | None = None + conversation_id: str | None = Field(default=None, description="Conversation UUID") retriever_from: str = Field(default="dev") auto_generate_name: bool = Field(default=True, description="Auto generate conversation name") workflow_id: str | None = Field(default=None, description="Workflow ID for advanced chat") + @field_validator("conversation_id", mode="before") + @classmethod + def normalize_conversation_id(cls, value: str | UUID | None) -> str | None: + """Allow missing or blank conversation IDs; enforce UUID format when provided.""" + if not value: + return None + + try: + return helper.uuid_value(value) + except ValueError as exc: + raise ValueError("conversation_id must be a valid UUID") from exc + register_schema_models(service_api_ns, CompletionRequestPayload, ChatRequestPayload) diff --git a/api/libs/helper.py b/api/libs/helper.py index 0506e0ed5f..a278ace6ad 100644 --- a/api/libs/helper.py +++ b/api/libs/helper.py @@ -107,7 +107,7 @@ def email(email): EmailStr = Annotated[str, AfterValidator(email)] -def uuid_value(value): +def uuid_value(value: Any) -> str: if value == "": return str(value) diff --git a/api/tests/unit_tests/controllers/service_api/app/test_chat_request_payload.py b/api/tests/unit_tests/controllers/service_api/app/test_chat_request_payload.py new file mode 100644 index 0000000000..1fb7e7009d --- /dev/null +++ b/api/tests/unit_tests/controllers/service_api/app/test_chat_request_payload.py @@ -0,0 +1,25 @@ +import uuid + +import pytest +from pydantic import ValidationError + +from controllers.service_api.app.completion import ChatRequestPayload + + +def test_chat_request_payload_accepts_blank_conversation_id(): + payload = ChatRequestPayload.model_validate({"inputs": {}, "query": "hello", "conversation_id": ""}) + + assert payload.conversation_id is None + + +def test_chat_request_payload_validates_uuid(): + conversation_id = str(uuid.uuid4()) + + payload = ChatRequestPayload.model_validate({"inputs": {}, "query": "hello", "conversation_id": conversation_id}) + + assert payload.conversation_id == conversation_id + + +def test_chat_request_payload_rejects_invalid_uuid(): + with pytest.raises(ValidationError): + ChatRequestPayload.model_validate({"inputs": {}, "query": "hello", "conversation_id": "invalid"}) From d152d63e7dbeb67d33cce4ed9ba5035114c15cdd Mon Sep 17 00:00:00 2001 From: wangxiaolei Date: Thu, 11 Dec 2025 09:47:39 +0800 Subject: [PATCH 15/28] =?UTF-8?q?chore:=20update=20remove=5Fleading=5Fsymb?= =?UTF-8?q?ols=20pattern,=20keep=20=E3=80=90=20(#29419)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- api/core/tools/utils/text_processing_utils.py | 2 +- api/tests/unit_tests/utils/test_text_processing.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/api/core/tools/utils/text_processing_utils.py b/api/core/tools/utils/text_processing_utils.py index 80c69e94c8..0f9a91a111 100644 --- a/api/core/tools/utils/text_processing_utils.py +++ b/api/core/tools/utils/text_processing_utils.py @@ -13,5 +13,5 @@ def remove_leading_symbols(text: str) -> str: """ # Match Unicode ranges for punctuation and symbols # FIXME this pattern is confused quick fix for #11868 maybe refactor it later - pattern = r"^[\u2000-\u206F\u2E00-\u2E7F\u3000-\u303F\"#$%&'()*+,./:;<=>?@^_`~]+" + pattern = r'^[\[\]\u2000-\u2025\u2027-\u206F\u2E00-\u2E7F\u3000-\u300F\u3011-\u303F"#$%&\'()*+,./:;<=>?@^_`~]+' return re.sub(pattern, "", text) diff --git a/api/tests/unit_tests/utils/test_text_processing.py b/api/tests/unit_tests/utils/test_text_processing.py index 8af47e8967..11e017464a 100644 --- a/api/tests/unit_tests/utils/test_text_processing.py +++ b/api/tests/unit_tests/utils/test_text_processing.py @@ -14,6 +14,7 @@ from core.tools.utils.text_processing_utils import remove_leading_symbols ("Hello, World!", "Hello, World!"), ("", ""), (" ", " "), + ("【测试】", "【测试】"), ], ) def test_remove_leading_symbols(input_text, expected_output): From 266d1c70ac11f314f4e822cfb7f2aa6952f19043 Mon Sep 17 00:00:00 2001 From: wangxiaolei Date: Thu, 11 Dec 2025 09:48:45 +0800 Subject: [PATCH 16/28] fix: fix custom model credentials display as plaintext (#29425) --- api/services/model_provider_service.py | 23 ++++- ...est_model_provider_service_sanitization.py | 88 +++++++++++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 api/tests/unit_tests/services/test_model_provider_service_sanitization.py diff --git a/api/services/model_provider_service.py b/api/services/model_provider_service.py index a9e2c72534..eea382febe 100644 --- a/api/services/model_provider_service.py +++ b/api/services/model_provider_service.py @@ -70,9 +70,28 @@ class ModelProviderService: continue provider_config = provider_configuration.custom_configuration.provider - model_config = provider_configuration.custom_configuration.models + models = provider_configuration.custom_configuration.models can_added_models = provider_configuration.custom_configuration.can_added_models + # IMPORTANT: Never expose decrypted credentials in the provider list API. + # Sanitize custom model configurations by dropping the credentials payload. + sanitized_model_config = [] + if models: + from core.entities.provider_entities import CustomModelConfiguration # local import to avoid cycles + + for model in models: + sanitized_model_config.append( + CustomModelConfiguration( + model=model.model, + model_type=model.model_type, + credentials=None, # strip secrets from list view + current_credential_id=model.current_credential_id, + current_credential_name=model.current_credential_name, + available_model_credentials=model.available_model_credentials, + unadded_to_model_list=model.unadded_to_model_list, + ) + ) + provider_response = ProviderResponse( tenant_id=tenant_id, provider=provider_configuration.provider.provider, @@ -95,7 +114,7 @@ class ModelProviderService: current_credential_id=getattr(provider_config, "current_credential_id", None), current_credential_name=getattr(provider_config, "current_credential_name", None), available_credentials=getattr(provider_config, "available_credentials", []), - custom_models=model_config, + custom_models=sanitized_model_config, can_added_models=can_added_models, ), system_configuration=SystemConfigurationResponse( diff --git a/api/tests/unit_tests/services/test_model_provider_service_sanitization.py b/api/tests/unit_tests/services/test_model_provider_service_sanitization.py new file mode 100644 index 0000000000..9a107da1c7 --- /dev/null +++ b/api/tests/unit_tests/services/test_model_provider_service_sanitization.py @@ -0,0 +1,88 @@ +import types + +import pytest + +from core.entities.provider_entities import CredentialConfiguration, CustomModelConfiguration +from core.model_runtime.entities.common_entities import I18nObject +from core.model_runtime.entities.model_entities import ModelType +from core.model_runtime.entities.provider_entities import ConfigurateMethod +from models.provider import ProviderType +from services.model_provider_service import ModelProviderService + + +class _FakeConfigurations: + def __init__(self, provider_configuration: types.SimpleNamespace) -> None: + self._provider_configuration = provider_configuration + + def values(self) -> list[types.SimpleNamespace]: + return [self._provider_configuration] + + +@pytest.fixture +def service_with_fake_configurations(): + # Build a fake provider schema with minimal fields used by ProviderResponse + fake_provider = types.SimpleNamespace( + provider="langgenius/openai_api_compatible/openai_api_compatible", + label=I18nObject(en_US="OpenAI API Compatible", zh_Hans="OpenAI API Compatible"), + description=None, + icon_small=None, + icon_small_dark=None, + icon_large=None, + background=None, + help=None, + supported_model_types=[ModelType.LLM], + configurate_methods=[ConfigurateMethod.CUSTOMIZABLE_MODEL], + provider_credential_schema=None, + model_credential_schema=None, + ) + + # Include decrypted credentials to simulate the leak source + custom_model = CustomModelConfiguration( + model="gpt-4o-mini", + model_type=ModelType.LLM, + credentials={"api_key": "sk-plain-text", "endpoint": "https://example.com"}, + current_credential_id="cred-1", + current_credential_name="API KEY 1", + available_model_credentials=[], + unadded_to_model_list=False, + ) + + fake_custom_provider = types.SimpleNamespace( + current_credential_id="cred-1", + current_credential_name="API KEY 1", + available_credentials=[CredentialConfiguration(credential_id="cred-1", credential_name="API KEY 1")], + ) + + fake_custom_configuration = types.SimpleNamespace( + provider=fake_custom_provider, models=[custom_model], can_added_models=[] + ) + + fake_system_configuration = types.SimpleNamespace(enabled=False, current_quota_type=None, quota_configurations=[]) + + fake_provider_configuration = types.SimpleNamespace( + provider=fake_provider, + preferred_provider_type=ProviderType.CUSTOM, + custom_configuration=fake_custom_configuration, + system_configuration=fake_system_configuration, + is_custom_configuration_available=lambda: True, + ) + + class _FakeProviderManager: + def get_configurations(self, tenant_id: str) -> _FakeConfigurations: + return _FakeConfigurations(fake_provider_configuration) + + svc = ModelProviderService() + svc.provider_manager = _FakeProviderManager() + return svc + + +def test_get_provider_list_strips_credentials(service_with_fake_configurations: ModelProviderService): + providers = service_with_fake_configurations.get_provider_list(tenant_id="tenant-1", model_type=None) + + assert len(providers) == 1 + custom_models = providers[0].custom_configuration.custom_models + + assert custom_models is not None + assert len(custom_models) == 1 + # The sanitizer should drop credentials in list response + assert custom_models[0].credentials is None From a9627ba60a81e7e88eec3be0fc61ddf640bcd5eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:49:19 +0800 Subject: [PATCH 17/28] chore(deps): bump types-shapely from 2.0.0.20250404 to 2.1.0.20250917 in /api (#29441) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- api/pyproject.toml | 2 +- api/uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/pyproject.toml b/api/pyproject.toml index cabba92036..2a8432f571 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -151,7 +151,7 @@ dev = [ "types-pywin32~=310.0.0", "types-pyyaml~=6.0.12", "types-regex~=2024.11.6", - "types-shapely~=2.0.0", + "types-shapely~=2.1.0", "types-simplejson>=3.20.0", "types-six>=1.17.0", "types-tensorflow>=2.18.0", diff --git a/api/uv.lock b/api/uv.lock index e46fb77596..44703a0247 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1681,7 +1681,7 @@ dev = [ { name = "types-redis", specifier = ">=4.6.0.20241004" }, { name = "types-regex", specifier = "~=2024.11.6" }, { name = "types-setuptools", specifier = ">=80.9.0" }, - { name = "types-shapely", specifier = "~=2.0.0" }, + { name = "types-shapely", specifier = "~=2.1.0" }, { name = "types-simplejson", specifier = ">=3.20.0" }, { name = "types-six", specifier = ">=1.17.0" }, { name = "types-tensorflow", specifier = ">=2.18.0" }, @@ -6557,14 +6557,14 @@ wheels = [ [[package]] name = "types-shapely" -version = "2.0.0.20250404" +version = "2.1.0.20250917" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/55/c71a25fd3fc9200df4d0b5fd2f6d74712a82f9a8bbdd90cefb9e6aee39dd/types_shapely-2.0.0.20250404.tar.gz", hash = "sha256:863f540b47fa626c33ae64eae06df171f9ab0347025d4458d2df496537296b4f", size = 25066, upload-time = "2025-04-04T02:54:30.592Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/19/7f28b10994433d43b9caa66f3b9bd6a0a9192b7ce8b5a7fc41534e54b821/types_shapely-2.1.0.20250917.tar.gz", hash = "sha256:5c56670742105aebe40c16414390d35fcaa55d6f774d328c1a18273ab0e2134a", size = 26363, upload-time = "2025-09-17T02:47:44.604Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/ff/7f4d414eb81534ba2476f3d54f06f1463c2ebf5d663fd10cff16ba607dd6/types_shapely-2.0.0.20250404-py3-none-any.whl", hash = "sha256:170fb92f5c168a120db39b3287697fdec5c93ef3e1ad15e52552c36b25318821", size = 36350, upload-time = "2025-04-04T02:54:29.506Z" }, + { url = "https://files.pythonhosted.org/packages/e5/a9/554ac40810e530263b6163b30a2b623bc16aae3fb64416f5d2b3657d0729/types_shapely-2.1.0.20250917-py3-none-any.whl", hash = "sha256:9334a79339504d39b040426be4938d422cec419168414dc74972aa746a8bf3a1", size = 37813, upload-time = "2025-09-17T02:47:43.788Z" }, ] [[package]] From acdbcdb6f82b2ed2d48d5dd20cbfd745375588a6 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:51:30 +0800 Subject: [PATCH 18/28] chore: update packageManager version in package.json to pnpm@10.25.0 (#29407) --- web/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/package.json b/web/package.json index 46f8c77e2a..f118dff25b 100644 --- a/web/package.json +++ b/web/package.json @@ -2,7 +2,7 @@ "name": "dify-web", "version": "1.11.0", "private": true, - "packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a", + "packageManager": "pnpm@10.25.0+sha512.5e82639027af37cf832061bcc6d639c219634488e0f2baebe785028a793de7b525ffcd3f7ff574f5e9860654e098fe852ba8ac5dd5cefe1767d23a020a92f501", "engines": { "node": ">=v22.11.0" }, From 91f6d25daebb3e8889fdc544fc232bf320694745 Mon Sep 17 00:00:00 2001 From: Hengdong Gong Date: Thu, 11 Dec 2025 11:17:08 +0800 Subject: [PATCH 19/28] fix: knowledge dataset description field validation error #29404 (#29405) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- api/core/entities/knowledge_entities.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/core/entities/knowledge_entities.py b/api/core/entities/knowledge_entities.py index b9ca7414dc..bed3a35400 100644 --- a/api/core/entities/knowledge_entities.py +++ b/api/core/entities/knowledge_entities.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel +from pydantic import BaseModel, Field class PreviewDetail(BaseModel): @@ -20,7 +20,7 @@ class IndexingEstimate(BaseModel): class PipelineDataset(BaseModel): id: str name: str - description: str + description: str | None = Field(default="", description="knowledge dataset description") chunk_structure: str From 18476099263c6292e821f2d87f73f2cc818cff2d Mon Sep 17 00:00:00 2001 From: crazywoola <100913391+crazywoola@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:05:44 +0800 Subject: [PATCH 20/28] fix: failed to delete model (#29456) --- api/controllers/console/workspace/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/controllers/console/workspace/models.py b/api/controllers/console/workspace/models.py index 246a869291..a5b45ef514 100644 --- a/api/controllers/console/workspace/models.py +++ b/api/controllers/console/workspace/models.py @@ -230,7 +230,7 @@ class ModelProviderModelApi(Resource): return {"result": "success"}, 200 - @console_ns.expect(console_ns.models[ParserDeleteModels.__name__], validate=True) + @console_ns.expect(console_ns.models[ParserDeleteModels.__name__]) @setup_required @login_required @is_admin_or_owner_required From 2e1efd62e1a9e568d9d587c063b10eed04d3100b Mon Sep 17 00:00:00 2001 From: -LAN- Date: Thu, 11 Dec 2025 12:53:37 +0800 Subject: [PATCH 21/28] revert: "fix(ops): add streaming metrics and LLM span for agent-chat traces" (#29469) --- .../advanced_chat/generate_task_pipeline.py | 90 ++----------------- api/core/app/entities/task_entities.py | 3 - .../easy_ui_based_generate_task_pipeline.py | 18 ---- api/core/ops/tencent_trace/span_builder.py | 53 ----------- api/core/ops/tencent_trace/tencent_trace.py | 6 +- api/models/model.py | 8 -- 6 files changed, 7 insertions(+), 171 deletions(-) diff --git a/api/core/app/apps/advanced_chat/generate_task_pipeline.py b/api/core/app/apps/advanced_chat/generate_task_pipeline.py index b297f3ff20..da1e9f19b6 100644 --- a/api/core/app/apps/advanced_chat/generate_task_pipeline.py +++ b/api/core/app/apps/advanced_chat/generate_task_pipeline.py @@ -62,8 +62,7 @@ from core.app.task_pipeline.message_cycle_manager import MessageCycleManager from core.base.tts import AppGeneratorTTSPublisher, AudioTrunk from core.model_runtime.entities.llm_entities import LLMUsage from core.model_runtime.utils.encoders import jsonable_encoder -from core.ops.entities.trace_entity import TraceTaskName -from core.ops.ops_trace_manager import TraceQueueManager, TraceTask +from core.ops.ops_trace_manager import TraceQueueManager from core.workflow.enums import WorkflowExecutionStatus from core.workflow.nodes import NodeType from core.workflow.repositories.draft_variable_repository import DraftVariableSaverFactory @@ -73,7 +72,7 @@ from extensions.ext_database import db from libs.datetime_utils import naive_utc_now from models import Account, Conversation, EndUser, Message, MessageFile from models.enums import CreatorUserRole -from models.workflow import Workflow, WorkflowNodeExecutionModel +from models.workflow import Workflow logger = logging.getLogger(__name__) @@ -581,7 +580,7 @@ class AdvancedChatAppGenerateTaskPipeline(GraphRuntimeStateSupport): with self._database_session() as session: # Save message - self._save_message(session=session, graph_runtime_state=resolved_state, trace_manager=trace_manager) + self._save_message(session=session, graph_runtime_state=resolved_state) yield workflow_finish_resp elif event.stopped_by in ( @@ -591,7 +590,7 @@ class AdvancedChatAppGenerateTaskPipeline(GraphRuntimeStateSupport): # When hitting input-moderation or annotation-reply, the workflow will not start with self._database_session() as session: # Save message - self._save_message(session=session, trace_manager=trace_manager) + self._save_message(session=session) yield self._message_end_to_stream_response() @@ -600,7 +599,6 @@ class AdvancedChatAppGenerateTaskPipeline(GraphRuntimeStateSupport): event: QueueAdvancedChatMessageEndEvent, *, graph_runtime_state: GraphRuntimeState | None = None, - trace_manager: TraceQueueManager | None = None, **kwargs, ) -> Generator[StreamResponse, None, None]: """Handle advanced chat message end events.""" @@ -618,7 +616,7 @@ class AdvancedChatAppGenerateTaskPipeline(GraphRuntimeStateSupport): # Save message with self._database_session() as session: - self._save_message(session=session, graph_runtime_state=resolved_state, trace_manager=trace_manager) + self._save_message(session=session, graph_runtime_state=resolved_state) yield self._message_end_to_stream_response() @@ -772,13 +770,7 @@ class AdvancedChatAppGenerateTaskPipeline(GraphRuntimeStateSupport): if self._conversation_name_generate_thread: logger.debug("Conversation name generation running as daemon thread") - def _save_message( - self, - *, - session: Session, - graph_runtime_state: GraphRuntimeState | None = None, - trace_manager: TraceQueueManager | None = None, - ): + def _save_message(self, *, session: Session, graph_runtime_state: GraphRuntimeState | None = None): message = self._get_message(session=session) # If there are assistant files, remove markdown image links from answer @@ -817,14 +809,6 @@ class AdvancedChatAppGenerateTaskPipeline(GraphRuntimeStateSupport): metadata = self._task_state.metadata.model_dump() message.message_metadata = json.dumps(jsonable_encoder(metadata)) - - # Extract model provider and model_id from workflow node executions for tracing - if message.workflow_run_id: - model_info = self._extract_model_info_from_workflow(session, message.workflow_run_id) - if model_info: - message.model_provider = model_info.get("provider") - message.model_id = model_info.get("model") - message_files = [ MessageFile( message_id=message.id, @@ -842,68 +826,6 @@ class AdvancedChatAppGenerateTaskPipeline(GraphRuntimeStateSupport): ] session.add_all(message_files) - # Trigger MESSAGE_TRACE for tracing integrations - if trace_manager: - trace_manager.add_trace_task( - TraceTask( - TraceTaskName.MESSAGE_TRACE, conversation_id=self._conversation_id, message_id=self._message_id - ) - ) - - def _extract_model_info_from_workflow(self, session: Session, workflow_run_id: str) -> dict[str, str] | None: - """ - Extract model provider and model_id from workflow node executions. - Returns dict with 'provider' and 'model' keys, or None if not found. - """ - try: - # Query workflow node executions for LLM or Agent nodes - stmt = ( - select(WorkflowNodeExecutionModel) - .where(WorkflowNodeExecutionModel.workflow_run_id == workflow_run_id) - .where(WorkflowNodeExecutionModel.node_type.in_(["llm", "agent"])) - .order_by(WorkflowNodeExecutionModel.created_at.desc()) - .limit(1) - ) - node_execution = session.scalar(stmt) - - if not node_execution: - return None - - # Try to extract from execution_metadata for agent nodes - if node_execution.execution_metadata: - try: - metadata = json.loads(node_execution.execution_metadata) - agent_log = metadata.get("agent_log", []) - # Look for the first agent thought with provider info - for log_entry in agent_log: - entry_metadata = log_entry.get("metadata", {}) - provider_str = entry_metadata.get("provider") - if provider_str: - # Parse format like "langgenius/deepseek/deepseek" - parts = provider_str.split("/") - if len(parts) >= 3: - return {"provider": parts[1], "model": parts[2]} - elif len(parts) == 2: - return {"provider": parts[0], "model": parts[1]} - except (json.JSONDecodeError, KeyError, AttributeError) as e: - logger.debug("Failed to parse execution_metadata: %s", e) - - # Try to extract from process_data for llm nodes - if node_execution.process_data: - try: - process_data = json.loads(node_execution.process_data) - provider = process_data.get("model_provider") - model = process_data.get("model_name") - if provider and model: - return {"provider": provider, "model": model} - except (json.JSONDecodeError, KeyError) as e: - logger.debug("Failed to parse process_data: %s", e) - - return None - except Exception as e: - logger.warning("Failed to extract model info from workflow: %s", e) - return None - def _seed_graph_runtime_state_from_queue_manager(self) -> None: """Bootstrap the cached runtime state from the queue manager when present.""" candidate = self._base_task_pipeline.queue_manager.graph_runtime_state diff --git a/api/core/app/entities/task_entities.py b/api/core/app/entities/task_entities.py index 7692128985..79a5e657b3 100644 --- a/api/core/app/entities/task_entities.py +++ b/api/core/app/entities/task_entities.py @@ -40,9 +40,6 @@ class EasyUITaskState(TaskState): """ llm_result: LLMResult - first_token_time: float | None = None - last_token_time: float | None = None - is_streaming_response: bool = False class WorkflowTaskState(TaskState): diff --git a/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py b/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py index 98548ddfbb..5c169f4db1 100644 --- a/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py +++ b/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py @@ -332,12 +332,6 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline): if not self._task_state.llm_result.prompt_messages: self._task_state.llm_result.prompt_messages = chunk.prompt_messages - # Track streaming response times - if self._task_state.first_token_time is None: - self._task_state.first_token_time = time.perf_counter() - self._task_state.is_streaming_response = True - self._task_state.last_token_time = time.perf_counter() - # handle output moderation chunk should_direct_answer = self._handle_output_moderation_chunk(cast(str, delta_text)) if should_direct_answer: @@ -404,18 +398,6 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline): message.total_price = usage.total_price message.currency = usage.currency self._task_state.llm_result.usage.latency = message.provider_response_latency - - # Add streaming metrics to usage if available - if self._task_state.is_streaming_response and self._task_state.first_token_time: - start_time = self.start_at - first_token_time = self._task_state.first_token_time - last_token_time = self._task_state.last_token_time or first_token_time - usage.time_to_first_token = round(first_token_time - start_time, 3) - usage.time_to_generate = round(last_token_time - first_token_time, 3) - - # Update metadata with the complete usage info - self._task_state.metadata.usage = usage - message.message_metadata = self._task_state.metadata.model_dump_json() if trace_manager: diff --git a/api/core/ops/tencent_trace/span_builder.py b/api/core/ops/tencent_trace/span_builder.py index db92e9b8bd..26e8779e3e 100644 --- a/api/core/ops/tencent_trace/span_builder.py +++ b/api/core/ops/tencent_trace/span_builder.py @@ -222,59 +222,6 @@ class TencentSpanBuilder: links=links, ) - @staticmethod - def build_message_llm_span( - trace_info: MessageTraceInfo, trace_id: int, parent_span_id: int, user_id: str - ) -> SpanData: - """Build LLM span for message traces with detailed LLM attributes.""" - status = Status(StatusCode.OK) - if trace_info.error: - status = Status(StatusCode.ERROR, trace_info.error) - - # Extract model information from `metadata`` or `message_data` - trace_metadata = trace_info.metadata or {} - message_data = trace_info.message_data or {} - - model_provider = trace_metadata.get("ls_provider") or ( - message_data.get("model_provider", "") if isinstance(message_data, dict) else "" - ) - model_name = trace_metadata.get("ls_model_name") or ( - message_data.get("model_id", "") if isinstance(message_data, dict) else "" - ) - - inputs_str = str(trace_info.inputs or "") - outputs_str = str(trace_info.outputs or "") - - attributes = { - GEN_AI_SESSION_ID: trace_metadata.get("conversation_id", ""), - GEN_AI_USER_ID: str(user_id), - GEN_AI_SPAN_KIND: GenAISpanKind.GENERATION.value, - GEN_AI_FRAMEWORK: "dify", - GEN_AI_MODEL_NAME: str(model_name), - GEN_AI_PROVIDER: str(model_provider), - GEN_AI_USAGE_INPUT_TOKENS: str(trace_info.message_tokens or 0), - GEN_AI_USAGE_OUTPUT_TOKENS: str(trace_info.answer_tokens or 0), - GEN_AI_USAGE_TOTAL_TOKENS: str(trace_info.total_tokens or 0), - GEN_AI_PROMPT: inputs_str, - GEN_AI_COMPLETION: outputs_str, - INPUT_VALUE: inputs_str, - OUTPUT_VALUE: outputs_str, - } - - if trace_info.is_streaming_request: - attributes[GEN_AI_IS_STREAMING_REQUEST] = "true" - - return SpanData( - trace_id=trace_id, - parent_span_id=parent_span_id, - span_id=TencentTraceUtils.convert_to_span_id(trace_info.message_id, "llm"), - name="GENERATION", - start_time=TencentSpanBuilder._get_time_nanoseconds(trace_info.start_time), - end_time=TencentSpanBuilder._get_time_nanoseconds(trace_info.end_time), - attributes=attributes, - status=status, - ) - @staticmethod def build_tool_span(trace_info: ToolTraceInfo, trace_id: int, parent_span_id: int) -> SpanData: """Build tool span.""" diff --git a/api/core/ops/tencent_trace/tencent_trace.py b/api/core/ops/tencent_trace/tencent_trace.py index c345cee7a9..93ec186863 100644 --- a/api/core/ops/tencent_trace/tencent_trace.py +++ b/api/core/ops/tencent_trace/tencent_trace.py @@ -107,12 +107,8 @@ class TencentDataTrace(BaseTraceInstance): links.append(TencentTraceUtils.create_link(trace_info.trace_id)) message_span = TencentSpanBuilder.build_message_span(trace_info, trace_id, str(user_id), links) - self.trace_client.add_span(message_span) - # Add LLM child span with detailed attributes - parent_span_id = TencentTraceUtils.convert_to_span_id(trace_info.message_id, "message") - llm_span = TencentSpanBuilder.build_message_llm_span(trace_info, trace_id, parent_span_id, str(user_id)) - self.trace_client.add_span(llm_span) + self.trace_client.add_span(message_span) self._record_message_llm_metrics(trace_info) diff --git a/api/models/model.py b/api/models/model.py index 6b0bf4b4a2..c8fa6fd406 100644 --- a/api/models/model.py +++ b/api/models/model.py @@ -1255,13 +1255,9 @@ class Message(Base): "id": self.id, "app_id": self.app_id, "conversation_id": self.conversation_id, - "model_provider": self.model_provider, "model_id": self.model_id, "inputs": self.inputs, "query": self.query, - "message_tokens": self.message_tokens, - "answer_tokens": self.answer_tokens, - "provider_response_latency": self.provider_response_latency, "total_price": self.total_price, "message": self.message, "answer": self.answer, @@ -1283,12 +1279,8 @@ class Message(Base): id=data["id"], app_id=data["app_id"], conversation_id=data["conversation_id"], - model_provider=data.get("model_provider"), model_id=data["model_id"], inputs=data["inputs"], - message_tokens=data.get("message_tokens", 0), - answer_tokens=data.get("answer_tokens", 0), - provider_response_latency=data.get("provider_response_latency", 0.0), total_price=data["total_price"], query=data["query"], message=data["message"], From aac6f44562dc2cdd6522fda030976b5aa3991657 Mon Sep 17 00:00:00 2001 From: CrabSAMA <40541269+CrabSAMA@users.noreply.github.com> Date: Thu, 11 Dec 2025 13:47:37 +0800 Subject: [PATCH 22/28] fix: workflow end node validate error (#29473) Co-authored-by: Novice --- api/core/workflow/nodes/base/entities.py | 2 +- ...node_without_value_type_field_workflow.yml | 127 ++++++++++++++++++ .../test_end_node_without_value_type.py | 60 +++++++++ 3 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 api/tests/fixtures/workflow/end_node_without_value_type_field_workflow.yml create mode 100644 api/tests/unit_tests/core/workflow/graph_engine/test_end_node_without_value_type.py diff --git a/api/core/workflow/nodes/base/entities.py b/api/core/workflow/nodes/base/entities.py index e816e16d74..5aab6bbde4 100644 --- a/api/core/workflow/nodes/base/entities.py +++ b/api/core/workflow/nodes/base/entities.py @@ -59,7 +59,7 @@ class OutputVariableEntity(BaseModel): """ variable: str - value_type: OutputVariableType + value_type: OutputVariableType = OutputVariableType.ANY value_selector: Sequence[str] @field_validator("value_type", mode="before") diff --git a/api/tests/fixtures/workflow/end_node_without_value_type_field_workflow.yml b/api/tests/fixtures/workflow/end_node_without_value_type_field_workflow.yml new file mode 100644 index 0000000000..a69339691d --- /dev/null +++ b/api/tests/fixtures/workflow/end_node_without_value_type_field_workflow.yml @@ -0,0 +1,127 @@ +app: + description: 'End node without value_type field reproduction' + icon: 🤖 + icon_background: '#FFEAD5' + mode: workflow + name: end_node_without_value_type_field_reproduction + use_icon_as_answer_icon: false +dependencies: [] +kind: app +version: 0.5.0 +workflow: + conversation_variables: [] + environment_variables: [] + features: + file_upload: + allowed_file_extensions: + - .JPG + - .JPEG + - .PNG + - .GIF + - .WEBP + - .SVG + allowed_file_types: + - image + allowed_file_upload_methods: + - local_file + - remote_url + enabled: false + fileUploadConfig: + audio_file_size_limit: 50 + batch_count_limit: 5 + file_size_limit: 15 + image_file_batch_limit: 10 + image_file_size_limit: 10 + single_chunk_attachment_limit: 10 + video_file_size_limit: 100 + workflow_file_upload_limit: 10 + image: + enabled: false + number_limits: 3 + transfer_methods: + - local_file + - remote_url + number_limits: 3 + opening_statement: '' + retriever_resource: + enabled: true + sensitive_word_avoidance: + enabled: false + speech_to_text: + enabled: false + suggested_questions: [] + suggested_questions_after_answer: + enabled: false + text_to_speech: + enabled: false + language: '' + voice: '' + graph: + edges: + - data: + isInIteration: false + isInLoop: false + sourceType: start + targetType: end + id: 1765423445456-source-1765423454810-target + source: '1765423445456' + sourceHandle: source + target: '1765423454810' + targetHandle: target + type: custom + zIndex: 0 + nodes: + - data: + selected: false + title: 用户输入 + type: start + variables: + - default: '' + hint: '' + label: query + max_length: 48 + options: [] + placeholder: '' + required: true + type: text-input + variable: query + height: 109 + id: '1765423445456' + position: + x: -48 + y: 261 + positionAbsolute: + x: -48 + y: 261 + selected: false + sourcePosition: right + targetPosition: left + type: custom + width: 242 + - data: + outputs: + - value_selector: + - '1765423445456' + - query + variable: query + selected: true + title: 输出 + type: end + height: 88 + id: '1765423454810' + position: + x: 382 + y: 282 + positionAbsolute: + x: 382 + y: 282 + selected: true + sourcePosition: right + targetPosition: left + type: custom + width: 242 + viewport: + x: 139 + y: -135 + zoom: 1 + rag_pipeline_variables: [] diff --git a/api/tests/unit_tests/core/workflow/graph_engine/test_end_node_without_value_type.py b/api/tests/unit_tests/core/workflow/graph_engine/test_end_node_without_value_type.py new file mode 100644 index 0000000000..b1380cd6d2 --- /dev/null +++ b/api/tests/unit_tests/core/workflow/graph_engine/test_end_node_without_value_type.py @@ -0,0 +1,60 @@ +""" +Test case for end node without value_type field (backward compatibility). + +This test validates that end nodes work correctly even when the value_type +field is missing from the output configuration, ensuring backward compatibility +with older workflow definitions. +""" + +from core.workflow.graph_events import ( + GraphRunStartedEvent, + GraphRunSucceededEvent, + NodeRunStartedEvent, + NodeRunStreamChunkEvent, + NodeRunSucceededEvent, +) + +from .test_table_runner import TableTestRunner, WorkflowTestCase + + +def test_end_node_without_value_type_field(): + """ + Test that end node works without explicit value_type field. + + The fixture implements a simple workflow that: + 1. Takes a query input from start node + 2. Passes it directly to end node + 3. End node outputs the value without specifying value_type + 4. Should correctly infer the type and output the value + + This ensures backward compatibility with workflow definitions + created before value_type became a required field. + """ + fixture_name = "end_node_without_value_type_field_workflow" + + case = WorkflowTestCase( + fixture_path=fixture_name, + inputs={"query": "test query"}, + expected_outputs={"query": "test query"}, + expected_event_sequence=[ + # Graph start + GraphRunStartedEvent, + # Start node + NodeRunStartedEvent, + NodeRunStreamChunkEvent, # Start node streams the input value + NodeRunSucceededEvent, + # End node + NodeRunStartedEvent, + NodeRunSucceededEvent, + # Graph end + GraphRunSucceededEvent, + ], + description="End node without value_type field should work correctly", + ) + + runner = TableTestRunner() + result = runner.run_test_case(case) + assert result.success, f"Test failed: {result.error}" + assert result.actual_outputs == {"query": "test query"}, ( + f"Expected output to be {{'query': 'test query'}}, got {result.actual_outputs}" + ) From 69a22af1c96f5f4c20679a77798fd177689eb4b4 Mon Sep 17 00:00:00 2001 From: Jyong <76649700+JohnJyong@users.noreply.github.com> Date: Thu, 11 Dec 2025 13:50:46 +0800 Subject: [PATCH 23/28] fix: optimize database query when retrieval knowledge in App (#29467) --- api/core/rag/retrieval/dataset_retrieval.py | 201 ++++++++++---------- 1 file changed, 103 insertions(+), 98 deletions(-) diff --git a/api/core/rag/retrieval/dataset_retrieval.py b/api/core/rag/retrieval/dataset_retrieval.py index a65069b1b7..635eab73f0 100644 --- a/api/core/rag/retrieval/dataset_retrieval.py +++ b/api/core/rag/retrieval/dataset_retrieval.py @@ -592,111 +592,116 @@ class DatasetRetrieval: """Handle retrieval end.""" with flask_app.app_context(): dify_documents = [document for document in documents if document.provider == "dify"] - segment_ids = [] - segment_index_node_ids = [] + if not dify_documents: + self._send_trace_task(message_id, documents, timer) + return + with Session(db.engine) as session: - for document in dify_documents: - if document.metadata is not None: - dataset_document_stmt = select(DatasetDocument).where( - DatasetDocument.id == document.metadata["document_id"] - ) - dataset_document = session.scalar(dataset_document_stmt) - if dataset_document: - if dataset_document.doc_form == IndexStructureType.PARENT_CHILD_INDEX: - segment_id = None - if ( - "doc_type" not in document.metadata - or document.metadata.get("doc_type") == DocType.TEXT - ): - child_chunk_stmt = select(ChildChunk).where( - ChildChunk.index_node_id == document.metadata["doc_id"], - ChildChunk.dataset_id == dataset_document.dataset_id, - ChildChunk.document_id == dataset_document.id, - ) - child_chunk = session.scalar(child_chunk_stmt) - if child_chunk: - segment_id = child_chunk.segment_id - elif ( - "doc_type" in document.metadata - and document.metadata.get("doc_type") == DocType.IMAGE - ): - attachment_info_dict = RetrievalService.get_segment_attachment_info( - dataset_document.dataset_id, - dataset_document.tenant_id, - document.metadata.get("doc_id") or "", - session, - ) - if attachment_info_dict: - segment_id = attachment_info_dict["segment_id"] + # Collect all document_ids and batch fetch DatasetDocuments + document_ids = { + doc.metadata["document_id"] + for doc in dify_documents + if doc.metadata and "document_id" in doc.metadata + } + if not document_ids: + self._send_trace_task(message_id, documents, timer) + return + + dataset_docs_stmt = select(DatasetDocument).where(DatasetDocument.id.in_(document_ids)) + dataset_docs = session.scalars(dataset_docs_stmt).all() + dataset_doc_map = {str(doc.id): doc for doc in dataset_docs} + + # Categorize documents by type and collect necessary IDs + parent_child_text_docs: list[tuple[Document, DatasetDocument]] = [] + parent_child_image_docs: list[tuple[Document, DatasetDocument]] = [] + normal_text_docs: list[tuple[Document, DatasetDocument]] = [] + normal_image_docs: list[tuple[Document, DatasetDocument]] = [] + + for doc in dify_documents: + if not doc.metadata or "document_id" not in doc.metadata: + continue + dataset_doc = dataset_doc_map.get(doc.metadata["document_id"]) + if not dataset_doc: + continue + + is_image = doc.metadata.get("doc_type") == DocType.IMAGE + is_parent_child = dataset_doc.doc_form == IndexStructureType.PARENT_CHILD_INDEX + + if is_parent_child: + if is_image: + parent_child_image_docs.append((doc, dataset_doc)) + else: + parent_child_text_docs.append((doc, dataset_doc)) + else: + if is_image: + normal_image_docs.append((doc, dataset_doc)) + else: + normal_text_docs.append((doc, dataset_doc)) + + segment_ids_to_update: set[str] = set() + + # Process PARENT_CHILD_INDEX text documents - batch fetch ChildChunks + if parent_child_text_docs: + index_node_ids = [doc.metadata["doc_id"] for doc, _ in parent_child_text_docs if doc.metadata] + if index_node_ids: + child_chunks_stmt = select(ChildChunk).where(ChildChunk.index_node_id.in_(index_node_ids)) + child_chunks = session.scalars(child_chunks_stmt).all() + child_chunk_map = {chunk.index_node_id: chunk.segment_id for chunk in child_chunks} + for doc, _ in parent_child_text_docs: + if doc.metadata: + segment_id = child_chunk_map.get(doc.metadata["doc_id"]) if segment_id: - if segment_id not in segment_ids: - segment_ids.append(segment_id) - _ = ( - session.query(DocumentSegment) - .where(DocumentSegment.id == segment_id) - .update( - {DocumentSegment.hit_count: DocumentSegment.hit_count + 1}, - synchronize_session=False, - ) - ) - else: - query = None - if ( - "doc_type" not in document.metadata - or document.metadata.get("doc_type") == DocType.TEXT - ): - if document.metadata["doc_id"] not in segment_index_node_ids: - segment = ( - session.query(DocumentSegment) - .where(DocumentSegment.index_node_id == document.metadata["doc_id"]) - .first() - ) - if segment: - segment_index_node_ids.append(document.metadata["doc_id"]) - segment_ids.append(segment.id) - query = session.query(DocumentSegment).where( - DocumentSegment.id == segment.id - ) - elif ( - "doc_type" in document.metadata - and document.metadata.get("doc_type") == DocType.IMAGE - ): - attachment_info_dict = RetrievalService.get_segment_attachment_info( - dataset_document.dataset_id, - dataset_document.tenant_id, - document.metadata.get("doc_id") or "", - session, - ) - if attachment_info_dict: - segment_id = attachment_info_dict["segment_id"] - if segment_id not in segment_ids: - segment_ids.append(segment_id) - query = session.query(DocumentSegment).where(DocumentSegment.id == segment_id) - if query: - # if 'dataset_id' in document.metadata: - if "dataset_id" in document.metadata: - query = query.where( - DocumentSegment.dataset_id == document.metadata["dataset_id"] - ) + segment_ids_to_update.add(str(segment_id)) - # add hit count to document segment - query.update( - {DocumentSegment.hit_count: DocumentSegment.hit_count + 1}, - synchronize_session=False, - ) + # Process non-PARENT_CHILD_INDEX text documents - batch fetch DocumentSegments + if normal_text_docs: + index_node_ids = [doc.metadata["doc_id"] for doc, _ in normal_text_docs if doc.metadata] + if index_node_ids: + segments_stmt = select(DocumentSegment).where(DocumentSegment.index_node_id.in_(index_node_ids)) + segments = session.scalars(segments_stmt).all() + segment_map = {seg.index_node_id: seg.id for seg in segments} + for doc, _ in normal_text_docs: + if doc.metadata: + segment_id = segment_map.get(doc.metadata["doc_id"]) + if segment_id: + segment_ids_to_update.add(str(segment_id)) - db.session.commit() + # Process IMAGE documents - batch fetch SegmentAttachmentBindings + all_image_docs = parent_child_image_docs + normal_image_docs + if all_image_docs: + attachment_ids = [ + doc.metadata["doc_id"] + for doc, _ in all_image_docs + if doc.metadata and doc.metadata.get("doc_id") + ] + if attachment_ids: + bindings_stmt = select(SegmentAttachmentBinding).where( + SegmentAttachmentBinding.attachment_id.in_(attachment_ids) + ) + bindings = session.scalars(bindings_stmt).all() + segment_ids_to_update.update(str(binding.segment_id) for binding in bindings) - # get tracing instance - trace_manager: TraceQueueManager | None = ( - self.application_generate_entity.trace_manager if self.application_generate_entity else None - ) - if trace_manager: - trace_manager.add_trace_task( - TraceTask( - TraceTaskName.DATASET_RETRIEVAL_TRACE, message_id=message_id, documents=documents, timer=timer + # Batch update hit_count for all segments + if segment_ids_to_update: + session.query(DocumentSegment).where(DocumentSegment.id.in_(segment_ids_to_update)).update( + {DocumentSegment.hit_count: DocumentSegment.hit_count + 1}, + synchronize_session=False, ) + session.commit() + + self._send_trace_task(message_id, documents, timer) + + def _send_trace_task(self, message_id: str | None, documents: list[Document], timer: dict | None): + """Send trace task if trace manager is available.""" + trace_manager: TraceQueueManager | None = ( + self.application_generate_entity.trace_manager if self.application_generate_entity else None + ) + if trace_manager: + trace_manager.add_trace_task( + TraceTask( + TraceTaskName.DATASET_RETRIEVAL_TRACE, message_id=message_id, documents=documents, timer=timer ) + ) def _on_query( self, From fcadee9413a97c3322aca0bf6fd0ce598e66537c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=9E=E6=B3=95=E6=93=8D=E4=BD=9C?= Date: Thu, 11 Dec 2025 14:30:09 +0800 Subject: [PATCH 24/28] fix: flask db downgrade not work (#29465) --- ...1_15_2102-09cfdda155d1_mysql_adaptation.py | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/api/migrations/versions/2025_11_15_2102-09cfdda155d1_mysql_adaptation.py b/api/migrations/versions/2025_11_15_2102-09cfdda155d1_mysql_adaptation.py index a3f6c3cb19..877fa2f309 100644 --- a/api/migrations/versions/2025_11_15_2102-09cfdda155d1_mysql_adaptation.py +++ b/api/migrations/versions/2025_11_15_2102-09cfdda155d1_mysql_adaptation.py @@ -1,4 +1,4 @@ -"""empty message +"""mysql adaptation Revision ID: 09cfdda155d1 Revises: 669ffd70119c @@ -97,11 +97,31 @@ def downgrade(): batch_op.alter_column('include_plugins', existing_type=sa.JSON(), type_=postgresql.ARRAY(sa.VARCHAR(length=255)), - existing_nullable=False) + existing_nullable=False, + postgresql_using=""" + COALESCE( + regexp_replace( + replace(replace(include_plugins::text, '[', '{'), ']', '}'), + '"', + '', + 'g' + )::varchar(255)[], + ARRAY[]::varchar(255)[] + )""") batch_op.alter_column('exclude_plugins', existing_type=sa.JSON(), type_=postgresql.ARRAY(sa.VARCHAR(length=255)), - existing_nullable=False) + existing_nullable=False, + postgresql_using=""" + COALESCE( + regexp_replace( + replace(replace(exclude_plugins::text, '[', '{'), ']', '}'), + '"', + '', + 'g' + )::varchar(255)[], + ARRAY[]::varchar(255)[] + )""") with op.batch_alter_table('external_knowledge_bindings', schema=None) as batch_op: batch_op.alter_column('external_knowledge_id', From 7344adf65e03687d87c1b2d886e7621933eb79fc Mon Sep 17 00:00:00 2001 From: Coding On Star <447357187@qq.com> Date: Thu, 11 Dec 2025 14:44:12 +0800 Subject: [PATCH 25/28] feat: add Amplitude API key to Docker entrypoint script (#29477) Co-authored-by: CodingOnStar --- docker/docker-compose-template.yaml | 1 + docker/docker-compose.yaml | 1 + web/.env.example | 3 +++ .../components/base/amplitude/AmplitudeProvider.tsx | 10 ++++++++-- web/app/components/base/amplitude/index.ts | 2 +- web/app/components/base/amplitude/utils.ts | 9 +++++++++ web/docker/entrypoint.sh | 2 ++ 7 files changed, 25 insertions(+), 3 deletions(-) diff --git a/docker/docker-compose-template.yaml b/docker/docker-compose-template.yaml index b3d5cca245..c89224fa8a 100644 --- a/docker/docker-compose-template.yaml +++ b/docker/docker-compose-template.yaml @@ -136,6 +136,7 @@ services: environment: CONSOLE_API_URL: ${CONSOLE_API_URL:-} APP_API_URL: ${APP_API_URL:-} + AMPLITUDE_API_KEY: ${AMPLITUDE_API_KEY:-} NEXT_PUBLIC_COOKIE_DOMAIN: ${NEXT_PUBLIC_COOKIE_DOMAIN:-} SENTRY_DSN: ${WEB_SENTRY_DSN:-} NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0} diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index b961f6b216..160dbbec46 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -773,6 +773,7 @@ services: environment: CONSOLE_API_URL: ${CONSOLE_API_URL:-} APP_API_URL: ${APP_API_URL:-} + AMPLITUDE_API_KEY: ${AMPLITUDE_API_KEY:-} NEXT_PUBLIC_COOKIE_DOMAIN: ${NEXT_PUBLIC_COOKIE_DOMAIN:-} SENTRY_DSN: ${WEB_SENTRY_DSN:-} NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0} diff --git a/web/.env.example b/web/.env.example index eff6f77fd9..34ea7de522 100644 --- a/web/.env.example +++ b/web/.env.example @@ -70,3 +70,6 @@ NEXT_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX=false # The maximum number of tree node depth for workflow NEXT_PUBLIC_MAX_TREE_DEPTH=50 + +# The api key of amplitude +NEXT_PUBLIC_AMPLITUDE_API_KEY= diff --git a/web/app/components/base/amplitude/AmplitudeProvider.tsx b/web/app/components/base/amplitude/AmplitudeProvider.tsx index 6f2f43b614..c242326c30 100644 --- a/web/app/components/base/amplitude/AmplitudeProvider.tsx +++ b/web/app/components/base/amplitude/AmplitudeProvider.tsx @@ -11,13 +11,19 @@ export type IAmplitudeProps = { sessionReplaySampleRate?: number } +// Check if Amplitude should be enabled +export const isAmplitudeEnabled = () => { + const apiKey = process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY + return IS_CLOUD_EDITION && !!apiKey +} + const AmplitudeProvider: FC = ({ apiKey = process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY ?? '', sessionReplaySampleRate = 1, }) => { useEffect(() => { - // Only enable in Saas edition - if (!IS_CLOUD_EDITION) + // Only enable in Saas edition with valid API key + if (!isAmplitudeEnabled()) return // Initialize Amplitude diff --git a/web/app/components/base/amplitude/index.ts b/web/app/components/base/amplitude/index.ts index e447a0c5e3..acc792339e 100644 --- a/web/app/components/base/amplitude/index.ts +++ b/web/app/components/base/amplitude/index.ts @@ -1,2 +1,2 @@ -export { default } from './AmplitudeProvider' +export { default, isAmplitudeEnabled } from './AmplitudeProvider' export { resetUser, setUserId, setUserProperties, trackEvent } from './utils' diff --git a/web/app/components/base/amplitude/utils.ts b/web/app/components/base/amplitude/utils.ts index 8423c43bb2..57b96243ec 100644 --- a/web/app/components/base/amplitude/utils.ts +++ b/web/app/components/base/amplitude/utils.ts @@ -1,4 +1,5 @@ import * as amplitude from '@amplitude/analytics-browser' +import { isAmplitudeEnabled } from './AmplitudeProvider' /** * Track custom event @@ -6,6 +7,8 @@ import * as amplitude from '@amplitude/analytics-browser' * @param eventProperties Event properties (optional) */ export const trackEvent = (eventName: string, eventProperties?: Record) => { + if (!isAmplitudeEnabled()) + return amplitude.track(eventName, eventProperties) } @@ -14,6 +17,8 @@ export const trackEvent = (eventName: string, eventProperties?: Record { + if (!isAmplitudeEnabled()) + return amplitude.setUserId(userId) } @@ -22,6 +27,8 @@ export const setUserId = (userId: string) => { * @param properties User properties */ export const setUserProperties = (properties: Record) => { + if (!isAmplitudeEnabled()) + return const identifyEvent = new amplitude.Identify() Object.entries(properties).forEach(([key, value]) => { identifyEvent.set(key, value) @@ -33,5 +40,7 @@ export const setUserProperties = (properties: Record) => { * Reset user (e.g., when user logs out) */ export const resetUser = () => { + if (!isAmplitudeEnabled()) + return amplitude.reset() } diff --git a/web/docker/entrypoint.sh b/web/docker/entrypoint.sh index 3325690239..565c906624 100755 --- a/web/docker/entrypoint.sh +++ b/web/docker/entrypoint.sh @@ -25,6 +25,8 @@ export NEXT_PUBLIC_SENTRY_DSN=${SENTRY_DSN} export NEXT_PUBLIC_SITE_ABOUT=${SITE_ABOUT} export NEXT_TELEMETRY_DISABLED=${NEXT_TELEMETRY_DISABLED} +export NEXT_PUBLIC_AMPLITUDE_API_KEY=${AMPLITUDE_API_KEY} + export NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS=${TEXT_GENERATION_TIMEOUT_MS} export NEXT_PUBLIC_CSP_WHITELIST=${CSP_WHITELIST} export NEXT_PUBLIC_ALLOW_EMBED=${ALLOW_EMBED} From a30cbe3c9550aef56e7bef7de938152f8abeb348 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Thu, 11 Dec 2025 15:05:37 +0800 Subject: [PATCH 26/28] test: add debug-with-multiple-model spec (#29490) --- .../debug-with-multiple-model/index.spec.tsx | 480 ++++++++++++++++++ 1 file changed, 480 insertions(+) create mode 100644 web/app/components/app/configuration/debug/debug-with-multiple-model/index.spec.tsx diff --git a/web/app/components/app/configuration/debug/debug-with-multiple-model/index.spec.tsx b/web/app/components/app/configuration/debug/debug-with-multiple-model/index.spec.tsx new file mode 100644 index 0000000000..7607a21b07 --- /dev/null +++ b/web/app/components/app/configuration/debug/debug-with-multiple-model/index.spec.tsx @@ -0,0 +1,480 @@ +import '@testing-library/jest-dom' +import type { CSSProperties } from 'react' +import { fireEvent, render, screen } from '@testing-library/react' +import DebugWithMultipleModel from './index' +import type { DebugWithMultipleModelContextType } from './context' +import { APP_CHAT_WITH_MULTIPLE_MODEL } from '../types' +import type { ModelAndParameter } from '../types' +import type { Inputs, ModelConfig } from '@/models/debug' +import { DEFAULT_AGENT_SETTING, DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config' +import type { FeatureStoreState } from '@/app/components/base/features/store' +import type { FileEntity } from '@/app/components/base/file-uploader/types' +import type { InputForm } from '@/app/components/base/chat/chat/type' +import { AppModeEnum, ModelModeType, type PromptVariable, Resolution, TransferMethod } from '@/types/app' + +type PromptVariableWithMeta = Omit & { + type: PromptVariable['type'] | 'api' + required?: boolean + hide?: boolean +} + +const mockUseDebugConfigurationContext = jest.fn() +const mockUseFeaturesSelector = jest.fn() +const mockUseEventEmitterContext = jest.fn() +const mockUseAppStoreSelector = jest.fn() +const mockEventEmitter = { emit: jest.fn() } +const mockSetShowAppConfigureFeaturesModal = jest.fn() +let capturedChatInputProps: MockChatInputAreaProps | null = null +let modelIdCounter = 0 +let featureState: FeatureStoreState + +type MockChatInputAreaProps = { + onSend?: (message: string, files?: FileEntity[]) => void + onFeatureBarClick?: (state: boolean) => void + showFeatureBar?: boolean + showFileUpload?: boolean + inputs?: Record + inputsForm?: InputForm[] + speechToTextConfig?: unknown + visionConfig?: unknown +} + +const mockFiles: FileEntity[] = [ + { + id: 'file-1', + name: 'file.txt', + size: 10, + type: 'text/plain', + progress: 100, + transferMethod: TransferMethod.remote_url, + supportFileType: 'text', + }, +] + +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})) + +jest.mock('@/context/debug-configuration', () => ({ + __esModule: true, + useDebugConfigurationContext: () => mockUseDebugConfigurationContext(), +})) + +jest.mock('@/app/components/base/features/hooks', () => ({ + __esModule: true, + useFeatures: (selector: (state: FeatureStoreState) => unknown) => mockUseFeaturesSelector(selector), +})) + +jest.mock('@/context/event-emitter', () => ({ + __esModule: true, + useEventEmitterContextContext: () => mockUseEventEmitterContext(), +})) + +jest.mock('@/app/components/app/store', () => ({ + __esModule: true, + useStore: (selector: (state: { setShowAppConfigureFeaturesModal: typeof mockSetShowAppConfigureFeaturesModal }) => unknown) => mockUseAppStoreSelector(selector), +})) + +jest.mock('./debug-item', () => ({ + __esModule: true, + default: ({ + modelAndParameter, + className, + style, + }: { + modelAndParameter: ModelAndParameter + className?: string + style?: CSSProperties + }) => ( +
+ DebugItem-{modelAndParameter.id} +
+ ), +})) + +jest.mock('@/app/components/base/chat/chat/chat-input-area', () => ({ + __esModule: true, + default: (props: MockChatInputAreaProps) => { + capturedChatInputProps = props + return ( +
+ + +
+ ) + }, +})) + +const createFeatureState = (): FeatureStoreState => ({ + features: { + speech2text: { enabled: true }, + file: { + image: { + enabled: true, + detail: Resolution.high, + number_limits: 2, + transfer_methods: [TransferMethod.remote_url], + }, + }, + }, + setFeatures: jest.fn(), + showFeaturesModal: false, + setShowFeaturesModal: jest.fn(), +}) + +const createModelConfig = (promptVariables: PromptVariableWithMeta[] = []): ModelConfig => ({ + provider: 'OPENAI', + model_id: 'gpt-4', + mode: ModelModeType.chat, + configs: { + prompt_template: '', + prompt_variables: promptVariables as unknown as PromptVariable[], + }, + chat_prompt_config: DEFAULT_CHAT_PROMPT_CONFIG, + completion_prompt_config: DEFAULT_COMPLETION_PROMPT_CONFIG, + opening_statement: '', + more_like_this: null, + suggested_questions: [], + suggested_questions_after_answer: null, + speech_to_text: null, + text_to_speech: null, + file_upload: null, + retriever_resource: null, + sensitive_word_avoidance: null, + annotation_reply: null, + external_data_tools: [], + system_parameters: { + audio_file_size_limit: 0, + file_size_limit: 0, + image_file_size_limit: 0, + video_file_size_limit: 0, + workflow_file_upload_limit: 0, + }, + dataSets: [], + agentConfig: DEFAULT_AGENT_SETTING, +}) + +type DebugConfiguration = { + mode: AppModeEnum + inputs: Inputs + modelConfig: ModelConfig +} + +const createDebugConfiguration = (overrides: Partial = {}): DebugConfiguration => ({ + mode: AppModeEnum.CHAT, + inputs: {}, + modelConfig: createModelConfig(), + ...overrides, +}) + +const createModelAndParameter = (overrides: Partial = {}): ModelAndParameter => ({ + id: `model-${++modelIdCounter}`, + model: 'gpt-3.5-turbo', + provider: 'openai', + parameters: {}, + ...overrides, +}) + +const createProps = (overrides: Partial = {}): DebugWithMultipleModelContextType => ({ + multipleModelConfigs: [createModelAndParameter()], + onMultipleModelConfigsChange: jest.fn(), + onDebugWithMultipleModelChange: jest.fn(), + ...overrides, +}) + +const renderComponent = (props?: Partial) => { + const mergedProps = createProps(props) + return render() +} + +describe('DebugWithMultipleModel', () => { + beforeEach(() => { + jest.clearAllMocks() + capturedChatInputProps = null + modelIdCounter = 0 + featureState = createFeatureState() + mockUseFeaturesSelector.mockImplementation(selector => selector(featureState)) + mockUseEventEmitterContext.mockReturnValue({ eventEmitter: mockEventEmitter }) + mockUseAppStoreSelector.mockImplementation(selector => selector({ setShowAppConfigureFeaturesModal: mockSetShowAppConfigureFeaturesModal })) + mockUseDebugConfigurationContext.mockReturnValue(createDebugConfiguration()) + }) + + describe('chat input rendering', () => { + it('should render chat input in chat mode with transformed prompt variables and feature handler', () => { + // Arrange + const promptVariables: PromptVariableWithMeta[] = [ + { key: 'city', name: 'City', type: 'string', required: true }, + { key: 'audience', name: 'Audience', type: 'number' }, + { key: 'hidden', name: 'Hidden', type: 'select', hide: true }, + { key: 'api-only', name: 'API Only', type: 'api' }, + ] + const debugConfiguration = createDebugConfiguration({ + inputs: { audience: 'engineers' }, + modelConfig: createModelConfig(promptVariables), + }) + mockUseDebugConfigurationContext.mockReturnValue(debugConfiguration) + + // Act + renderComponent() + fireEvent.click(screen.getByRole('button', { name: /feature/i })) + + // Assert + expect(screen.getByTestId('chat-input-area')).toBeInTheDocument() + expect(capturedChatInputProps?.inputs).toEqual({ audience: 'engineers' }) + expect(capturedChatInputProps?.inputsForm).toEqual([ + expect.objectContaining({ label: 'City', variable: 'city', hide: false, required: true }), + expect.objectContaining({ label: 'Audience', variable: 'audience', hide: false, required: false }), + expect.objectContaining({ label: 'Hidden', variable: 'hidden', hide: true, required: false }), + ]) + expect(capturedChatInputProps?.showFeatureBar).toBe(true) + expect(capturedChatInputProps?.showFileUpload).toBe(false) + expect(capturedChatInputProps?.speechToTextConfig).toEqual(featureState.features.speech2text) + expect(capturedChatInputProps?.visionConfig).toEqual(featureState.features.file) + expect(mockSetShowAppConfigureFeaturesModal).toHaveBeenCalledWith(true) + }) + + it('should render chat input in agent chat mode', () => { + // Arrange + mockUseDebugConfigurationContext.mockReturnValue(createDebugConfiguration({ + mode: AppModeEnum.AGENT_CHAT, + })) + + // Act + renderComponent() + + // Assert + expect(screen.getByTestId('chat-input-area')).toBeInTheDocument() + }) + + it('should hide chat input when not in chat mode', () => { + // Arrange + mockUseDebugConfigurationContext.mockReturnValue(createDebugConfiguration({ + mode: AppModeEnum.COMPLETION, + })) + const multipleModelConfigs = [createModelAndParameter()] + + // Act + renderComponent({ multipleModelConfigs }) + + // Assert + expect(screen.queryByTestId('chat-input-area')).not.toBeInTheDocument() + expect(screen.getAllByTestId('debug-item')).toHaveLength(1) + }) + }) + + describe('sending flow', () => { + it('should emit chat event when allowed to send', () => { + // Arrange + const checkCanSend = jest.fn(() => true) + const multipleModelConfigs = [createModelAndParameter(), createModelAndParameter()] + renderComponent({ multipleModelConfigs, checkCanSend }) + + // Act + fireEvent.click(screen.getByRole('button', { name: /send/i })) + + // Assert + expect(checkCanSend).toHaveBeenCalled() + expect(mockEventEmitter.emit).toHaveBeenCalledWith({ + type: APP_CHAT_WITH_MULTIPLE_MODEL, + payload: { + message: 'test message', + files: mockFiles, + }, + }) + }) + + it('should emit when no checkCanSend is provided', () => { + renderComponent() + + fireEvent.click(screen.getByRole('button', { name: /send/i })) + + expect(mockEventEmitter.emit).toHaveBeenCalledWith({ + type: APP_CHAT_WITH_MULTIPLE_MODEL, + payload: { + message: 'test message', + files: mockFiles, + }, + }) + }) + + it('should block sending when checkCanSend returns false', () => { + // Arrange + const checkCanSend = jest.fn(() => false) + renderComponent({ checkCanSend }) + + // Act + fireEvent.click(screen.getByRole('button', { name: /send/i })) + + // Assert + expect(checkCanSend).toHaveBeenCalled() + expect(mockEventEmitter.emit).not.toHaveBeenCalled() + }) + + it('should tolerate missing event emitter without throwing', () => { + mockUseEventEmitterContext.mockReturnValue({ eventEmitter: null }) + renderComponent() + + expect(() => fireEvent.click(screen.getByRole('button', { name: /send/i }))).not.toThrow() + expect(mockEventEmitter.emit).not.toHaveBeenCalled() + }) + }) + + describe('layout sizing and positioning', () => { + const expectItemLayout = ( + element: HTMLElement, + expectation: { + width?: string + height?: string + transform: string + classes?: string[] + }, + ) => { + if (expectation.width !== undefined) + expect(element.style.width).toBe(expectation.width) + else + expect(element.style.width).toBe('') + + if (expectation.height !== undefined) + expect(element.style.height).toBe(expectation.height) + else + expect(element.style.height).toBe('') + + expect(element.style.transform).toBe(expectation.transform) + expectation.classes?.forEach(cls => expect(element).toHaveClass(cls)) + } + + it('should arrange items in two-column layout for two models', () => { + // Arrange + const multipleModelConfigs = [createModelAndParameter(), createModelAndParameter()] + + // Act + renderComponent({ multipleModelConfigs }) + const items = screen.getAllByTestId('debug-item') + + // Assert + expect(items).toHaveLength(2) + expectItemLayout(items[0], { + width: 'calc(50% - 4px - 24px)', + height: '100%', + transform: 'translateX(0) translateY(0)', + classes: ['mr-2'], + }) + expectItemLayout(items[1], { + width: 'calc(50% - 4px - 24px)', + height: '100%', + transform: 'translateX(calc(100% + 8px)) translateY(0)', + classes: [], + }) + }) + + it('should arrange items in thirds for three models', () => { + // Arrange + const multipleModelConfigs = [createModelAndParameter(), createModelAndParameter(), createModelAndParameter()] + + // Act + renderComponent({ multipleModelConfigs }) + const items = screen.getAllByTestId('debug-item') + + // Assert + expect(items).toHaveLength(3) + expectItemLayout(items[0], { + width: 'calc(33.3% - 5.33px - 16px)', + height: '100%', + transform: 'translateX(0) translateY(0)', + classes: ['mr-2'], + }) + expectItemLayout(items[1], { + width: 'calc(33.3% - 5.33px - 16px)', + height: '100%', + transform: 'translateX(calc(100% + 8px)) translateY(0)', + classes: ['mr-2'], + }) + expectItemLayout(items[2], { + width: 'calc(33.3% - 5.33px - 16px)', + height: '100%', + transform: 'translateX(calc(200% + 16px)) translateY(0)', + classes: [], + }) + }) + + it('should position items on a grid for four models', () => { + // Arrange + const multipleModelConfigs = [ + createModelAndParameter(), + createModelAndParameter(), + createModelAndParameter(), + createModelAndParameter(), + ] + + // Act + renderComponent({ multipleModelConfigs }) + const items = screen.getAllByTestId('debug-item') + + // Assert + expect(items).toHaveLength(4) + expectItemLayout(items[0], { + width: 'calc(50% - 4px - 24px)', + height: 'calc(50% - 4px)', + transform: 'translateX(0) translateY(0)', + classes: ['mr-2', 'mb-2'], + }) + expectItemLayout(items[1], { + width: 'calc(50% - 4px - 24px)', + height: 'calc(50% - 4px)', + transform: 'translateX(calc(100% + 8px)) translateY(0)', + classes: ['mb-2'], + }) + expectItemLayout(items[2], { + width: 'calc(50% - 4px - 24px)', + height: 'calc(50% - 4px)', + transform: 'translateX(0) translateY(calc(100% + 8px))', + classes: ['mr-2'], + }) + expectItemLayout(items[3], { + width: 'calc(50% - 4px - 24px)', + height: 'calc(50% - 4px)', + transform: 'translateX(calc(100% + 8px)) translateY(calc(100% + 8px))', + classes: [], + }) + }) + + it('should fall back to single column layout when only one model is provided', () => { + // Arrange + const multipleModelConfigs = [createModelAndParameter()] + + // Act + renderComponent({ multipleModelConfigs }) + const item = screen.getByTestId('debug-item') + + // Assert + expectItemLayout(item, { + transform: 'translateX(0) translateY(0)', + classes: [], + }) + }) + + it('should set scroll area height for chat modes', () => { + const { container } = renderComponent() + const scrollArea = container.querySelector('.relative.mb-3.grow.overflow-auto.px-6') as HTMLElement + expect(scrollArea).toBeInTheDocument() + expect(scrollArea.style.height).toBe('calc(100% - 60px)') + }) + + it('should set full height when chat input is hidden', () => { + mockUseDebugConfigurationContext.mockReturnValue(createDebugConfiguration({ + mode: AppModeEnum.COMPLETION, + })) + + const { container } = renderComponent() + const scrollArea = container.querySelector('.relative.mb-3.grow.overflow-auto.px-6') as HTMLElement + expect(scrollArea.style.height).toBe('100%') + }) + }) +}) From 6e802a343ed224c7e8c3275bafbcc4e94f5655e9 Mon Sep 17 00:00:00 2001 From: wangxiaolei Date: Thu, 11 Dec 2025 15:18:27 +0800 Subject: [PATCH 27/28] perf: remove the n+1 query (#29483) --- api/models/model.py | 52 +++- .../unit_tests/models/test_app_models.py | 255 ++++++++++++++++++ 2 files changed, 295 insertions(+), 12 deletions(-) diff --git a/api/models/model.py b/api/models/model.py index c8fa6fd406..c8fbdc40ec 100644 --- a/api/models/model.py +++ b/api/models/model.py @@ -835,7 +835,29 @@ class Conversation(Base): @property def status_count(self): - messages = db.session.scalars(select(Message).where(Message.conversation_id == self.id)).all() + from models.workflow import WorkflowRun + + # Get all messages with workflow_run_id for this conversation + messages = db.session.scalars( + select(Message).where(Message.conversation_id == self.id, Message.workflow_run_id.isnot(None)) + ).all() + + if not messages: + return None + + # Batch load all workflow runs in a single query, filtered by this conversation's app_id + workflow_run_ids = [msg.workflow_run_id for msg in messages if msg.workflow_run_id] + workflow_runs = {} + + if workflow_run_ids: + workflow_runs_query = db.session.scalars( + select(WorkflowRun).where( + WorkflowRun.id.in_(workflow_run_ids), + WorkflowRun.app_id == self.app_id, # Filter by this conversation's app_id + ) + ).all() + workflow_runs = {run.id: run for run in workflow_runs_query} + status_counts = { WorkflowExecutionStatus.RUNNING: 0, WorkflowExecutionStatus.SUCCEEDED: 0, @@ -845,18 +867,24 @@ class Conversation(Base): } for message in messages: - if message.workflow_run: - status_counts[WorkflowExecutionStatus(message.workflow_run.status)] += 1 + # Guard against None to satisfy type checker and avoid invalid dict lookups + if message.workflow_run_id is None: + continue + workflow_run = workflow_runs.get(message.workflow_run_id) + if not workflow_run: + continue - return ( - { - "success": status_counts[WorkflowExecutionStatus.SUCCEEDED], - "failed": status_counts[WorkflowExecutionStatus.FAILED], - "partial_success": status_counts[WorkflowExecutionStatus.PARTIAL_SUCCEEDED], - } - if messages - else None - ) + try: + status_counts[WorkflowExecutionStatus(workflow_run.status)] += 1 + except (ValueError, KeyError): + # Handle invalid status values gracefully + pass + + return { + "success": status_counts[WorkflowExecutionStatus.SUCCEEDED], + "failed": status_counts[WorkflowExecutionStatus.FAILED], + "partial_success": status_counts[WorkflowExecutionStatus.PARTIAL_SUCCEEDED], + } @property def first_message(self): diff --git a/api/tests/unit_tests/models/test_app_models.py b/api/tests/unit_tests/models/test_app_models.py index 268ba1282a..e35788660d 100644 --- a/api/tests/unit_tests/models/test_app_models.py +++ b/api/tests/unit_tests/models/test_app_models.py @@ -1149,3 +1149,258 @@ class TestModelIntegration: # Assert assert site.app_id == app.id assert app.enable_site is True + + +class TestConversationStatusCount: + """Test suite for Conversation.status_count property N+1 query fix.""" + + def test_status_count_no_messages(self): + """Test status_count returns None when conversation has no messages.""" + # Arrange + conversation = Conversation( + app_id=str(uuid4()), + mode=AppMode.CHAT, + name="Test Conversation", + status="normal", + from_source="api", + ) + conversation.id = str(uuid4()) + + # Mock the database query to return no messages + with patch("models.model.db.session.scalars") as mock_scalars: + mock_scalars.return_value.all.return_value = [] + + # Act + result = conversation.status_count + + # Assert + assert result is None + + def test_status_count_messages_without_workflow_runs(self): + """Test status_count when messages have no workflow_run_id.""" + # Arrange + app_id = str(uuid4()) + conversation_id = str(uuid4()) + + conversation = Conversation( + app_id=app_id, + mode=AppMode.CHAT, + name="Test Conversation", + status="normal", + from_source="api", + ) + conversation.id = conversation_id + + # Mock the database query to return no messages with workflow_run_id + with patch("models.model.db.session.scalars") as mock_scalars: + mock_scalars.return_value.all.return_value = [] + + # Act + result = conversation.status_count + + # Assert + assert result is None + + def test_status_count_batch_loading_implementation(self): + """Test that status_count uses batch loading instead of N+1 queries.""" + # Arrange + from core.workflow.enums import WorkflowExecutionStatus + + app_id = str(uuid4()) + conversation_id = str(uuid4()) + + # Create workflow run IDs + workflow_run_id_1 = str(uuid4()) + workflow_run_id_2 = str(uuid4()) + workflow_run_id_3 = str(uuid4()) + + conversation = Conversation( + app_id=app_id, + mode=AppMode.CHAT, + name="Test Conversation", + status="normal", + from_source="api", + ) + conversation.id = conversation_id + + # Mock messages with workflow_run_id + mock_messages = [ + MagicMock( + conversation_id=conversation_id, + workflow_run_id=workflow_run_id_1, + ), + MagicMock( + conversation_id=conversation_id, + workflow_run_id=workflow_run_id_2, + ), + MagicMock( + conversation_id=conversation_id, + workflow_run_id=workflow_run_id_3, + ), + ] + + # Mock workflow runs with different statuses + mock_workflow_runs = [ + MagicMock( + id=workflow_run_id_1, + status=WorkflowExecutionStatus.SUCCEEDED.value, + app_id=app_id, + ), + MagicMock( + id=workflow_run_id_2, + status=WorkflowExecutionStatus.FAILED.value, + app_id=app_id, + ), + MagicMock( + id=workflow_run_id_3, + status=WorkflowExecutionStatus.PARTIAL_SUCCEEDED.value, + app_id=app_id, + ), + ] + + # Track database calls + calls_made = [] + + def mock_scalars(query): + calls_made.append(str(query)) + mock_result = MagicMock() + + # Return messages for the first query (messages with workflow_run_id) + if "messages" in str(query) and "conversation_id" in str(query): + mock_result.all.return_value = mock_messages + # Return workflow runs for the batch query + elif "workflow_runs" in str(query): + mock_result.all.return_value = mock_workflow_runs + else: + mock_result.all.return_value = [] + + return mock_result + + # Act & Assert + with patch("models.model.db.session.scalars", side_effect=mock_scalars): + result = conversation.status_count + + # Verify only 2 database queries were made (not N+1) + assert len(calls_made) == 2, f"Expected 2 queries, got {len(calls_made)}: {calls_made}" + + # Verify the first query gets messages + assert "messages" in calls_made[0] + assert "conversation_id" in calls_made[0] + + # Verify the second query batch loads workflow runs with proper filtering + assert "workflow_runs" in calls_made[1] + assert "app_id" in calls_made[1] # Security filter applied + assert "IN" in calls_made[1] # Batch loading with IN clause + + # Verify correct status counts + assert result["success"] == 1 # One SUCCEEDED + assert result["failed"] == 1 # One FAILED + assert result["partial_success"] == 1 # One PARTIAL_SUCCEEDED + + def test_status_count_app_id_filtering(self): + """Test that status_count filters workflow runs by app_id for security.""" + # Arrange + app_id = str(uuid4()) + other_app_id = str(uuid4()) + conversation_id = str(uuid4()) + workflow_run_id = str(uuid4()) + + conversation = Conversation( + app_id=app_id, + mode=AppMode.CHAT, + name="Test Conversation", + status="normal", + from_source="api", + ) + conversation.id = conversation_id + + # Mock message with workflow_run_id + mock_messages = [ + MagicMock( + conversation_id=conversation_id, + workflow_run_id=workflow_run_id, + ), + ] + + calls_made = [] + + def mock_scalars(query): + calls_made.append(str(query)) + mock_result = MagicMock() + + if "messages" in str(query): + mock_result.all.return_value = mock_messages + elif "workflow_runs" in str(query): + # Return empty list because no workflow run matches the correct app_id + mock_result.all.return_value = [] # Workflow run filtered out by app_id + else: + mock_result.all.return_value = [] + + return mock_result + + # Act + with patch("models.model.db.session.scalars", side_effect=mock_scalars): + result = conversation.status_count + + # Assert - query should include app_id filter + workflow_query = calls_made[1] + assert "app_id" in workflow_query + + # Since workflow run has wrong app_id, it shouldn't be included in counts + assert result["success"] == 0 + assert result["failed"] == 0 + assert result["partial_success"] == 0 + + def test_status_count_handles_invalid_workflow_status(self): + """Test that status_count gracefully handles invalid workflow status values.""" + # Arrange + app_id = str(uuid4()) + conversation_id = str(uuid4()) + workflow_run_id = str(uuid4()) + + conversation = Conversation( + app_id=app_id, + mode=AppMode.CHAT, + name="Test Conversation", + status="normal", + from_source="api", + ) + conversation.id = conversation_id + + mock_messages = [ + MagicMock( + conversation_id=conversation_id, + workflow_run_id=workflow_run_id, + ), + ] + + # Mock workflow run with invalid status + mock_workflow_runs = [ + MagicMock( + id=workflow_run_id, + status="invalid_status", # Invalid status that should raise ValueError + app_id=app_id, + ), + ] + + with patch("models.model.db.session.scalars") as mock_scalars: + # Mock the messages query + def mock_scalars_side_effect(query): + mock_result = MagicMock() + if "messages" in str(query): + mock_result.all.return_value = mock_messages + elif "workflow_runs" in str(query): + mock_result.all.return_value = mock_workflow_runs + else: + mock_result.all.return_value = [] + return mock_result + + mock_scalars.side_effect = mock_scalars_side_effect + + # Act - should not raise exception + result = conversation.status_count + + # Assert - should handle invalid status gracefully + assert result["success"] == 0 + assert result["failed"] == 0 + assert result["partial_success"] == 0 From f20a2d158689d357a7c9b9344bfefab73a813156 Mon Sep 17 00:00:00 2001 From: Coding On Star <447357187@qq.com> Date: Thu, 11 Dec 2025 15:21:52 +0800 Subject: [PATCH 28/28] chore: add placeholder for Amplitude API key in .env.example (#29489) Co-authored-by: CodingOnStar --- docker/.env.example | 3 +++ docker/docker-compose.yaml | 1 + web/.env.example | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docker/.env.example b/docker/.env.example index 85e8b1dc7f..04088b72a8 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -1432,3 +1432,6 @@ WORKFLOW_SCHEDULE_MAX_DISPATCH_PER_TICK=0 # Tenant isolated task queue configuration TENANT_ISOLATED_TASK_CONCURRENCY=1 + +# The API key of amplitude +AMPLITUDE_API_KEY= diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 160dbbec46..68f5726797 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -635,6 +635,7 @@ x-shared-env: &shared-api-worker-env WORKFLOW_SCHEDULE_POLLER_BATCH_SIZE: ${WORKFLOW_SCHEDULE_POLLER_BATCH_SIZE:-100} WORKFLOW_SCHEDULE_MAX_DISPATCH_PER_TICK: ${WORKFLOW_SCHEDULE_MAX_DISPATCH_PER_TICK:-0} TENANT_ISOLATED_TASK_CONCURRENCY: ${TENANT_ISOLATED_TASK_CONCURRENCY:-1} + AMPLITUDE_API_KEY: ${AMPLITUDE_API_KEY:-} services: # Init container to fix permissions diff --git a/web/.env.example b/web/.env.example index 34ea7de522..b488c31057 100644 --- a/web/.env.example +++ b/web/.env.example @@ -71,5 +71,5 @@ NEXT_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX=false # The maximum number of tree node depth for workflow NEXT_PUBLIC_MAX_TREE_DEPTH=50 -# The api key of amplitude +# The API key of amplitude NEXT_PUBLIC_AMPLITUDE_API_KEY=