fix: align generation size and duration options
This commit is contained in:
@@ -29,6 +29,7 @@ import {
|
||||
type GeneratedVideo,
|
||||
type Job,
|
||||
type RuntimeModelOption,
|
||||
type RuntimeSizeOption,
|
||||
} from "@/lib/api"
|
||||
|
||||
type CreationMode = "text-video" | "text-image" | "first-frame-video" | "first-last-frame-video"
|
||||
@@ -99,6 +100,19 @@ function isVideoMode(mode: CreationMode) {
|
||||
return mode !== "text-image"
|
||||
}
|
||||
|
||||
const DEFAULT_IMAGE_SIZE_OPTIONS: RuntimeSizeOption[] = [
|
||||
{ id: "1024x1536", label: "竖图 2:3", value: "1024x1536" },
|
||||
{ id: "1024x1024", label: "方图 1:1", value: "1024x1024" },
|
||||
{ id: "1536x1024", label: "横图 3:2", value: "1536x1024" },
|
||||
{ id: "auto", label: "自动", value: "auto" },
|
||||
]
|
||||
|
||||
const DEFAULT_VIDEO_SIZE_OPTIONS: RuntimeSizeOption[] = [
|
||||
{ id: "720x1280", label: "竖屏 9:16", value: "720x1280" },
|
||||
{ id: "1280x720", label: "横屏 16:9", value: "1280x720" },
|
||||
{ id: "1024x1024", label: "方形 1:1", value: "1024x1024" },
|
||||
]
|
||||
|
||||
export default function Home() {
|
||||
const [mode, setMode] = useState<CreationMode>("text-video")
|
||||
const [prompt, setPrompt] = useState("")
|
||||
@@ -109,12 +123,17 @@ export default function Home() {
|
||||
const [lastFramePreview, setLastFramePreview] = useState("")
|
||||
const [imageModel, setImageModel] = useState("auto")
|
||||
const [videoModel, setVideoModel] = useState("seedance")
|
||||
const [imageSize, setImageSize] = useState("1024x1536")
|
||||
const [videoSize, setVideoSize] = useState("720x1280")
|
||||
const [videoDurationOptions, setVideoDurationOptions] = useState<number[]>([5, 8, 10, 12, 15])
|
||||
const [imageOptions, setImageOptions] = useState<RuntimeModelOption[]>([
|
||||
{ id: "auto", label: "自动", model: "gpt-image-2", available: true },
|
||||
])
|
||||
const [videoOptions, setVideoOptions] = useState<RuntimeModelOption[]>([
|
||||
{ id: "seedance", label: "Seedance", model: "seedance", available: true },
|
||||
])
|
||||
const [imageSizeOptions, setImageSizeOptions] = useState<RuntimeSizeOption[]>(DEFAULT_IMAGE_SIZE_OPTIONS)
|
||||
const [videoSizeOptions, setVideoSizeOptions] = useState<RuntimeSizeOption[]>(DEFAULT_VIDEO_SIZE_OPTIONS)
|
||||
const [job, setJob] = useState<Job | null>(null)
|
||||
const [busy, setBusy] = useState<BusyTask>(null)
|
||||
const [error, setError] = useState("")
|
||||
@@ -140,14 +159,26 @@ export default function Home() {
|
||||
const nextVideoOptions = models?.video_options?.length
|
||||
? models.video_options
|
||||
: [{ id: models?.video || "seedance", label: "Seedance", model: models?.video || "seedance", available: !!models?.video_configured }]
|
||||
const nextImageSizeOptions = models?.image_size_options?.length ? models.image_size_options : DEFAULT_IMAGE_SIZE_OPTIONS
|
||||
const nextVideoSizeOptions = models?.video_size_options?.length ? models.video_size_options : DEFAULT_VIDEO_SIZE_OPTIONS
|
||||
const nextDurationOptions = models?.video_duration_options?.length ? models.video_duration_options : [5, 8, 10, 12, 15]
|
||||
setImageOptions(nextImageOptions)
|
||||
setVideoOptions(nextVideoOptions)
|
||||
setImageSizeOptions(nextImageSizeOptions)
|
||||
setVideoSizeOptions(nextVideoSizeOptions)
|
||||
setVideoDurationOptions(nextDurationOptions)
|
||||
if (!nextImageOptions.some((item) => item.id === imageModel)) setImageModel(nextImageOptions[0]?.id || "auto")
|
||||
if (!nextVideoOptions.some((item) => item.id === videoModel)) setVideoModel(nextVideoOptions[0]?.id || "seedance")
|
||||
if (!nextImageSizeOptions.some((item) => item.value === imageSize)) setImageSize(nextImageSizeOptions[0]?.value || "1024x1536")
|
||||
if (!nextVideoSizeOptions.some((item) => item.value === videoSize)) setVideoSize(nextVideoSizeOptions[0]?.value || "720x1280")
|
||||
if (!nextDurationOptions.includes(seconds)) setSeconds(nextDurationOptions.includes(12) ? 12 : (nextDurationOptions[0] ?? 5))
|
||||
})
|
||||
.catch(() => {
|
||||
setImageOptions([{ id: "auto", label: "自动", model: "gpt-image-2", available: true }])
|
||||
setVideoOptions([{ id: "seedance", label: "Seedance", model: "seedance", available: true }])
|
||||
setImageSizeOptions(DEFAULT_IMAGE_SIZE_OPTIONS)
|
||||
setVideoSizeOptions(DEFAULT_VIDEO_SIZE_OPTIONS)
|
||||
setVideoDurationOptions([5, 8, 10, 12, 15])
|
||||
})
|
||||
}, [])
|
||||
|
||||
@@ -246,6 +277,7 @@ export default function Home() {
|
||||
prompt: promptWithGuardrails(),
|
||||
mode: "text",
|
||||
model: imageModel,
|
||||
size: imageSize,
|
||||
})
|
||||
setJob(updated)
|
||||
toast.success("图片已生成")
|
||||
@@ -271,7 +303,7 @@ export default function Home() {
|
||||
count: 1,
|
||||
first_image: activeMode.needsFirstFrame ? { kind: "keyframe", frame_idx: 0 } : null,
|
||||
last_image: activeMode.needsLastFrame && lastFrame ? { kind: "keyframe", frame_idx: lastFrame.index } : null,
|
||||
size: "720x1280",
|
||||
size: videoSize,
|
||||
model: videoModel,
|
||||
})
|
||||
setJob(updated)
|
||||
@@ -423,17 +455,48 @@ export default function Home() {
|
||||
</select>
|
||||
</label>
|
||||
{isVideoMode(mode) ? (
|
||||
<>
|
||||
<label className="inline-flex h-9 items-center gap-2 rounded-xl border border-white/7 bg-black/14 px-3">
|
||||
画幅
|
||||
<select
|
||||
value={videoSize}
|
||||
onChange={(event) => setVideoSize(event.target.value)}
|
||||
className="max-w-32 bg-transparent text-white/76 outline-none"
|
||||
>
|
||||
{videoSizeOptions.map((item) => (
|
||||
<option key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="inline-flex h-9 items-center gap-2 rounded-xl border border-white/7 bg-black/14 px-3">
|
||||
时长
|
||||
<select
|
||||
value={seconds}
|
||||
onChange={(event) => setSeconds(Number(event.target.value))}
|
||||
className="bg-transparent text-white/76 outline-none"
|
||||
>
|
||||
{videoDurationOptions.map((value) => <option key={value} value={value}>{value}s</option>)}
|
||||
</select>
|
||||
</label>
|
||||
</>
|
||||
) : (
|
||||
<label className="inline-flex h-9 items-center gap-2 rounded-xl border border-white/7 bg-black/14 px-3">
|
||||
时长
|
||||
尺寸
|
||||
<select
|
||||
value={seconds}
|
||||
onChange={(event) => setSeconds(Number(event.target.value))}
|
||||
className="bg-transparent text-white/76 outline-none"
|
||||
value={imageSize}
|
||||
onChange={(event) => setImageSize(event.target.value)}
|
||||
className="max-w-32 bg-transparent text-white/76 outline-none"
|
||||
>
|
||||
{[5, 8, 12, 15, 20, 30].map((value) => <option key={value} value={value}>{value}s</option>)}
|
||||
{imageSizeOptions.map((item) => (
|
||||
<option key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
) : null}
|
||||
)}
|
||||
<span>{activeMode.needsFirstFrame ? "图片作为参考帧" : "只根据文字生成"}</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -260,6 +260,16 @@ export interface RuntimeModelOption {
|
||||
model: string
|
||||
description?: string
|
||||
available?: boolean
|
||||
duration_options?: number[]
|
||||
max_duration_seconds?: number
|
||||
size_options?: RuntimeSizeOption[]
|
||||
}
|
||||
|
||||
export interface RuntimeSizeOption {
|
||||
id: string
|
||||
label: string
|
||||
value: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export interface RuntimeModels {
|
||||
@@ -280,6 +290,7 @@ export interface RuntimeModels {
|
||||
image?: string
|
||||
image_base_url?: string
|
||||
image_options?: RuntimeModelOption[]
|
||||
image_size_options?: RuntimeSizeOption[]
|
||||
image_fallbacks?: string[]
|
||||
image_circuit?: {
|
||||
primary?: string
|
||||
@@ -303,6 +314,9 @@ export interface RuntimeModels {
|
||||
video?: string
|
||||
video_aliases?: Record<string, string>
|
||||
video_options?: RuntimeModelOption[]
|
||||
video_duration_options?: number[]
|
||||
video_max_duration_seconds?: number
|
||||
video_size_options?: RuntimeSizeOption[]
|
||||
video_provider?: string
|
||||
video_base_url?: string
|
||||
video_configured?: boolean
|
||||
@@ -1231,7 +1245,7 @@ export async function translateText(text: string, target: "en" | "zh" = "en"): P
|
||||
export async function generateImage(
|
||||
jobId: string,
|
||||
frameIdx: number,
|
||||
body: { prompt: string; extra_prompt?: string; negative_prompt?: string; model?: string; mode?: "edit" | "text"; from_selected?: boolean },
|
||||
body: { prompt: string; extra_prompt?: string; negative_prompt?: string; model?: string; size?: string; mode?: "edit" | "text"; from_selected?: boolean },
|
||||
): Promise<Job> {
|
||||
const res = await fetch(`${API_BASE}/jobs/${jobId}/frames/${frameIdx}/generate`, {
|
||||
method: "POST",
|
||||
|
||||
Reference in New Issue
Block a user