oss请求处理

This commit is contained in:
andy
2026-08-19 10:52:55 +08:00
parent 18cb51670c
commit a9a4cdf125
18 changed files with 450 additions and 29 deletions

View File

@@ -5,6 +5,7 @@ import { Download, Eye, ImageIcon, Info, Loader2, Music, RefreshCw, Trash2, X }
import { clampPage, pageItems, Pagination } from "@/components/pagination";
import { modalEnter, modalExit, pulseFeedback, revealChildren, runScopedMotion } from "@/lib/ui/motion";
import { formatBillingAmount } from "@/lib/billing";
import { assetPreviewUrl } from "@/lib/client/asset-urls";
import type { Asset, GenerationJob } from "@/lib/types";
type AssetView = "assets" | "tasks";
@@ -386,10 +387,10 @@ function renderAssetPreview(asset: Asset) {
);
}
if (asset.kind === "video" || isVideo(asset)) {
return <video className="asset-thumb" src={asset.url} muted playsInline preload="metadata" />;
return <video className="asset-thumb" src={assetPreviewUrl(asset)} muted playsInline preload="metadata" />;
}
if (asset.kind === "image" || asset.kind === "mask" || asset.kind === "reference" || isImage(asset)) {
return <img className="asset-thumb" src={asset.url} alt={asset.name} />;
return <img className="asset-thumb" src={assetPreviewUrl(asset)} alt={asset.name} />;
}
return (
<div className="asset-placeholder asset-kind-placeholder">
@@ -400,10 +401,10 @@ function renderAssetPreview(asset: Asset) {
}
function renderAssetPreviewLarge(asset: Asset) {
if (isAudio(asset)) return <audio src={asset.url} controls />;
if (asset.kind === "video" || isVideo(asset)) return <video src={asset.url} controls playsInline />;
if (isAudio(asset)) return <audio src={assetPreviewUrl(asset)} controls />;
if (asset.kind === "video" || isVideo(asset)) return <video src={assetPreviewUrl(asset)} controls playsInline />;
if (asset.kind === "image" || asset.kind === "mask" || asset.kind === "reference" || isImage(asset)) {
return <img src={asset.url} alt={asset.name} />;
return <img src={assetPreviewUrl(asset)} alt={asset.name} />;
}
return (
<div className="asset-placeholder asset-kind-placeholder">

View File

@@ -17,6 +17,7 @@ import {
} from "@/lib/prompt/material-draft";
import { extractMaterialPlaceholders } from "@/lib/prompt/material-placeholders";
import { formatBillingAmount } from "@/lib/billing";
import { assetPreviewUrl, materialPreviewUrl, storedAssetPreviewUrl } from "@/lib/client/asset-urls";
import type { BillingQuote } from "@/lib/types";
type GenerateMode = "image" | "video";
@@ -217,6 +218,7 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: Generate
() => recentJobs.find((job) => job.id === taskDetailJobId) || null,
[recentJobs, taskDetailJobId]
);
const previewTemplateImageURL = storedAssetPreviewUrl(previewTemplate?.previewImageUrl, taskAssets);
const hasLiveTasks = useMemo(() => recentJobs.some((job) => !isTerminalStatus(job.status)), [recentJobs]);
useEffect(() => {
@@ -476,7 +478,7 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: Generate
const [asset] = await uploadAssetFiles([file]);
setTemplateForm((form) => ({
...form,
previewImageUrl: asset.url,
previewImageUrl: assetPreviewUrl(asset),
name: form.name || asset.name.replace(/\.[^.]+$/, "")
}));
} catch (err) {
@@ -608,7 +610,10 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: Generate
}
function openTemplateEditor(template: ImageTemplate) {
setTemplateForm(formFromTemplate(template));
setTemplateForm({
...formFromTemplate(template),
previewImageUrl: storedAssetPreviewUrl(template.previewImageUrl, taskAssets)
});
setEditingTemplateId(template.id);
setTemplateError(null);
setMaterialDraft(null);
@@ -919,7 +924,7 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: Generate
aria-label={`放大预览 ${template.name}`}
onClick={() => setPreviewTemplate(template)}
>
{renderImageTemplatePreview(template)}
{renderImageTemplatePreview(template, taskAssets)}
</button>
<div className="image-template-card-footer">
<div className="image-template-card-copy">
@@ -1363,11 +1368,11 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: Generate
</button>
</div>
<div className="template-preview-dialog-media">
{previewTemplate.previewImageUrl ? <img src={previewTemplate.previewImageUrl} alt="" /> : <ImagePlus size={40} />}
{previewTemplateImageURL ? <img src={previewTemplateImageURL} alt="" /> : <ImagePlus size={40} />}
</div>
<div className="template-preview-dialog-actions">
{previewTemplate.previewImageUrl ? (
<a className="button" href={previewTemplate.previewImageUrl} download>
{previewTemplateImageURL ? (
<a className="button" href={previewTemplateImageURL} download>
<Download size={16} />
下载图片
</a>
@@ -1495,8 +1500,8 @@ function renderTaskThumbnail(job: GenerationJob, assetById: Map<string, Asset>)
}
const isVideo = asset.kind === "video" || materialTypeForAsset(asset) === "video";
const media = isVideo
? <video src={asset.url} muted playsInline preload="metadata" />
: <img src={asset.url} alt="" />;
? <video src={assetPreviewUrl(asset)} muted playsInline preload="metadata" />
: <img src={assetPreviewUrl(asset)} alt="" />;
return <div className="create-task-thumb" aria-hidden="true">{media}</div>;
}
@@ -1698,9 +1703,9 @@ function BillingEstimate({
function renderTaskAssetMedia(asset: Asset, size: "thumb" | "large" = "large") {
const type = materialTypeForAsset(asset);
if (type === "video") return <video className={`task-detail-media ${size}`} src={asset.url} controls={size === "large"} muted={size === "thumb"} playsInline preload="metadata" />;
if (type === "audio") return <audio className="task-detail-audio" src={asset.url} controls />;
return <img className={`task-detail-media ${size}`} src={asset.url} alt={asset.name} />;
if (type === "video") return <video className={`task-detail-media ${size}`} src={assetPreviewUrl(asset)} controls={size === "large"} muted={size === "thumb"} playsInline preload="metadata" />;
if (type === "audio") return <audio className="task-detail-audio" src={assetPreviewUrl(asset)} controls />;
return <img className={`task-detail-media ${size}`} src={assetPreviewUrl(asset)} alt={asset.name} />;
}
function renderMaterialUrlPreview(url: string, type: MaterialKind) {
@@ -1892,10 +1897,11 @@ function formatDuration(ms: number) {
}
function renderMaterialPreview(material: PromptMaterial, size: "normal" | "tiny" | "large" = "normal") {
const previewURL = materialPreviewUrl(material);
return (
<span className={clsx("material-preview", size, material.type)} aria-hidden="true">
{material.type === "image" ? <img src={material.url} alt="" /> : null}
{material.type === "video" ? <video src={material.url} muted playsInline preload="metadata" /> : null}
{material.type === "image" ? <img src={previewURL} alt="" /> : null}
{material.type === "video" ? <video src={previewURL} muted playsInline preload="metadata" /> : null}
{material.type === "audio" ? <Music size={size === "tiny" ? 13 : size === "large" ? 20 : 15} /> : null}
</span>
);
@@ -1966,10 +1972,11 @@ function sortTemplates(a: ImageTemplate, b: ImageTemplate) {
return (b.updatedAt || "").localeCompare(a.updatedAt || "");
}
function renderImageTemplatePreview(template: ImageTemplate) {
function renderImageTemplatePreview(template: ImageTemplate, assets: Asset[]) {
const previewURL = storedAssetPreviewUrl(template.previewImageUrl, assets);
return (
<span className="image-template-preview" aria-hidden="true">
{template.previewImageUrl ? <img src={template.previewImageUrl} alt="" /> : <ImagePlus size={22} />}
{previewURL ? <img src={previewURL} alt="" /> : <ImagePlus size={22} />}
</span>
);
}