ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Creator Studio 模型管理页(/models)迭代实录:许可费「N ⚡ per M」比率输入、卡片布局与过滤器控件的源码级解析

Creator Studio 模型管理页(/models)迭代实录:许可费「N ⚡ per M」比率输入、卡片布局与过滤器控件的源码级解析 Creator Studio 模型管理页/models迭代实录许可费「N ⚡ per M」比率输入、卡片布局与过滤器控件的源码级解析【免费下载链接】civitaiA repository of models, textual inversions, and more项目地址: https://gitcode.com/GitHub_Trending/ci/civitaiCreator Studio 的/models页面是创作者集中管理模型版本、设置许可费licensing fee与付费访问early/paid access的核心后台。本文以 models-feedback-justin.md2026-07-13 捕获的第一轮 Justin 反馈为骨架完整还原该轮反馈驱动的 UI 修复与功能演进模型卡片头的 grid 布局根因、搜索/排序/过滤控件的可用性改进以及最有技术含量的「N ⚡ per M 张图」许可费比率输入体系前端整数守卫、共享换算模块、服务端 zod 校验与写入策略。读完后你将掌握这套整数比率输入 → 精确小数存储的无损往返设计以及 Creator Studio 与civitai/buzz包之间单一事实来源的工程分工。一、反馈迭代的背景与状态标记文档以✅ done · in progress · needs a design/product decision三种状态记录每一项反馈的落地情况是典型的反馈驱动开发档案状态含义对应条目✅ done已完成卡片头布局、状态徽章配色、搜索触发、排序图标、许可费比率输入 in progress进行中过滤器图标当前 fee 过滤 needs decision需设计/产品决策filters popover、fee-as-ratio 输入值得注意的是文档中标为延后/待决策的filters popover在当前仓库代码中已经落地为完整实现见 routes/(app)/models/page.svelte/models/page.svelte#L583-L697)这是本文后续要重点对照的文档快照 vs 当前源码演进点。二、布局与显示修复grid 覆盖 flex 的经典教训2.1 模型卡片头垂直堆叠的根因文档记录的第一个问题模型卡片头model header垂直堆叠模型名、类型、状态各占一行且全选复选框悬浮在名字上方。根因非常典型civitai/ui的CardHeader是display: grid所以外层传的flex-row覆盖从未生效。对照源码card-header.svelte 的类名确实内建了 grid 布局gap-1 rounded-t-xl px-4 … grid auto-rows-min items-start has-data-[slotcard-action]:grid-cols-[1fr_auto] has-data-[slotcard-description]:grid-rows-[auto_auto]grid auto-rows-min意味着子元素默认按每行一个排布外部再传flex-row也无法改变display: grid的布局上下文——这正是grid 覆盖 flex的经典陷阱。2.2 修复方式与结果修复方案是在卡片头内部包一层显式的flex行容器而不是试图覆盖 CardHeader 的 display。当前 routes/(app)/models/page.svelte/models/page.svelte#L802-L845) 中可见最终形态全选复选框Checkbox位于名称左侧模型名CardTitle、类型徽章Badge variantsecondary如 Checkpoint、POI 警示徽章Real person、状态徽章位于同一行状态徽章通过ml-auto推到行尾行尾提供跳转 civitai.com 的IconExternalLink外链按钮。2.3 状态徽章配色统一文档要求模型级状态徽章与版本级状态徽章使用同一套视觉规则Published 用default其余用outline。当前源码完全印证了这一约定模型级page.svelte/models/page.svelte#L830) 中variant{model.status Published ? default : outline}版本级page.svelte/models/page.svelte#L142-L146) 中statusBadgeCls()函数Published 用绿色系border-green-5/30 bg-green-5/10 text-green-5Draft 等其他状态用暗色系。配色语义上许可费徽章feeChip也遵循同样的绿/暗两色约定page.svelte/models/page.svelte#L97-L102) 中 Fee off 为暗色N ⚡ / gen为绿色系。三、控件可用性搜索、排序与过滤器3.1 搜索blur Enter 显式搜索按钮文档要求解决搜索触发不明确的问题最终行为是blur 和 Enter 都触发搜索并提供显式的放大镜搜索按钮。当前实现更进一步是即输即搜filter-as-you-type架构输入框 debounce 400ms 后自动触发搜索typed()/models/page.svelte#L244-L248)Enter 和按钮点击则跳过等待直接搜索submitSearch()/models/page.svelte#L251-L255)搜索请求打到专门的轻量端点 routes/(app)/models/search/server.ts/models/search/server.ts)GET直接复用getModelsView(locals.user, url, cookies)返回 JSON——每次击键只换一个查询集而不是整页 load前端用AbortController取消在途旧请求并用自增searchSeq序列号防止乱序响应覆盖新结果search()/models/page.svelte#L215-L242)搜索期间列表只做降透明度处理opacity-60而不是闪 spinner避免每次击键整表闪烁。3.2 排序下拉用图标破除神秘下拉框文档记录已为排序控件前置一个IconArrowsSort图标使其一眼可辨。当前页面 排序区/models/page.svelte#L698-L712) 提供 Recently updated / Name 两个选项并在控制区保持IconSearch搜索与IconFilter过滤器图标以强化控件可发现性。3.3 过滤器从单图标到完整 popover文档第 5 条记录先在当前 fee 过滤器上加过滤图标而base-model status 过滤器 popover因依赖 load 提供可用值列表而延后。当前源码已将其完整实现page.svelte/models/page.svelte#L583-L697) 中的Popover.Root包含过滤维度控件选项/取值StatusRadioGroupActivehide drafts默认/ Published only / Drafts only / All statusesModel typeSelectAll types view.modelTypesload 提供的可用类型Base modelSelectAll base models view.baseModelsload 提供的可用基础模型Has early / paid accessCheckbox布尔开关access1Licensing feeRadioGroupAll fees / Has a fee / No fee这正是文档所说needs the load to supply the available base models statuses的落地——可用值来自服务端视图数据前端不硬编码。非默认过滤器的数量会以activeFilterCount徽章显示在触发器上并提供 Clear filters 一键重置page.svelte/models/page.svelte#L685-L695)。过滤器全部通过 URL 参数status/bm/mt/access/fee驱动因此可分享、可刷新、可被 CSV 导出复用。四、许可费比率输入本文档的技术核心4.1 需求输入「N ⚡ per M 张图」而不是小数反馈要求创作者以N ⚡ per M images的整数比率表达许可费而非手输小数如0.05。最终 UI 形态是[ N ] ⚡ per [ images ▾ ] generationsbuzz 输入整数专用由beforeinput守卫拦截非数字字符images 下拉固定选项而非自由输入框文档记录时为1 / 10 / 100。对照源码NumberInput.svelte 的整数守卫逻辑非常简洁function handleBeforeInput(e: InputEvent) { if (!integer) return; const text e.data ?? e.dataTransfer?.getData(text) ?? ; if (text /\D/.test(text)) e.preventDefault(); }同时step{integer ? 1 : undefined}、inputmodenumeric且默认clamptrue会把超上限的值吸附回max。UI 组装在 LicensingFeeFields.svelteNumberInput ⚡ per images Select generations 文案并内联Suggested: N ⚡ / M generations与Use this快捷按钮、以及Leave empty to clear the fee提示。该组件同时服务于单版本侧栏编辑器和批量对话框是一份 UI、两处复用。4.2 单一事实来源$lib/monetization/fee.ts → civitai/buzz文档强调换算逻辑收敛在一个共享模块apps/creator-studio/src/lib/monetization/fee.ts。该文件本身不实现逻辑而是从civitai/buzz包重导出——真正的实现位于 packages/civitai-buzz/src/licensing-fee.ts。这意味着主应用、Creator Studio 及其它 spoke 共享同一份换算代码不可能出现两处实现不一致。核心常量与函数符号值/行为位置MAX_LICENSING_FEE100单张图/次生成的上限Buzzlicensing-fee.ts#L9VIDEO_CAP_MULTIPLIER5视频生成成本高上限按 5 倍放大licensing-fee.ts#L14FEE_IMAGE_OPTIONS[1, 10, 20, 50, 100]分母选项升序licensing-fee.ts#L36DEFAULT_FEE_IMAGES10批量默认1 ⚡ per 10licensing-fee.ts#L37ratioToFee(buzz, images)buzz ÷ images按 0.01 精度取整存储licensing-fee.ts#L56-L59feeToRatio(perImage)存储值 → 最小可整除分母的整数对licensing-fee.ts#L43-L52演进点文档记录时分母集合是{1, 10, 100}示例0.05 → 5 per 100而当前源码已扩展为[1, 10, 20, 50, 100]。feeToRatio的设计保证了这个扩展是零成本的——注释明确指出adding a value here (e.g. 20, 50) is all it takes且必须保留 1000.01 列精度的最细分母以保证每个费用都能精确表示。扩展后0.05会映射到1 per 20因为5 分 × 20 100恰好整除而5 × 10 50不整除。4.3 无有损往返的数学保证由于分母被限制在选项集合内任何可存储的每图费用只能是k/1、k/10、k/20、k/50、k/100形式它们都能精确映射回某个分母选项不存在游离小数。文档中给出的完整换算表结合当前源码更新存储的每图费用回显比率说明11 per 11 ⚡ / generation0.11 per 101 ⚡ / 10 generations0.55 per 105 ⚡ / 10 generations0.051 per 20文档时代为5 per 100分母扩展后取更小选项0.011 per 100最小可表达费用0.01/张0/null—费用关闭OffratioToFee用Math.round((buzz / images) * 100) / 100落到 0.01 精度对应数据库ModelVersion.licensingFee DECIMAL(10,2)列并用整数百分位运算避免浮点误差licensing-fee.ts#L43-L52 的注释Integer-hundredths math (the column is 0.01 precision) stays float-safe。范围最小1 ⚡ per 1000.01/张最大100 ⚡ per 1列上限。视频类模型capMediaType判定上限放大 5 倍到 500 ⚡/generation。批量默认1 ⚡ per 10 imagesDEFAULT_FEE_IMAGES 10。清空语义buzz 为空或 0 即清除费用后端写NULL而非 0使fee off过滤与写入路径守卫对费用已移除的判断一致见 licensing-fee.ts#L173-L180。展示层同样来自共享模块formatFeeRatio输出媒体无关的文案licensing-fee.ts#L90-L95——1 ⚡ / generation、5 ⚡ / 10 generations或Off。由于费用可能按图、按视频等不同输出类型收取名词统一用中性的 generation这也是LicensingFeeFields.svelte里文案为 generations 而非 images 的原因。4.4 编辑器种子值seedFeeRatio编辑器打开时显示什么monetization-limits.ts 的seedFeeRatio定义了三层规则版本已有费用 → 打开在该费用实际存储时的分母feeToRatio(licensingFee)无费用 → 按模型类型的建议值Checkpoint 建议1 ⚡/generation其余默认0.1 ⚡/张 1 per 10见SUGGESTED_FEE_PER_IMAGE与DEFAULT_SUGGESTED_FEE_PER_IMAGE建议值经suggestedFeeRatio钳制到编辑器实际提供的分母集合——若编辑器没有对应分母选项则回退到平分母 金额 0避免种出一个下拉里不存在的选项。对应的单元测试 monetization-limits.test.ts 验证了Checkpoint 打开在{1, 1}、LoRA 打开在{1, 10}、已有费用0.05优先于建议值、视频模型建议值携带 5 倍系数{5, 10}。测试还断言每个种子建议值都可保存——建议值与上限是两个独立常量若某次改动让建议值越过上限新建编辑器会打开一个服务端拒绝的值因此测试特意将两者联动校验monetization-limits.test.ts#L155-L167。4.5 服务端校验licensingFeeRatioSchema文档强调Backend stored decimal unchanged——存储格式不变变化的是输入层。服务端在 licensing-fee.ts#L33-L62 用 zod 定义了licensingFeeRatioSchema即使客户端绕过 UI 也必须通过export const licensingFeeRatioSchema z .object({ buzz: z.coerce .number({ message: Enter a whole number of buzz. }) .int(Buzz must be a whole number.) .min(0, Buzz cannot be negative.), images: z.coerce .number() .int() .refine((n) IMAGE_VALUES.includes(n), Choose one of the offered image amounts.), }) .transform((v, ctx) { if (v.buzz 0) return null; // empty / 0 buzz clears the fee const perImage Math.round((v.buzz / v.images) * 100) / 100; if (perImage 0) { ctx.addIssue({ code: custom, message: That fee rounds to nothing — the smallest is 1 ⚡ per 100 generations. }); return z.NEVER; } if (perImage FEE_SCHEMA_CEILING) { ctx.addIssue({ code: custom, message: That fee is too high — the maximum is ${FEE_SCHEMA_CEILING} ⚡ per generation. }); return z.NEVER; } return perImage; });注意FEE_SCHEMA_CEILING maxLicensingFeeCeiling(video) 500licensing-fee.ts#L25是 schema 的全局上界真正的按媒体类型上限在写入函数里逐版本施加。4.6 写入路径setLicensingFee 的完整防线单版本写入 setLicensingFee 依次执行所有权检查ownedVersions只取Model.userId userId且未删除的版本同时也是归属校验非商用基础模型拦截NON_COMMERCIAL_BASE_MODELS new Set([Ideogram 4.0])需与主应用nonCommercialBaseModels同步许可证 flag 才是事实来源POI 拦截licensingFeeBlockedFor({ poi })——涉及真实人物的模型不可商业化Creator Studio 是直写 SQL、不经主应用所以必须在 spoke 侧重复施加策略否则就成了绕过口子上限校验raisesOverCap(normalized, currentFee, cap)cap 由capMediaType(baseModel)决定image 100 / video 500——只拒绝提高越过上限的写入因此祖父化的历史费用可保留定价资格 月度额度unpricedVersionIds判定此前无费用且无永久付费门的版本避免对已在售版本二次计费再经assertPricingAllowed校验资格下限与月度 allowance编辑或清空已定价版本豁免权利确认RightsAffirmation首次设置费用必须确认权利RIGHTS_AFFIRMATION_REQUIRED_ERROR事务化写入writeFee stampRightsAffirmation recordPricingSlots在同一事务中——费用上线却没有确认记录正是该特性要杜绝的产物清空时的槽位释放费用清空后在事务外读取提交后的状态调用releasePricingSlots。4.7 批量语义严格上限 精确错误定位批量路径 bulkSetLicensingFee 有独立的决策规则非商用/POI 列表化报错拒绝时把违规版本点名列出最多列 5 个名称截断 60 字符nameList因为批量选择跨页只说其中 1 个有问题创作者根本找不到是哪个注释引用了工单 868m15nnc严格上限批量只拒绝会把某个版本提高到其媒体类型上限以上的情况报错给出整个选择中最严格的上限值前端bulkLimitspage.svelte#L321-L331/models/page.svelte#L321-L331)同构计算——跨页全选时未加载的版本按更严格的 image 上限处理批量重定价免费只有未定价 → 定价的版本消耗月度 allowance对已收费模型整体改价不占额度无论选择多大批量清空bulkClearFee不需要权利确认——确认是开始收费的要求不是停止收费的要求page.server.ts#L311-L321/models/page.server.ts#L311-L321)。页面侧的批量入口是BulkBarBulkActionDialogpage.svelte#L739-L766/models/page.svelte#L739-L766)右侧栏弹出 fee 表单、付费访问表单等BulkActionDialog接收limits、feeCapsByType按 image/video 分组显示上限避免你的上限是 N掩盖到底是哪个成员受限、suggestedFee仅当类型过滤锁定单一模型类型时才无歧义等数据。五、延后项与后续演进对照文档末尾列出的待办与当前仓库对照如下Filters popoverbase model status——文档标注needs available-values from the load; bigger build。当前已实现page.svelte的完整过滤器 popover 从view.baseModels/view.modelTypes取可用值见本文 3.3 节load 提供可用值列表的设想已落地。Fee-as-ratio input——文档标注pending Justins confirm on the design。当前实现即为比率形态N ⚡ per M且分母选项已从文档记录的{1, 10, 100}扩展为[1, 10, 20, 50, 100]。六、关键文件索引关注点路径反馈文档原文docs/creator-studio/models-feedback-justin.md许可费换算重导出层apps/creator-studio/src/lib/monetization/fee.ts许可费换算事实来源packages/civitai-buzz/src/licensing-fee.ts编辑器种子与上限聚合packages/civitai-buzz/src/monetization-limits.ts换算与上限单元测试packages/civitai-buzz/src/monetization-limits.test.ts服务端 schema 与写入/批量apps/creator-studio/src/lib/server/monetization/licensing-fee.ts/models 页面 load 与 actionsapps/creator-studio/src/routes/(app)/models/page.server.ts/models/page.server.ts)/models 页面 UIapps/creator-studio/src/routes/(app)/models/page.svelte/models/page.svelte)即输即搜端点apps/creator-studio/src/routes/(app)/models/search/server.ts/models/search/server.ts)许可费输入组件apps/creator-studio/src/lib/components/monetization/LicensingFeeFields.svelte整数输入守卫apps/creator-studio/src/lib/components/NumberInput.svelteCardHeader grid 布局根因packages/civitai-ui/src/lib/components/ui/card/card-header.svelte这套实现最值得借鉴的工程决策有三一是把「比率 ↔ 每单位小数」的换算收敛为单一共享模块并配齐往返测试保证 UI 显示、数据库存储、服务端校验三方永远一致二是把整数比率 固定分母当作产品约束来消灭浮点与展示歧义三是 spokeCreator Studio直写数据库时重复施加主应用的策略守卫非商用模型、POI、上限、额度、权利确认杜绝绕过主应用即绕过规则的旁路风险。【免费下载链接】civitaiA repository of models, textual inversions, and more项目地址: https://gitcode.com/GitHub_Trending/ci/civitai创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表