From 0d2dda7e774fa281508700f01c82f88e86260d73 Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 10 Dec 2025 11:45:18 +0800 Subject: [PATCH 1/6] feat: add modals --- .../billing/plan-upgrade-modal/index.tsx | 79 +++++++++++++++++++ .../plan-upgrade-modal/style.module.css | 30 +++++++ .../datasets/create/step-one/index.tsx | 11 +++ 3 files changed, 120 insertions(+) create mode 100644 web/app/components/billing/plan-upgrade-modal/index.tsx create mode 100644 web/app/components/billing/plan-upgrade-modal/style.module.css diff --git a/web/app/components/billing/plan-upgrade-modal/index.tsx b/web/app/components/billing/plan-upgrade-modal/index.tsx new file mode 100644 index 0000000000..7dfce468d0 --- /dev/null +++ b/web/app/components/billing/plan-upgrade-modal/index.tsx @@ -0,0 +1,79 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import Modal from '@/app/components/base/modal' +import Button from '@/app/components/base/button' +import { TriggerAll } from '@/app/components/base/icons/src/vender/workflow' +import UpgradeBtn from '@/app/components/billing/upgrade-btn' +import styles from './style.module.css' + +type Props = { + Icon?: React.ComponentType> + title: string + description: string + extraInfo?: React.ReactNode + show: boolean + onClose: () => void + onUpgrade?: () => void +} + +const PlanUpgradeModal: FC = ({ + Icon = TriggerAll, + title, + description, + extraInfo, + show, + onClose: onClose, + onUpgrade, +}) => { + const { t } = useTranslation() + // const { plan } = useProviderContext() + return ( + +
+
+
+
+ +
+
+
+ {title} +
+
+ {description} +
+
+ {extraInfo} +
+
+ +
+ + +
+ + ) +} + +export default React.memo(PlanUpgradeModal) diff --git a/web/app/components/billing/plan-upgrade-modal/style.module.css b/web/app/components/billing/plan-upgrade-modal/style.module.css new file mode 100644 index 0000000000..e8e86719e6 --- /dev/null +++ b/web/app/components/billing/plan-upgrade-modal/style.module.css @@ -0,0 +1,30 @@ +.surface { + border: 0.5px solid var(--color-components-panel-border, rgba(16, 24, 40, 0.08)); + background: + linear-gradient(109deg, var(--color-background-section, #f9fafb) 0%, var(--color-background-section-burn, #f2f4f7) 100%), + var(--color-components-panel-bg, #fff); +} + +.heroOverlay { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='54' height='54' fill='none'%3E%3Crect x='1' y='1' width='48' height='48' rx='12' stroke='rgba(16, 24, 40, 0.3)' stroke-width='1' opacity='0.08'/%3E%3C/svg%3E"); + background-size: 54px 54px; + background-position: 31px -23px; + background-repeat: repeat; + mask-image: linear-gradient(180deg, rgba(255, 255, 255, 1) 45%, rgba(255, 255, 255, 0) 75%); + -webkit-mask-image: linear-gradient(180deg, rgba(255, 255, 255, 1) 45%, rgba(255, 255, 255, 0) 75%); +} + +.icon { + border: 0.5px solid transparent; + background: + linear-gradient(180deg, var(--color-components-avatar-bg-mask-stop-0, rgba(255, 255, 255, 0.12)) 0%, var(--color-components-avatar-bg-mask-stop-100, rgba(255, 255, 255, 0.08)) 100%), + var(--color-util-colors-blue-brand-blue-brand-500, #296dff); + box-shadow: 0 10px 20px color-mix(in srgb, var(--color-util-colors-blue-brand-blue-brand-500, #296dff) 35%, transparent); +} + +.highlight { + background: linear-gradient(97deg, var(--color-components-input-border-active-prompt-1, rgba(11, 165, 236, 0.95)) -4%, var(--color-components-input-border-active-prompt-2, rgba(21, 90, 239, 0.95)) 45%); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; +} diff --git a/web/app/components/datasets/create/step-one/index.tsx b/web/app/components/datasets/create/step-one/index.tsx index cab1637661..893f9ff07d 100644 --- a/web/app/components/datasets/create/step-one/index.tsx +++ b/web/app/components/datasets/create/step-one/index.tsx @@ -22,6 +22,8 @@ import classNames from '@/utils/classnames' import { ENABLE_WEBSITE_FIRECRAWL, ENABLE_WEBSITE_JINAREADER, ENABLE_WEBSITE_WATERCRAWL } from '@/config' import NotionConnector from '@/app/components/base/notion-connector' import type { DataSourceAuth } from '@/app/components/header/account-setting/data-source-page-new/types' +import PlanUpgradeModal from '@/app/components/billing/plan-upgrade-modal' +import { noop } from 'lodash-es' type IStepOneProps = { datasetId?: string @@ -330,6 +332,15 @@ const StepOne = ({ /> )} {currentWebsite && } + { + + }
From be94274fbdb2e1631b0473069610c71f4959d203 Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 10 Dec 2025 14:09:56 +0800 Subject: [PATCH 2/6] chore: enchance popup modal --- .../assets/vender/other/square-checklist.svg | 3 ++ .../src/vender/other/SquareChecklist.json | 26 +++++++++++++ .../src/vender/other/SquareChecklist.tsx | 20 ++++++++++ .../base/icons/src/vender/other/index.ts | 1 + .../billing/plan-upgrade-modal/index.tsx | 39 +++++++++++-------- .../plan-upgrade-modal/style.module.css | 1 - .../datasets/create/step-one/index.tsx | 14 ++++--- 7 files changed, 82 insertions(+), 22 deletions(-) create mode 100644 web/app/components/base/icons/assets/vender/other/square-checklist.svg create mode 100644 web/app/components/base/icons/src/vender/other/SquareChecklist.json create mode 100644 web/app/components/base/icons/src/vender/other/SquareChecklist.tsx diff --git a/web/app/components/base/icons/assets/vender/other/square-checklist.svg b/web/app/components/base/icons/assets/vender/other/square-checklist.svg new file mode 100644 index 0000000000..eaca7dfdea --- /dev/null +++ b/web/app/components/base/icons/assets/vender/other/square-checklist.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/app/components/base/icons/src/vender/other/SquareChecklist.json b/web/app/components/base/icons/src/vender/other/SquareChecklist.json new file mode 100644 index 0000000000..2295cf3599 --- /dev/null +++ b/web/app/components/base/icons/src/vender/other/SquareChecklist.json @@ -0,0 +1,26 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "24", + "height": "24", + "viewBox": "0 0 24 24", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M19 6C19 5.44771 18.5523 5 18 5H6C5.44771 5 5 5.44771 5 6V18C5 18.5523 5.44771 19 6 19H18C18.5523 19 19 18.5523 19 18V6ZM9.73926 13.1533C10.0706 12.7115 10.6978 12.6218 11.1396 12.9531C11.5815 13.2845 11.6712 13.9117 11.3398 14.3535L9.46777 16.8486C9.14935 17.2732 8.55487 17.3754 8.11328 17.0811L6.98828 16.3311C6.52878 16.0247 6.40465 15.4039 6.71094 14.9443C7.01729 14.4848 7.63813 14.3606 8.09766 14.667L8.43457 14.8916L9.73926 13.1533ZM16 14C16.5523 14 17 14.4477 17 15C17 15.5523 16.5523 16 16 16H14C13.4477 16 13 15.5523 13 15C13 14.4477 13.4477 14 14 14H16ZM9.73926 7.15234C10.0706 6.71052 10.6978 6.62079 11.1396 6.95215C11.5815 7.28352 11.6712 7.91071 11.3398 8.35254L9.46777 10.8477C9.14936 11.2722 8.55487 11.3744 8.11328 11.0801L6.98828 10.3301C6.52884 10.0238 6.40476 9.40286 6.71094 8.94336C7.0173 8.48384 7.63814 8.35965 8.09766 8.66602L8.43457 8.89062L9.73926 7.15234ZM16.0576 8C16.6099 8 17.0576 8.44772 17.0576 9C17.0576 9.55228 16.6099 10 16.0576 10H14.0576C13.5055 9.99985 13.0576 9.55219 13.0576 9C13.0576 8.44781 13.5055 8.00015 14.0576 8H16.0576ZM21 18C21 19.6569 19.6569 21 18 21H6C4.34315 21 3 19.6569 3 18V6C3 4.34315 4.34315 3 6 3H18C19.6569 3 21 4.34315 21 6V18Z", + "fill": "currentColor" + }, + "children": [] + } + ] + }, + "name": "SquareChecklist" +} diff --git a/web/app/components/base/icons/src/vender/other/SquareChecklist.tsx b/web/app/components/base/icons/src/vender/other/SquareChecklist.tsx new file mode 100644 index 0000000000..f927fa88d2 --- /dev/null +++ b/web/app/components/base/icons/src/vender/other/SquareChecklist.tsx @@ -0,0 +1,20 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './SquareChecklist.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconData } from '@/app/components/base/icons/IconBase' + +const Icon = ( + { + ref, + ...props + }: React.SVGProps & { + ref?: React.RefObject>; + }, +) => + +Icon.displayName = 'SquareChecklist' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/other/index.ts b/web/app/components/base/icons/src/vender/other/index.ts index 89cbe9033d..0ca5f22bcf 100644 --- a/web/app/components/base/icons/src/vender/other/index.ts +++ b/web/app/components/base/icons/src/vender/other/index.ts @@ -6,3 +6,4 @@ export { default as Mcp } from './Mcp' export { default as NoToolPlaceholder } from './NoToolPlaceholder' export { default as Openai } from './Openai' export { default as ReplayLine } from './ReplayLine' +export { default as SquareChecklist } from './SquareChecklist' diff --git a/web/app/components/billing/plan-upgrade-modal/index.tsx b/web/app/components/billing/plan-upgrade-modal/index.tsx index 7dfce468d0..79f354333c 100644 --- a/web/app/components/billing/plan-upgrade-modal/index.tsx +++ b/web/app/components/billing/plan-upgrade-modal/index.tsx @@ -1,12 +1,13 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' -import { TriggerAll } from '@/app/components/base/icons/src/vender/workflow' import UpgradeBtn from '@/app/components/billing/upgrade-btn' import styles from './style.module.css' +import { SquareChecklist } from '../../base/icons/src/vender/other' +import { useModalContext } from '@/context/modal-context' type Props = { Icon?: React.ComponentType> @@ -19,38 +20,44 @@ type Props = { } const PlanUpgradeModal: FC = ({ - Icon = TriggerAll, + Icon = SquareChecklist, title, description, extraInfo, show, - onClose: onClose, + onClose, onUpgrade, }) => { const { t } = useTranslation() - // const { plan } = useProviderContext() + const { setShowPricingModal } = useModalContext() + + const handleUpgrade = useCallback(() => { + onClose() + onUpgrade ? onUpgrade() : setShowPricingModal() + }, [onClose, onUpgrade, setShowPricingModal]) + return ( -
+
-
-
- +
+
+
-
-
+
+
{title}
-
+
{description}
@@ -58,7 +65,7 @@ const PlanUpgradeModal: FC = ({
-
+
diff --git a/web/app/components/billing/plan-upgrade-modal/style.module.css b/web/app/components/billing/plan-upgrade-modal/style.module.css index e8e86719e6..50ad488388 100644 --- a/web/app/components/billing/plan-upgrade-modal/style.module.css +++ b/web/app/components/billing/plan-upgrade-modal/style.module.css @@ -19,7 +19,6 @@ background: linear-gradient(180deg, var(--color-components-avatar-bg-mask-stop-0, rgba(255, 255, 255, 0.12)) 0%, var(--color-components-avatar-bg-mask-stop-100, rgba(255, 255, 255, 0.08)) 100%), var(--color-util-colors-blue-brand-blue-brand-500, #296dff); - box-shadow: 0 10px 20px color-mix(in srgb, var(--color-util-colors-blue-brand-blue-brand-500, #296dff) 35%, transparent); } .highlight { diff --git a/web/app/components/datasets/create/step-one/index.tsx b/web/app/components/datasets/create/step-one/index.tsx index 893f9ff07d..53f55a37f2 100644 --- a/web/app/components/datasets/create/step-one/index.tsx +++ b/web/app/components/datasets/create/step-one/index.tsx @@ -23,7 +23,7 @@ import { ENABLE_WEBSITE_FIRECRAWL, ENABLE_WEBSITE_JINAREADER, ENABLE_WEBSITE_WAT import NotionConnector from '@/app/components/base/notion-connector' import type { DataSourceAuth } from '@/app/components/header/account-setting/data-source-page-new/types' import PlanUpgradeModal from '@/app/components/billing/plan-upgrade-modal' -import { noop } from 'lodash-es' +import { useBoolean } from 'ahooks' type IStepOneProps = { datasetId?: string @@ -132,6 +132,11 @@ const StepOne = ({ return authedDataSourceList.find(item => item.provider === 'notion_datasource')?.credentials_list || [] }, [authedDataSourceList]) + const [isShowPlanUpgradeModal, { + // setTrue: showPlanUpgradeModal, + setFalse: hidePlanUpgradeModal, + }] = useBoolean(true) + return (
@@ -332,15 +337,14 @@ const StepOne = ({ /> )} {currentWebsite && } - { + {isShowPlanUpgradeModal && ( - } + )}
From bacc9a7970e8587b85aae8e51e2083b9f487ead9 Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 10 Dec 2025 14:32:34 +0800 Subject: [PATCH 3/6] feat: not to next if multi sent in sandbox --- .../datasets/create/step-one/index.tsx | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/web/app/components/datasets/create/step-one/index.tsx b/web/app/components/datasets/create/step-one/index.tsx index 53f55a37f2..3caeae965a 100644 --- a/web/app/components/datasets/create/step-one/index.tsx +++ b/web/app/components/datasets/create/step-one/index.tsx @@ -54,7 +54,7 @@ const StepOne = ({ dataSourceTypeDisable, changeType, onSetting, - onStepChange, + onStepChange: doOnStepChange, files, updateFileList, updateFile, @@ -113,6 +113,31 @@ const StepOne = ({ const isVectorSpaceFull = plan.usage.vectorSpace >= plan.total.vectorSpace const isShowVectorSpaceFull = (allFileLoaded || hasNotin) && isVectorSpaceFull && enableBilling const notSupportBatchUpload = enableBilling && plan.type === 'sandbox' + + const [isShowPlanUpgradeModal, { + setTrue: showPlanUpgradeModal, + setFalse: hidePlanUpgradeModal, + }] = useBoolean(false) + const onStepChange = useCallback(() => { + if (notSupportBatchUpload) { + let isMultiple = false + if (dataSourceType === DataSourceType.FILE && files.length > 1) + isMultiple = true + + if (dataSourceType === DataSourceType.NOTION && notionPages.length > 1) + isMultiple = true + + if (dataSourceType === DataSourceType.WEB && websitePages.length > 1) + isMultiple = true + + if (isMultiple) { + showPlanUpgradeModal() + return + } + } + doOnStepChange() + }, [dataSourceType, doOnStepChange, files.length, notSupportBatchUpload, notionPages.length, showPlanUpgradeModal, websitePages.length]) + const nextDisabled = useMemo(() => { if (!files.length) return true @@ -132,11 +157,6 @@ const StepOne = ({ return authedDataSourceList.find(item => item.provider === 'notion_datasource')?.credentials_list || [] }, [authedDataSourceList]) - const [isShowPlanUpgradeModal, { - // setTrue: showPlanUpgradeModal, - setFalse: hidePlanUpgradeModal, - }] = useBoolean(true) - return (
From f7a9aadc9825adaca59131c5af80e3d20b00b321 Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 10 Dec 2025 14:41:37 +0800 Subject: [PATCH 4/6] chore: i18n --- web/app/components/datasets/create/step-one/index.tsx | 4 ++-- web/i18n/en-US/billing.ts | 6 ++++++ web/i18n/ja-JP/billing.ts | 6 ++++++ web/i18n/zh-Hans/billing.ts | 6 ++++++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/web/app/components/datasets/create/step-one/index.tsx b/web/app/components/datasets/create/step-one/index.tsx index 3caeae965a..523bf6e141 100644 --- a/web/app/components/datasets/create/step-one/index.tsx +++ b/web/app/components/datasets/create/step-one/index.tsx @@ -361,8 +361,8 @@ const StepOne = ({ )}
diff --git a/web/i18n/en-US/billing.ts b/web/i18n/en-US/billing.ts index 2531e5831a..9c88acf4d0 100644 --- a/web/i18n/en-US/billing.ts +++ b/web/i18n/en-US/billing.ts @@ -221,6 +221,12 @@ const translation = { fullTipLine2: 'annotate more conversations.', quotaTitle: 'Annotation Reply Quota', }, + upgrade: { + uploadMultiplePages: { + title: 'Upgrade to upload multiple pages at once', + description: 'You’ve reached the upload limit — only one page can be selected and uploaded at a time on your current plan.', + }, + }, } export default translation diff --git a/web/i18n/ja-JP/billing.ts b/web/i18n/ja-JP/billing.ts index 97fa4eb0e6..881e3797b6 100644 --- a/web/i18n/ja-JP/billing.ts +++ b/web/i18n/ja-JP/billing.ts @@ -202,6 +202,12 @@ const translation = { quotaTitle: '注釈返信クォータ', }, teamMembers: 'チームメンバー', + upgrade: { + uploadMultiplePages: { + title: '複数ページを一度にアップロードするにはアップグレード', + description: '現在のプランではアップロード上限に達しています。1回の操作で選択・アップロードできるページは1つのみです。', + }, + }, } export default translation diff --git a/web/i18n/zh-Hans/billing.ts b/web/i18n/zh-Hans/billing.ts index b404240b3d..dd319ef964 100644 --- a/web/i18n/zh-Hans/billing.ts +++ b/web/i18n/zh-Hans/billing.ts @@ -202,6 +202,12 @@ const translation = { quotaTitle: '标注的配额', }, teamMembers: '团队成员', + upgrade: { + uploadMultiplePages: { + title: '升级以一次性上传多个页面', + description: '您已达到当前套餐的上传限制 —— 该套餐每次只能选择并上传 1 个页面。', + }, + }, } export default translation From 5bbc626b5ee58e8fe6191c17c0e4e0b5aac238b5 Mon Sep 17 00:00:00 2001 From: yyh Date: Wed, 10 Dec 2025 15:09:56 +0800 Subject: [PATCH 5/6] refactor: simpilify trigger events limit modal css and props --- .../index.module.css | 1 - .../trigger-events-limit-modal/index.tsx | 34 ++++++++----------- .../hooks/use-trigger-events-limit-modal.ts | 2 -- web/context/modal-context.test.tsx | 9 +++-- web/context/modal-context.tsx | 3 +- 5 files changed, 20 insertions(+), 29 deletions(-) diff --git a/web/app/components/billing/trigger-events-limit-modal/index.module.css b/web/app/components/billing/trigger-events-limit-modal/index.module.css index e8e86719e6..50ad488388 100644 --- a/web/app/components/billing/trigger-events-limit-modal/index.module.css +++ b/web/app/components/billing/trigger-events-limit-modal/index.module.css @@ -19,7 +19,6 @@ background: linear-gradient(180deg, var(--color-components-avatar-bg-mask-stop-0, rgba(255, 255, 255, 0.12)) 0%, var(--color-components-avatar-bg-mask-stop-100, rgba(255, 255, 255, 0.08)) 100%), var(--color-util-colors-blue-brand-blue-brand-500, #296dff); - box-shadow: 0 10px 20px color-mix(in srgb, var(--color-util-colors-blue-brand-blue-brand-500, #296dff) 35%, transparent); } .highlight { diff --git a/web/app/components/billing/trigger-events-limit-modal/index.tsx b/web/app/components/billing/trigger-events-limit-modal/index.tsx index c1065a7868..9434e12441 100644 --- a/web/app/components/billing/trigger-events-limit-modal/index.tsx +++ b/web/app/components/billing/trigger-events-limit-modal/index.tsx @@ -7,22 +7,20 @@ import Button from '@/app/components/base/button' import { TriggerAll } from '@/app/components/base/icons/src/vender/workflow' import UsageInfo from '@/app/components/billing/usage-info' import UpgradeBtn from '@/app/components/billing/upgrade-btn' -import type { Plan } from '@/app/components/billing/type' import styles from './index.module.css' type Props = { show: boolean - onDismiss: () => void + onClose: () => void onUpgrade: () => void usage: number total: number resetInDays?: number - planType: Plan } const TriggerEventsLimitModal: FC = ({ show, - onDismiss, + onClose, onUpgrade, usage, total, @@ -33,30 +31,30 @@ const TriggerEventsLimitModal: FC = ({ return ( -
+
-
-
- +
+
+
-
-
+
+
{t('billing.triggerLimitModal.title')}
-
+
{t('billing.triggerLimitModal.description')}
= ({
-
+
diff --git a/web/context/hooks/use-trigger-events-limit-modal.ts b/web/context/hooks/use-trigger-events-limit-modal.ts index b55501ffaf..ac02acc025 100644 --- a/web/context/hooks/use-trigger-events-limit-modal.ts +++ b/web/context/hooks/use-trigger-events-limit-modal.ts @@ -9,7 +9,6 @@ export type TriggerEventsLimitModalPayload = { usage: number total: number resetInDays?: number - planType: Plan storageKey?: string persistDismiss?: boolean } @@ -98,7 +97,6 @@ export const useTriggerEventsLimitModal = ({ payload: { usage: usage.triggerEvents, total: total.triggerEvents, - planType: type, resetInDays: triggerResetInDays, storageKey, persistDismiss, diff --git a/web/context/modal-context.test.tsx b/web/context/modal-context.test.tsx index f7e65bac6f..f929457180 100644 --- a/web/context/modal-context.test.tsx +++ b/web/context/modal-context.test.tsx @@ -31,7 +31,7 @@ const triggerEventsLimitModalMock = jest.fn((props: any) => { latestTriggerEventsModalProps = props return (
- +
) @@ -115,11 +115,10 @@ describe('ModalContextProvider trigger events limit modal', () => { usage: 3000, total: 3000, resetInDays: 5, - planType: Plan.professional, }) act(() => { - latestTriggerEventsModalProps.onDismiss() + latestTriggerEventsModalProps.onClose() }) await waitFor(() => expect(screen.queryByTestId('trigger-limit-modal')).not.toBeInTheDocument()) @@ -149,7 +148,7 @@ describe('ModalContextProvider trigger events limit modal', () => { await waitFor(() => expect(screen.getByTestId('trigger-limit-modal')).toBeInTheDocument()) act(() => { - latestTriggerEventsModalProps.onDismiss() + latestTriggerEventsModalProps.onClose() }) await waitFor(() => expect(screen.queryByTestId('trigger-limit-modal')).not.toBeInTheDocument()) @@ -177,7 +176,7 @@ describe('ModalContextProvider trigger events limit modal', () => { await waitFor(() => expect(screen.getByTestId('trigger-limit-modal')).toBeInTheDocument()) act(() => { - latestTriggerEventsModalProps.onDismiss() + latestTriggerEventsModalProps.onClose() }) await waitFor(() => expect(screen.queryByTestId('trigger-limit-modal')).not.toBeInTheDocument()) diff --git a/web/context/modal-context.tsx b/web/context/modal-context.tsx index 082b0f9c58..7f08045993 100644 --- a/web/context/modal-context.tsx +++ b/web/context/modal-context.tsx @@ -485,9 +485,8 @@ export const ModalContextProvider = ({ show usage={showTriggerEventsLimitModal.payload.usage} total={showTriggerEventsLimitModal.payload.total} - planType={showTriggerEventsLimitModal.payload.planType} resetInDays={showTriggerEventsLimitModal.payload.resetInDays} - onDismiss={() => { + onClose={() => { persistTriggerEventsLimitModalDismiss() setShowTriggerEventsLimitModal(null) }} From b16f87c9b6b45a8bea096488e30b2d9fd74fa34d Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 10 Dec 2025 15:23:39 +0800 Subject: [PATCH 6/6] feat: can add segement check --- .../components/base/premium-badge/index.tsx | 3 +- .../billing/plan-upgrade-modal/index.tsx | 3 +- .../components/billing/upgrade-btn/index.tsx | 5 ++- .../documents/detail/segment-add/index.tsx | 37 +++++++++++++++++-- web/i18n/en-US/billing.ts | 4 ++ web/i18n/ja-JP/billing.ts | 4 ++ web/i18n/zh-Hans/billing.ts | 4 ++ 7 files changed, 52 insertions(+), 8 deletions(-) diff --git a/web/app/components/base/premium-badge/index.tsx b/web/app/components/base/premium-badge/index.tsx index bdae8a0cba..7bf85cdcc3 100644 --- a/web/app/components/base/premium-badge/index.tsx +++ b/web/app/components/base/premium-badge/index.tsx @@ -12,6 +12,7 @@ const PremiumBadgeVariants = cva( size: { s: 'premium-badge-s', m: 'premium-badge-m', + custom: '', }, color: { blue: 'premium-badge-blue', @@ -33,7 +34,7 @@ const PremiumBadgeVariants = cva( ) type PremiumBadgeProps = { - size?: 's' | 'm' + size?: 's' | 'm' | 'custom' color?: 'blue' | 'indigo' | 'gray' | 'orange' allowHover?: boolean styleCss?: CSSProperties diff --git a/web/app/components/billing/plan-upgrade-modal/index.tsx b/web/app/components/billing/plan-upgrade-modal/index.tsx index 79f354333c..4f5d1ed3a6 100644 --- a/web/app/components/billing/plan-upgrade-modal/index.tsx +++ b/web/app/components/billing/plan-upgrade-modal/index.tsx @@ -72,9 +72,10 @@ const PlanUpgradeModal: FC = ({ {t('billing.triggerLimitModal.dismiss')} diff --git a/web/app/components/billing/upgrade-btn/index.tsx b/web/app/components/billing/upgrade-btn/index.tsx index d576e07f3e..b70daeb2e6 100644 --- a/web/app/components/billing/upgrade-btn/index.tsx +++ b/web/app/components/billing/upgrade-btn/index.tsx @@ -11,7 +11,7 @@ type Props = { className?: string style?: CSSProperties isFull?: boolean - size?: 'md' | 'lg' + size?: 's' | 'm' | 'custom' isPlain?: boolean isShort?: boolean onClick?: () => void @@ -21,6 +21,7 @@ type Props = { const UpgradeBtn: FC = ({ className, + size = 'm', style, isPlain = false, isShort = false, @@ -62,7 +63,7 @@ const UpgradeBtn: FC = ({ return ( = ({ embedding, }) => { const { t } = useTranslation() + const [isShowPlanUpgradeModal, { + setTrue: showPlanUpgradeModal, + setFalse: hidePlanUpgradeModal, + }] = useBoolean(false) + const { plan, enableBilling } = useProviderContext() + const { usage, total } = plan + const canAdd = enableBilling && (usage.vectorSpace < total.vectorSpace) + + const withNeedUpgradeCheck = useCallback((fn: () => void) => { + return () => { + if (!canAdd) { + showPlanUpgradeModal() + return + } + fn() + } + }, [canAdd, showPlanUpgradeModal]) const textColor = useMemo(() => { return embedding ? 'text-components-button-secondary-accent-text-disabled' @@ -90,7 +110,7 @@ const SegmentAdd: FC = ({ type='button' className={`inline-flex items-center rounded-l-lg border-r-[1px] border-r-divider-subtle px-2.5 py-2 hover:bg-state-base-hover disabled:cursor-not-allowed disabled:hover:bg-transparent`} - onClick={showNewSegmentModal} + onClick={withNeedUpgradeCheck(showNewSegmentModal)} disabled={embedding} > @@ -108,7 +128,7 @@ const SegmentAdd: FC = ({ @@ -116,7 +136,7 @@ const SegmentAdd: FC = ({ } btnElement={
- +
} btnClassName={open => cn( @@ -129,7 +149,16 @@ const SegmentAdd: FC = ({ className='h-fit min-w-[128px]' disabled={embedding} /> + {isShowPlanUpgradeModal && ( + + )}
+ ) } export default React.memo(SegmentAdd) diff --git a/web/i18n/en-US/billing.ts b/web/i18n/en-US/billing.ts index 9c88acf4d0..793e534e35 100644 --- a/web/i18n/en-US/billing.ts +++ b/web/i18n/en-US/billing.ts @@ -226,6 +226,10 @@ const translation = { title: 'Upgrade to upload multiple pages at once', description: 'You’ve reached the upload limit — only one page can be selected and uploaded at a time on your current plan.', }, + addChunks: { + title: 'Upgrade to continue adding chunks', + description: 'You’ve reached the limit of adding chunks for this plan. ', + }, }, } diff --git a/web/i18n/ja-JP/billing.ts b/web/i18n/ja-JP/billing.ts index 881e3797b6..a7dd695871 100644 --- a/web/i18n/ja-JP/billing.ts +++ b/web/i18n/ja-JP/billing.ts @@ -207,6 +207,10 @@ const translation = { title: '複数ページを一度にアップロードするにはアップグレード', description: '現在のプランではアップロード上限に達しています。1回の操作で選択・アップロードできるページは1つのみです。', }, + addChunks: { + title: 'アップグレードして、チャンクを引き続き追加できるようにしてください。', + description: 'このプランでは、チャンク追加の上限に達しています。 ', + }, }, } diff --git a/web/i18n/zh-Hans/billing.ts b/web/i18n/zh-Hans/billing.ts index dd319ef964..6e0c97de71 100644 --- a/web/i18n/zh-Hans/billing.ts +++ b/web/i18n/zh-Hans/billing.ts @@ -207,6 +207,10 @@ const translation = { title: '升级以一次性上传多个页面', description: '您已达到当前套餐的上传限制 —— 该套餐每次只能选择并上传 1 个页面。', }, + addChunks: { + title: '升级以继续添加分段', + description: '您已达到此计划的添加分段上限。', + }, }, }