From 3963d9ae2fb317174be4d804aeea30287036cb00 Mon Sep 17 00:00:00 2001 From: Stringadmin Date: Mon, 8 Jun 2026 15:55:50 +0800 Subject: [PATCH] Show billing estimate and clarify session replacement --- src/features/workbench/WorkbenchPage.tsx | 65 +++++++++++++++++++++++- src/stores/useSessionStore.ts | 4 +- src/styles/pages/legacy-pages.css | 15 ++++++ src/styles/themes/dark-green.css | 8 +++ src/utils/enterpriseVideoPolicy.ts | 4 +- 5 files changed, 90 insertions(+), 6 deletions(-) diff --git a/src/features/workbench/WorkbenchPage.tsx b/src/features/workbench/WorkbenchPage.tsx index a91d243..ef91565 100644 --- a/src/features/workbench/WorkbenchPage.tsx +++ b/src/features/workbench/WorkbenchPage.tsx @@ -79,7 +79,7 @@ import { import { isViduModel } from "../../utils/viduRouting"; import { isPixverseModel } from "../../utils/pixverseRouting"; import { resolveVideoRequestModel } from "../../utils/resolveVideoModel"; -import { ENTERPRISE_DEFAULT_VIDEO_MODEL } from "../../utils/enterpriseVideoPolicy"; +import { calculateEnterpriseVideoCredits, ENTERPRISE_DEFAULT_VIDEO_MODEL } from "../../utils/enterpriseVideoPolicy"; import { getImageQualityOptions, getDefaultImageQuality, @@ -220,6 +220,12 @@ const MODE_ICONS: Record = { video: , }; +function formatCreditValue(value: number): string { + if (!Number.isFinite(value)) return "-"; + if (value >= 100) return Math.round(value).toLocaleString("zh-CN"); + return Number(value.toFixed(2)).toString(); +} + function WorkbenchPage({ isAuthenticated, session, @@ -464,6 +470,48 @@ function WorkbenchPage({ const videoQualityLabel = getVideoQualityLabel(videoModel, videoQuality); const imageSettingsSummary = `${imageRatio} / ${imageQuality}`; + const billingEstimate = useMemo(() => { + if (activeMode === "image") { + return { + label: "预计 20 积分", + title: `图片生成按任务计费:${activeModel},${imageSettingsSummary},预计 20 积分`, + }; + } + if (activeMode === "video") { + try { + const durationSeconds = Math.max(1, Math.ceil(Number(videoDuration) || 1)); + const credits = calculateEnterpriseVideoCredits({ + model: activeModelValue, + resolution: videoQuality, + durationSeconds, + muted: false, + hasReferenceVideo: referenceItems.some((item) => item.kind === "video"), + }); + return { + label: `预计 ${formatCreditValue(credits)} 积分`, + title: `${activeModel},${videoQualityLabel},${durationSeconds} 秒,预计 ${formatCreditValue(credits)} 积分`, + }; + } catch { + return { + label: "计费以提交后为准", + title: "当前模型的预估计费暂不可用,实际扣费以服务端结算为准", + }; + } + } + return { + label: "按 Token 结算", + title: "文本对话按输入、输出 Token 实际用量结算,完成后显示本次积分", + }; + }, [ + activeMode, + activeModel, + activeModelValue, + imageSettingsSummary, + referenceItems, + videoDuration, + videoQuality, + videoQualityLabel, + ]); const composerPlaceholder = referenceItems.length > 0 ? `${toolTheme.placeholder},可输入 @ 引用参考内容` : toolTheme.placeholder; const dropdownDirection = hasActivatedWorkspace ? "up" : "down"; @@ -2545,7 +2593,15 @@ function WorkbenchPage({ } }; - const sendDisabled = !inputValue.trim() || (activeMode !== "chat" && getActiveGenerationTaskCount(getGenerationUserKey(session?.user.id)) >= 3); + const activeGenerationCount = getActiveGenerationTaskCount(getGenerationUserKey(session?.user.id)); + const generationLimitReached = activeMode !== "chat" && activeGenerationCount >= 3; + const promptIsEmpty = !inputValue.trim(); + const sendDisabled = promptIsEmpty || generationLimitReached; + const sendButtonTitle = promptIsEmpty + ? "输入内容后可发送" + : generationLimitReached + ? `当前已有 ${activeGenerationCount} 个任务进行中,请等待任一任务完成` + : billingEstimate.title; const suggestedPrompts = [ { text: "画一个赛博朋克风格的城市夜景", mode: "image" as WorkbenchMode }, @@ -2882,10 +2938,15 @@ function WorkbenchPage({ )}
+ + {billingEstimate.label} +