fix(*): 视频计费与结果解析 - 按 Token 预估展示并解析数组媒体结果

This commit is contained in:
ddCat
2026-09-19 07:48:08 +08:00
parent f549486fa2
commit 6c67019f2f
8 changed files with 92 additions and 14 deletions

View File

@@ -33,7 +33,9 @@ import { useCopyText } from "@/hooks/use-copy-text";
import { buildImageResolutionOptions, formatImageResolutionSize, supportsImageResolutionPresets } from "@/lib/image-resolution-tiers";
import { modelCapabilityConfigFor, normalizeVideoValue, videoDurationOptions, type ImageCapabilityConfig, type VideoCapabilityConfig } from "@/lib/model-capabilities";
import { mergedImageCapabilityConfig, type ModelRequirements } from "@/lib/model-selection";
import { modelQuoteDescription, modelQuoteRequest } from "@/lib/model-pricing";
import type { Skill } from "@/services/api/skills";
import { quoteModel, type LogicalModelQuote } from "@/services/api/logical-models";
import { resolveResourceUrl } from "@/services/api/resources";
import { modelOptionName, resolveModelChannel, type AiConfig } from "@/stores/use-config-store";
import { useAppearanceStore } from "@/stores/use-appearance-store";
@@ -367,6 +369,8 @@ export function CreationComposer(props: ComposerProps) {
const canSubmit = Boolean(props.prompt.trim()) && !interactionBusy;
const creditsEnabled = useUserStore((state) => state.features.creditsEnabled);
const priceChannel = resolveModelChannel(props.config, props.model);
const quoteRequest = useMemo(() => modelQuoteRequest(props.config, props.model, props.mode, props.modelRequirements), [props.config, props.mode, props.model, props.modelRequirements]);
const [routeQuote, setRouteQuote] = useState<LogicalModelQuote | null>(null);
const canOptimizePrompt = Boolean(props.promptOptimizerProvider) && (props.mode === "image" || props.mode === "video");
const optimizerReferences = props.references.filter((reference) => reference.active && reference.kind !== "skill");
const credits = requestCreditCost({
@@ -375,10 +379,28 @@ export function CreationComposer(props: ComposerProps) {
model: modelOptionName(props.model),
count: props.mode === "image" ? props.count : 1,
seconds: props.mode === "video" ? props.seconds : 1,
capability: props.mode,
config: props.config,
requirements: props.modelRequirements,
});
const showCost = creditsEnabled && credits !== null;
const formattedCredits = credits?.toLocaleString("zh-CN", { maximumFractionDigits: 6 });
const actionLabel = props.referenceReplacementBusy ? "正在替换参考图" : interactionBusy || (props.generationActive && !canSubmit) ? "生成中" : showCost ? `预计消耗 ${formattedCredits} 积分,发送` : "发送";
useEffect(() => {
if (!creditsEnabled || !quoteRequest) {
setRouteQuote(null);
return;
}
const controller = new AbortController();
setRouteQuote(null);
quoteModel(quoteRequest, controller.signal)
.then(({ quote }) => setRouteQuote(quote))
.catch(() => {
if (!controller.signal.aborted) setRouteQuote(null);
});
return () => controller.abort();
}, [creditsEnabled, quoteRequest]);
const generationCredits = routeQuote ? routeQuote.amountMicrocredits / 1_000_000 : credits;
const showCost = creditsEnabled && generationCredits !== null && generationCredits !== undefined;
const formattedCredits = generationCredits?.toLocaleString("zh-CN", { maximumFractionDigits: 6 });
const actionLabel = props.referenceReplacementBusy ? "正在替换参考图" : interactionBusy || (props.generationActive && !canSubmit) ? "生成中" : showCost ? `${routeQuote?.estimated ? "预估" : "消耗"} ${formattedCredits} 积分,发送` : "发送";
// Send-button working state must span the WHOLE generation (not just the
// submit-lock window): spinner + glow stay while a message is pending and
// the composer is empty; typing a next prompt returns the arrow so the
@@ -610,7 +632,7 @@ export function CreationComposer(props: ComposerProps) {
title={!canSubmit && !interactionBusy ? "输入创作想法后即可生成" : actionLabel}
>
{showWorkingGlow ? <WorkingGlow active color="var(--creation-text)" radius="999px" /> : null}
{showCost ? <span className="creation-submit-cost"><CreditSymbol /><span>{formattedCredits}</span></span> : null}
{showCost ? <span className="creation-submit-cost" title={routeQuote ? modelQuoteDescription(routeQuote) : undefined}><CreditSymbol /><span>{routeQuote?.estimated ? `预估:${formattedCredits}` : formattedCredits}</span></span> : null}
<span className="creation-submit-action" aria-hidden>{showWorkingSpinner ? <LoaderCircle className="size-4 animate-spin" /> : <ArrowUp className="size-4" />}<span>{showWorkingSpinner ? "生成中" : "开始创作"}</span></span>
</Button>
</footer>