From 4eaab49776da2466c0da1661f70d3e69ed8df3d8 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 27 Apr 2026 12:04:37 +0800 Subject: [PATCH] feat: sync video canvas billing and branding --- .../canvas/LiblibVideoCanvasReplica.tsx | 865 +++++++++++++----- 1 file changed, 617 insertions(+), 248 deletions(-) diff --git a/components/canvas/LiblibVideoCanvasReplica.tsx b/components/canvas/LiblibVideoCanvasReplica.tsx index 259ef1a09..fa76c20ad 100644 --- a/components/canvas/LiblibVideoCanvasReplica.tsx +++ b/components/canvas/LiblibVideoCanvasReplica.tsx @@ -110,7 +110,7 @@ type ComposerMenu = | "reference" | "slash" | null; -type MemberStoreTab = "vip" | "model" | "credits"; +type MemberStoreTab = "vip" | "history" | "credits"; type VideoToolbarMode = "clip" | "analysis" | null; type CapturedTool = { @@ -301,9 +301,98 @@ type SiteBrandSettings = { siteLogoDark?: string | null; }; +const DEFAULT_SITE_NAME = "AI Chatbot"; + +function resolveSiteName(siteName?: string | null) { + return String(siteName || DEFAULT_SITE_NAME).trim() || DEFAULT_SITE_NAME; +} + +function getSiteShowcaseName(siteName?: string | null) { + return `${resolveSiteName(siteName)} 创作广场`; +} + type MembershipSummaryResponse = { + currentPlanId?: string; + currentPlan?: { + id?: string; + name?: string; + monthlyPrice?: number; + annualPrice?: number; + monthlyCredits?: number; + dailyFreeCredits?: number; + features?: string[]; + badge?: string; + highlighted?: boolean; + }; summary?: { currentCredits?: number | null; + dailyFreeCredits?: number | null; + usedThisMonth?: number | null; + monthlyLimit?: number | null; + remainingThisMonth?: number | null; + totalImages?: number | null; + }; +}; + +type MembershipPlan = { + id: string; + name: string; + monthlyPrice: number; + annualPrice: number; + monthlyCredits: number; + dailyFreeCredits: number; + features: string[]; + badge?: string; + highlighted?: boolean; +}; + +type PointsPackage = { + id: string; + name: string; + points: number; + price: number; + currency: string; + isEnabled: boolean; +}; + +type AccountCenterResponse = { + profile?: { + name?: string; + email?: string; + currentPlanName?: string; + currentPlanId?: string; + availablePoints?: number; + monthlyCredits?: number; + dailyFreeCredits?: number; + }; + usageRecords?: Array<{ + id: string; + detail: string; + status: "consumed" | "earned"; + amount: number; + createdAt: string; + }>; + billingRecords?: Array<{ + id: string; + date: string; + category: string; + amount: number; + status: "completed" | "pending" | "failed"; + invoice: string; + }>; +}; + +type VideoCanvasBillingSettings = { + membershipConfig?: { + enabled?: boolean; + currency?: string; + annualDiscountLabel?: string; + plans?: MembershipPlan[]; + }; + pointsPackages?: PointsPackage[]; + dulupayConfig?: { + enabled?: boolean; + defaultType?: string; }; }; @@ -526,90 +615,6 @@ const AUDIO_REFERENCE_IMAGE_URL = "https://demo-sparkai.oss-cn-shanghai.aliyuncs.com/video-canvas-demo/audio-reference-image.png"; const PUBLISH_MODAL_BG_URL = "https://demo-sparkai.oss-cn-shanghai.aliyuncs.com/video-canvas-demo/publish-modal-bg.png"; -const MEMBER_CARD_THEMES: Record = { - "lib-nano": { - bg: "https://demo-sparkai.oss-cn-shanghai.aliyuncs.com/video-canvas-demo/member-card-lib-nano-bg.svg", - icon: "https://demo-sparkai.oss-cn-shanghai.aliyuncs.com/video-canvas-demo/member-card-lib-nano-icon.svg", - tone: "#2A2038", - }, - "seedance-2": { - bg: "https://demo-sparkai.oss-cn-shanghai.aliyuncs.com/video-canvas-demo/member-card-seedance-bg.svg", - icon: "https://demo-sparkai.oss-cn-shanghai.aliyuncs.com/video-canvas-demo/member-card-seedance-icon.svg", - tone: "#222A3E", - }, - "seedance-2-1080p": { - bg: "https://demo-sparkai.oss-cn-shanghai.aliyuncs.com/video-canvas-demo/member-card-seedance-1080-bg.svg", - icon: "https://demo-sparkai.oss-cn-shanghai.aliyuncs.com/video-canvas-demo/member-card-seedance-icon.svg", - tone: "#222222", - }, -}; -const MEMBER_MODEL_CARDS = [ - { - title: "Seedance 2.0", - label: "1080P 专享", - power: "45,000", - price: "2,999", - corner: "首购加赠45000", - desc: "大师版及以上会员可购买", - group: "seedance-2-1080p", - }, - { - title: "Seedance 2.0", - label: "1080P 专享", - power: "60,000", - price: "3,999", - corner: "首购加赠60000", - desc: "大师版及以上会员可购买", - group: "seedance-2-1080p", - }, - { - title: "Lib Nano Pro", - label: "模型积分卡", - power: "20,000", - price: "1,999", - corner: "多生成571张图片", - desc: "尊享版可购买", - group: "lib-nano", - }, - { - title: "Lib Nano 2", - label: "模型积分卡", - power: "45,000", - price: "2,999", - corner: "多生成1500张图片", - desc: "尊享版可购买", - group: "lib-nano", - }, - { - title: "Seedance 2.0", - label: "视频模型卡", - power: "7,500", - price: "599", - corner: "多生成341秒视频", - desc: "大师版及以上会员可购买", - group: "seedance-2", - }, - { - title: "Seedance 2.0", - label: "视频模型卡", - power: "15,000", - price: "999", - corner: "多生成682秒视频", - desc: "大师版及以上会员可购买", - group: "seedance-2", - }, -]; -const VIP_PLAN_CARDS = [ - { title: "基础版VIP", power: "每月 8,000 点积分", price: "39", benefits: ["登录每日赠送20积分", "云端存储空间20GB", "会员专属模型"] }, - { title: "大师版VIP", power: "每月 18,000 点积分", price: "89", benefits: ["无限并发任务", "会员专享无限次加速", "Seedance 2.0 活动权益"] }, - { title: "尊享版VIP", power: "每月 34,000 点积分", price: "169", benefits: ["云端存储空间500GB", "智能分镜(Kling3.0)", "去除品牌水印"] }, -]; -const CREDIT_PACKS = [ - { points: "1,000", price: "6", tag: "限时 6 折" }, - { points: "5,000", price: "30", tag: "首充推荐" }, - { points: "10,000", price: "60", tag: "最高赠送" }, - { points: "30,000", price: "180", tag: "创作囤货" }, -]; const STORY_SCRIPT_PROMPT = "根据我上传的剧本生成一个完整的故事脚本"; const AUDIO_TO_VIDEO_PROMPT = "根据上传的音频生成对应场景画面,镜头语言、节奏、音乐匹配情绪变化,电影级质感。"; @@ -898,7 +903,7 @@ function createWorkflowNode(workflow: WorkflowPreset, x: number, y: number): Clo id: makeId("workflow"), kind: "workflow", title: String(workflow.name || workflow.title || "工作流预设"), - subtitle: workflow.description || "LibTV workflow", + subtitle: workflow.description || "工作流预设", x, y, width: 230, @@ -2555,6 +2560,10 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib () => (canvasModelsData?.models || []).filter((model) => getCanvasModelRef(model)), [canvasModelsData?.models] ); + const resolvedSiteName = useMemo( + () => resolveSiteName(siteBrandSettings?.siteName), + [siteBrandSettings?.siteName] + ); const [nodes, setNodes] = useState([]); const [edges, setEdges] = useState([]); const [selectedNodeId, setSelectedNodeId] = useState(null); @@ -3222,7 +3231,7 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib } const link = document.createElement("a"); link.href = anchorSourceUrl; - link.download = `${anchor.title || "libtv-image"}.png`; + link.download = `${anchor.title || "未命名图片"}.png`; link.rel = "noreferrer"; document.body.appendChild(link); link.click(); @@ -4531,7 +4540,7 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib } const link = document.createElement("a"); link.href = mediaUrl; - link.download = `${node.title || "libtv-video"}.mp4`; + link.download = `${node.title || "未命名视频"}.mp4`; link.rel = "noreferrer"; document.body.appendChild(link); link.click(); @@ -5520,7 +5529,7 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib routerBack={() => router.push("/")} siteLogo={siteBrandSettings?.siteLogo} siteLogoDark={siteBrandSettings?.siteLogoDark} - siteName={siteBrandSettings?.siteName} + siteName={resolvedSiteName} /> setActivePanel(null)} /> + setActivePanel(null)} siteName={resolvedSiteName} /> ) : null} {activePanel === "share" ? ( @@ -5640,6 +5649,7 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib setActivePanel(null); }} onPublish={() => setActivePanel("publish")} + siteName={resolvedSiteName} /> ) : null} @@ -5649,6 +5659,7 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib publishPublic={publishPublic} onClose={() => setActivePanel(null)} onTogglePublic={() => setPublishPublic((value) => !value)} + siteName={resolvedSiteName} /> ) : null} @@ -5661,7 +5672,10 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib ) : null} {activePanel === "account" ? ( - setActivePanel(null)} /> + setActivePanel(null)} + onOpenMember={() => setActivePanel("member")} + /> ) : null} {previewNode ? ( @@ -6059,7 +6073,7 @@ function StylizedBrandLogo({ siteLogoDark?: string | null; siteName?: string | null; }) { - const resolvedSiteName = String(siteName || "AI Chatbot").trim() || "AI Chatbot"; + const resolvedSiteName = resolveSiteName(siteName); const displayLogo = siteLogoDark?.trim() || siteLogo?.trim() || ""; const shouldInvertLogo = !siteLogoDark?.trim() && Boolean(siteLogo?.trim()); @@ -10691,7 +10705,15 @@ function SkillsAccessPanel() { ); } -function SkillsAccessModal({ onClose }: { onClose: () => void }) { +function SkillsAccessModal({ + onClose, + siteName, +}: { + onClose: () => void; + siteName: string; +}) { + const skillsName = `${siteName} Skills`; + return (
@@ -10699,8 +10721,8 @@ function SkillsAccessModal({ onClose }: { onClose: () => void }) {

我的Access key

-

· Access key 仅用于 LibTV skills 的调用授权使用,一旦传播给他人,有账号泄露的风险,请谨慎保管!

-

· LibTVskills :是Libtv提供给agent使用的入口,可在openclaw中调用,完成专业视频创作。

+

· Access key 仅用于 {skillsName} 的调用授权使用,一旦传播给他人,有账号泄露的风险,请谨慎保管!

+

· {skillsName} 是 {siteName} 提供给 agent 使用的入口,可在 openclaw 中调用,完成专业视频创作。

· 安装使用请戳 ))}

- 免费用户登录每日赠送20积分,每日赠2次5折视频;云端存储空间3GB。 + 会员订阅仍以站点套餐配置为准;目前已优先打通积分充值与账单查看,避免视频画布继续展示与站点不一致的占位价格。
-
+
- {activeTab === "model" ? "模型积分卡" : activeTab === "vip" ? "会员特惠" : "积分充值"} + {activeTab === "credits" ? "积分充值" : activeTab === "vip" ? "会员特惠" : "账单记录"}
- {activeTab === "model" ? "模型超市内积分有效期为6个月,支付后不退不换。" : activeTab === "vip" ? "购买后立即生效,积分按月自动发放。" : "积分有效期2年,支付后不退不换。"} + {activeTab === "credits" + ? paymentEnabled + ? "直接调用当前站点的 DuluPay 充值接口,支付完成后积分自动到账。" + : "当前未开启在线充值,请先检查站点支付配置。" + : activeTab === "vip" + ? "会员套餐直接读取站点会员配置,未接通订阅支付前不再显示自定义占位售价。" + : "展示当前账号最近的支付与赠送记录,方便排查充值状态。"}
- +
+ {activeTab === "vip" && siteSettingsData?.membershipConfig?.annualDiscountLabel ? ( + + {siteSettingsData.membershipConfig.annualDiscountLabel} + + ) : null} + +
- {activeTab === "model" ? : null} - {activeTab === "vip" ? : null} - {activeTab === "credits" ? : null} + {activeTab === "credits" ? ( + + ) : null} + {activeTab === "vip" ? ( + + ) : null} + {activeTab === "history" ? ( + + ) : null}
@@ -10865,117 +11053,261 @@ function MemberStoreModal({ onClose }: { onClose: () => void }) { ); } -function ModelCardGrid() { +function VipPlanGrid({ + billingCycle, + currency, + currentPlanId, + onBillingCycleChange, + plans, +}: { + billingCycle: "monthly" | "annual"; + currency: string; + currentPlanId: string | null; + onBillingCycleChange: (value: "monthly" | "annual") => void; + plans: MembershipPlan[]; +}) { + if (plans.length === 0) { + return ( + + ); + } + return (
-
-
-
限时闪购 买一赠一
-
会员专属模型超市,顶级模型囤货必选
-
+
+
-
- {MEMBER_MODEL_CARDS.map((card) => ( - - ))} -
-
- ); -} +
+ {plans.map((plan) => { + const isCurrent = currentPlanId === plan.id; + const annualMonthlyPrice = + plan.annualPrice > 0 ? Number((plan.annualPrice / 12).toFixed(2)) : 0; + const isFree = plan.monthlyPrice <= 0 && plan.annualPrice <= 0; -function MemberModelCard({ card }: { card: (typeof MEMBER_MODEL_CARDS)[number] }) { - const theme = MEMBER_CARD_THEMES[card.group] || MEMBER_CARD_THEMES["seedance-2"]; - - return ( - - ); -} - -function VipPlanGrid() { - return ( -
- {VIP_PLAN_CARDS.map((plan, index) => ( -
-
- {plan.title} - {index === 1 ? 推荐 : null} -
-
{plan.power}
-
- ¥ - {plan.price} - /月起 -
- -
- {plan.benefits.map((benefit) => ( -
- - {benefit} + return ( +
+
+ {plan.name} + {plan.badge ? ( + + {plan.badge} + + ) : null}
- ))} -
-
- ))} +
+ 每月 {formatCreditValue(plan.monthlyCredits)} 积分 + {plan.dailyFreeCredits > 0 ? ` · 每日赠送 ${formatCreditValue(plan.dailyFreeCredits)} 积分` : ""} +
+
+ {isFree ? ( +
免费
+ ) : billingCycle === "monthly" ? ( +
+ {formatCurrencyAmount(currency, plan.monthlyPrice)} + /月 +
+ ) : ( +
+
+ {formatCurrencyAmount(currency, plan.annualPrice)} + /年 +
+
+ 折合 {formatCurrencyAmount(currency, annualMonthlyPrice)}/月 +
+
+ )} +
+ +
+ {(plan.features || []).map((feature) => ( +
+ + {feature} +
+ ))} +
+
+ ); + })} +
); } -function CreditPackGrid() { +function CreditPackGrid({ + creatingPaymentPackageId, + currency, + onCreateOrder, + packages, + paymentEnabled, +}: { + creatingPaymentPackageId: string | null; + currency: string; + onCreateOrder: (packageId: string) => void; + packages: PointsPackage[]; + paymentEnabled: boolean; +}) { + if (!paymentEnabled || packages.length === 0) { + return ( + + ); + } + return ( -
- {CREDIT_PACKS.map((pack) => ( - - ))} +
+
+ 支付成功后积分会直接充值到当前账号,视频画布与首页共用同一套积分余额和订单记录。 +
+
+ {packages.map((pkg) => { + const isPaying = creatingPaymentPackageId === pkg.id; + return ( +
+
+ {pkg.name} + + {pkg.points >= 5000 ? "推荐" : "灵活补充"} + +
+
{formatCreditValue(pkg.points)}
+
通用积分
+
+ {formatCurrencyAmount(pkg.currency || currency, pkg.price)} +
+ +
+ ); + })} +
+
+ ); +} + +function BillingHistoryGrid({ + currency, + records, +}: { + currency: string; + records: NonNullable; +}) { + if (records.length === 0) { + return ( + + ); + } + + return ( +
+
+ 时间 + 类型 + 金额 + 状态 +
+
+ {records.map((record) => { + const isRecharge = record.category === "积分充值"; + const amountLabel = isRecharge + ? formatCurrencyAmount(currency, Number(record.amount) || 0) + : `${Number(record.amount) > 0 ? "+" : ""}${Number(record.amount) || 0} 积分`; + + return ( +
+ {formatBillingDate(record.date)} + {record.category} + {amountLabel} + + + {getBillingStatusLabel(record.status)} + + +
+ ); + })} +
); } @@ -11036,9 +11368,30 @@ function NotificationsPopover({ onClose }: { onClose: () => void }) { ); } -function AccountPopover({ onClose }: { onClose: () => void }) { +function AccountPopover({ + onClose, + onOpenMember, +}: { + onClose: () => void; + onOpenMember: () => void; +}) { const { data: membershipSummary } = useSWR("/api/user/membership-summary", fetcher); - const credits = formatCreditValue(Math.max(0, Number(membershipSummary?.summary?.currentCredits ?? 0))); + const { data: accountCenterData } = useSWR("/api/user/account-center", fetcher); + const credits = formatCreditValue( + Math.max(0, Number(accountCenterData?.profile?.availablePoints ?? membershipSummary?.summary?.currentCredits ?? 0)) + ); + const displayName = + String(accountCenterData?.profile?.name || accountCenterData?.profile?.email || "当前用户").trim() || "当前用户"; + const displayEmail = String(accountCenterData?.profile?.email || "").trim(); + const currentPlanName = + String(accountCenterData?.profile?.currentPlanName || membershipSummary?.currentPlan?.name || "Free").trim() || + "Free"; + const dailyFreeCredits = formatCreditValue( + Math.max( + 0, + Number(accountCenterData?.profile?.dailyFreeCredits ?? membershipSummary?.summary?.dailyFreeCredits ?? 0) + ) + ); return (
@@ -11049,21 +11402,23 @@ function AccountPopover({ onClose }: { onClose: () => void }) { avatar
- 微信用户82a0d8 + {displayName} 个人
- UUID - + {displayEmail || "登录后查看账户邮箱"} + {displayEmail ? ( + + ) : null}
@@ -11076,12 +11431,15 @@ function AccountPopover({ onClose }: { onClose: () => void }) {
@@ -11089,11 +11447,19 @@ function AccountPopover({ onClose }: { onClose: () => void }) {
- - + +
+ } + label="积分与会员中心" + onClick={() => { + onClose(); + onOpenMember(); + }} + /> } label="个人中心" onClick={() => toast.info("个人中心入口待接入。")} /> } label="创建团队" onClick={() => toast.info("创建团队接口待接入。")} /> } label="订阅与开发票" onClick={() => toast.info("订阅与开发票入口待接入。")} /> @@ -11276,12 +11642,16 @@ function PublishModal({ onTogglePublic, projectTitle, publishPublic, + siteName, }: { onClose: () => void; onTogglePublic: () => void; projectTitle: string; publishPublic: boolean; + siteName: string; }) { + const showcaseName = getSiteShowcaseName(siteName); + return (
@@ -11302,10 +11672,10 @@ function PublishModal({

- 发布作品到LibTV + 发布作品到{siteName}

- 您的作品将展示到LibTV Show,方便大家交流使用 + 您的作品将展示到{showcaseName},方便大家交流使用

@@ -11340,7 +11710,7 @@ function PublishModal({ 活动标签