production runtime and mock removal
This commit is contained in:
1 parent
2ef3da7af5
commit
6480e503eb
55 files changed
+581
-540
No files matched your search
@@ -150,6 +150,7 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: Generate
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [billingQuote, setBillingQuote] = useState<BillingQuote | null>(null);
|
||||
const [billingQuoteLoading, setBillingQuoteLoading] = useState(false);
|
||||
const [billingQuoteError, setBillingQuoteError] = useState<string | null>(null);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [notice, setNotice] = useState<string | null>(null);
|
||||
@@ -307,10 +308,12 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: Generate
|
||||
const timer = window.setTimeout(async () => {
|
||||
if (!prompt.trim() || missingMaterialPlaceholders.length) {
|
||||
if (active) setBillingQuote(null);
|
||||
if (active) setBillingQuoteError(null);
|
||||
if (active) setBillingQuoteLoading(false);
|
||||
return;
|
||||
}
|
||||
setBillingQuoteLoading(true);
|
||||
setBillingQuoteError(null);
|
||||
try {
|
||||
const response = await fetch("/api/billing/quote", {
|
||||
method: "POST",
|
||||
@@ -319,11 +322,18 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: Generate
|
||||
});
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (active) {
|
||||
setBillingQuote(response.ok && payload.quote ? payload.quote as BillingQuote : null);
|
||||
if (response.ok && payload.quote) {
|
||||
setBillingQuote(payload.quote as BillingQuote);
|
||||
setBillingQuoteError(null);
|
||||
} else {
|
||||
setBillingQuote(null);
|
||||
setBillingQuoteError(typeof payload.error === "string" ? payload.error : "计费估算暂不可用,请检查服务配置");
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
if (active) {
|
||||
setBillingQuote(null);
|
||||
setBillingQuoteError("计费估算暂不可用,请检查后端服务配置");
|
||||
}
|
||||
} finally {
|
||||
if (active) setBillingQuoteLoading(false);
|
||||
@@ -1167,7 +1177,7 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: Generate
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<BillingEstimate quote={billingQuote} loading={billingQuoteLoading} />
|
||||
<BillingEstimate quote={billingQuote} loading={billingQuoteLoading} error={billingQuoteError} />
|
||||
</div>
|
||||
</div>
|
||||
{templateEditorOpen ? (
|
||||
@@ -1425,7 +1435,7 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: Generate
|
||||
<div className="create-task-body">
|
||||
<h3 title={taskName(job)}>{taskName(job)}</h3>
|
||||
<div className="create-task-meta">
|
||||
<span className={`status ${job.status}`}>{statusLabel(job.status)}</span>
|
||||
<span className={`status ${taskStatusClass(job, taskAssetById)}`}>{statusLabel(job.status, Boolean(firstOutputAsset(job, taskAssetById)))}</span>
|
||||
<span>{durationLabel(job, durationNow)}</span>
|
||||
<span className="billing-task-cost">{billingLabel(job)}</span>
|
||||
</div>
|
||||
@@ -1477,10 +1487,11 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: Generate
|
||||
function renderTaskThumbnail(job: GenerationJob, assetById: Map<string, Asset>) {
|
||||
const asset = job.outputAssetIds.map((assetId) => assetById.get(assetId)).find((item): item is Asset => Boolean(item));
|
||||
if (!asset) {
|
||||
const awaitingOutput = job.status === "succeeded";
|
||||
return (
|
||||
<div className={clsx("create-task-thumb create-task-thumb-placeholder", isPendingTask(job) && "generating")} aria-hidden="true">
|
||||
<div className={clsx("create-task-thumb create-task-thumb-placeholder", (isPendingTask(job) || awaitingOutput) && "generating")} aria-hidden="true">
|
||||
{isPendingTask(job) ? <Loader2 className="spin" size={18} /> : <ImageIcon size={18} />}
|
||||
<span>{isPendingTask(job) ? "生成中" : "无结果"}</span>
|
||||
<span>{isPendingTask(job) ? "生成中" : awaitingOutput ? "结果同步中" : "无结果"}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1532,7 +1543,7 @@ function TaskDetailModal({
|
||||
<span className="task-detail-kicker">任务详情</span>
|
||||
<h2 id="task-detail-title" title={taskName(job)}>{taskName(job)}</h2>
|
||||
<div className="task-detail-subline">
|
||||
<span className={`status ${job.status}`}>{statusLabel(job.status)}</span>
|
||||
<span className={`status ${taskStatusClass(job, assets)}`}>{statusLabel(job.status, outputAssets.length > 0)}</span>
|
||||
<span>{capabilityLabel(job.capability)}</span>
|
||||
<span>{durationLabel(job, now)}</span>
|
||||
</div>
|
||||
@@ -1666,20 +1677,23 @@ function firstOutputAsset(job: GenerationJob, assets: Map<string, Asset>) {
|
||||
|
||||
function BillingEstimate({
|
||||
quote,
|
||||
loading
|
||||
loading,
|
||||
error
|
||||
}: {
|
||||
quote: BillingQuote | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}) {
|
||||
return (
|
||||
<aside className={clsx("billing-estimate", loading && "is-loading")} aria-live="polite" aria-label={quote?.quotaExempt ? "本次预计费用(超级管理员不计额度)" : "本次预计消耗额度"}>
|
||||
<aside className={clsx("billing-estimate", loading && "is-loading", error && "has-error")} aria-live="polite" aria-label={quote?.quotaExempt ? "本次预计费用(超级管理员不计额度)" : "本次预计消耗额度"}>
|
||||
<div className="billing-estimate-head">
|
||||
<span className="billing-estimate-icon" aria-hidden="true"><CircleDollarSign size={16} /></span>
|
||||
<span>{quote?.quotaExempt ? "本次预计费用(不计额度)" : "本次预计消耗额度"}</span>
|
||||
</div>
|
||||
<strong className="billing-estimate-amount">
|
||||
{loading ? "…" : quote ? formatBillingAmount(quote.amountFen) : "—"}
|
||||
{loading ? "…" : error ? "暂不可用" : quote ? formatBillingAmount(quote.amountFen) : "—"}
|
||||
</strong>
|
||||
{error ? <span className="billing-estimate-error">{error}</span> : null}
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
@@ -1767,7 +1781,7 @@ function taskEngineLabel(value: string) {
|
||||
if (value === "evolink") return "Image2";
|
||||
if (value === "bailian") return "阿里云百炼";
|
||||
if (value === "seedance") return "Seedance";
|
||||
if (value === "mock") return "系统生成";
|
||||
if (value === "mock") return "历史任务";
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -1805,10 +1819,14 @@ function taskName(job: GenerationJob) {
|
||||
return text ? text : capabilityLabel(job.capability);
|
||||
}
|
||||
|
||||
function statusLabel(status: GenerationJob["status"]) {
|
||||
function taskStatusClass(job: GenerationJob, assets: Map<string, Asset>) {
|
||||
return job.status === "succeeded" && !firstOutputAsset(job, assets) ? "syncing" : job.status;
|
||||
}
|
||||
|
||||
function statusLabel(status: GenerationJob["status"], hasOutput = true) {
|
||||
if (status === "queued") return "排队中";
|
||||
if (status === "running") return "生成中";
|
||||
if (status === "succeeded") return "已完成";
|
||||
if (status === "succeeded") return hasOutput ? "已完成" : "结果同步中";
|
||||
if (status === "failed") return "失败";
|
||||
if (status === "expired") return "已过期";
|
||||
if (status === "cancelled") return "已取消";
|
||||
@@ -1844,7 +1862,7 @@ function formatDateTime(value: string) {
|
||||
}
|
||||
|
||||
function billingLabel(job: GenerationJob) {
|
||||
if (!job.billing) return job.provider === "mock" ? "免计费" : "未计费";
|
||||
if (!job.billing) return job.provider === "mock" ? "历史任务(未计费)" : "未计费";
|
||||
if (job.billing.quotaExempt) return `超管费用 ${formatBillingAmount(job.billing.amountFen)}(不计额度)`;
|
||||
if (job.billing.status === "refunded") return `已退款 ${formatBillingAmount(job.billing.amountFen)}`;
|
||||
if (job.billing.status === "pending") return `预计 ${formatBillingAmount(job.billing.amountFen)}`;
|
||||
|
||||
Reference in new issue
Block a user