fix(web): 模型价格 - 合并 PR #372 并修复零积分展示

This commit is contained in:
ddCat
2026-09-01 19:10:23 +08:00
5 changed files with 112 additions and 71 deletions

View File

@@ -1,6 +1,6 @@
import { expect, test } from "bun:test";
import { defaultConfig, type AiConfig } from "../src/stores/use-config-store";
import { createModelChannel, defaultConfig } from "../src/stores/use-config-store";
import { runBackendGenerationTask, runBackendGenerationTaskBatch } from "../src/services/api/generation-task";
import { deleteGenerationTask, formatTaskLog, listGenerationTasks, projectBackendSafeTaskLog, splitGenerationTaskObservationIds, type GenerationTask } from "../src/services/api/task-center";
import { isLocalDreaminaBackgroundTask, localDreaminaCancellationCopy, localDreaminaDetachOutcome, projectLocalDreaminaTask } from "../src/services/local-dreamina-task-projection";
@@ -24,24 +24,25 @@ function sourceSection(source: string, startMarker: string, endMarker: string) {
return compactSource(source.slice(start, end));
}
function remoteBackendConfig(model: string, overrides: Partial<AiConfig> = {}): AiConfig {
const modelName = model.includes("::") ? model.slice(model.indexOf("::") + 2) : model;
function backendModelConfig(model: string) {
const channel = createModelChannel({
id: "system-test-channel",
name: "系统测试渠道",
baseUrl: "/api/system-test-channel",
apiKey: "system",
apiFormat: "openai",
scope: "system",
models: [model],
});
const selectedModel = `${channel.id}::${model}`;
return {
...defaultConfig,
...overrides,
model,
channels: [
{
id: "default",
name: "Backend test channel",
baseUrl: "https://api.example.com",
apiKey: "test-key",
apiFormat: "openai",
interfaceType: "newapi-channel-2",
models: [modelName],
scope: "user",
},
],
channelMode: "remote" as const,
channels: [channel],
model: selectedModel,
imageModel: selectedModel,
videoModel: selectedModel,
audioModel: selectedModel,
};
}
@@ -1307,7 +1308,7 @@ test("remote provider keeps Create resolution semantics and still creates one Ba
{
mode: "video",
prompt: "A remote test clip",
config: remoteBackendConfig("default::grok-imagine-video", { vquality: "720", quality: "auto" }),
config: { ...backendModelConfig("grok-imagine-video"), vquality: "720", quality: "auto" },
},
{
createTask: async (input) => {
@@ -1375,7 +1376,7 @@ test("remote image video and audio references keep Backend parity without Dreami
{
mode: item.mode,
prompt: "Remote parity fixture",
config: remoteBackendConfig("default::provider-neutral-model"),
config: backendModelConfig("provider-neutral-model"),
...item.references,
},
{
@@ -1694,7 +1695,7 @@ test("Create audio upload converts, previews, removes, and submits through the s
projectId: running.projectId,
mode: "video",
prompt: running.prompt,
config: remoteBackendConfig("remote-video-audio-fixture", { videoModel: "remote-video-audio-fixture" }),
config: backendModelConfig("remote-video-audio-fixture"),
...references,
} as Parameters<typeof runBackendGenerationTask>[0],
{

View File

@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test";
import { modelQuoteRequest, normalizeTierResolution, priceTiersForCurrentSelection, requestCreditCost } from "../src/lib/model-pricing";
import { modelQuoteRequest, normalizeTierResolution, priceTierSummaryLabel, priceTiersForCurrentSelection, requestCreditCost } from "../src/lib/model-pricing";
import type { ModelRequirements } from "../src/lib/model-selection";
import { createModelChannel, defaultConfig, normalizeConfigSnapshot, resolveModelChannel, type AiConfig } from "../src/stores/use-config-store";
@@ -86,6 +86,19 @@ describe("model request pricing", () => {
).toBe(0.125);
});
test("displays zero-price system tiers as configured free pricing", () => {
const fixedConfig = systemConfig({
capability: "image",
tiers: [{ selector: {}, billingMode: "fixed_request", unitPriceMicrocredits: 0 }],
});
const perSecondConfig = systemConfig({
tiers: [{ selector: {}, billingMode: "per_second", unitPriceMicrocredits: 0 }],
});
expect(priceTierSummaryLabel(resolveModelChannel(fixedConfig, fixedConfig.model).modelCosts![0]!.logicalPriceTiers!)).toBe("0 积分");
expect(priceTierSummaryLabel(resolveModelChannel(perSecondConfig, perSecondConfig.model).modelCosts![0]!.logicalPriceTiers!)).toBe("0 积分/秒");
});
test("uses reference count and operation to avoid a text-video price tier", () => {
const config = systemConfig({
tiers: [