feat: add Bailian image generation support
This commit is contained in:
@@ -470,6 +470,7 @@ function providerLabel(provider: GenerationJob["provider"]) {
|
||||
if (provider === "volcengine-visual") return "火山视觉";
|
||||
if (provider === "evolink") return "EvoLink";
|
||||
if (provider === "seedance") return "Seedance";
|
||||
if (provider === "bailian") return "阿里云百炼";
|
||||
return "Mock";
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ import { extractMaterialPlaceholders } from "@/lib/prompt/material-placeholders"
|
||||
type GenerateMode = "image" | "video";
|
||||
type StudioMode = GenerateMode | ImageEditMode;
|
||||
type MaterialKind = PromptMaterial["type"];
|
||||
type ImageGenerateEngine = "jimeng" | "evolink";
|
||||
type ImageGenerateEngine = "jimeng" | "evolink" | "bailian";
|
||||
type VideoGenerateEngine = "seedance" | "bailian";
|
||||
|
||||
type MentionState = {
|
||||
start: number;
|
||||
@@ -154,6 +155,7 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: StudioMo
|
||||
const [imageEngine, setImageEngine] = useState<ImageGenerateEngine>("jimeng");
|
||||
const [jimengInfluence, setJimengInfluence] = useState(jimengInfluenceOptions[1].id);
|
||||
const [evolinkQuality, setEvolinkQuality] = useState(evolinkQualityOptions[1].id);
|
||||
const [videoEngine, setVideoEngine] = useState<VideoGenerateEngine>("seedance");
|
||||
const [videoRatio, setVideoRatio] = useState("9:16");
|
||||
const [videoDuration, setVideoDuration] = useState(VIDEO_DURATION_DEFAULT);
|
||||
const [videoResolution, setVideoResolution] = useState("720p");
|
||||
@@ -247,7 +249,7 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: StudioMo
|
||||
.then((response) => response.ok ? response.json() : null)
|
||||
.then((payload: { capabilities?: HealthCapability[] } | null) => {
|
||||
const engine = payload?.capabilities?.find((capability) => capability.id === "image.generate")?.engine;
|
||||
if (active && (engine === "evolink" || engine === "jimeng")) setImageEngine(engine);
|
||||
if (active && (engine === "evolink" || engine === "jimeng" || engine === "bailian")) setImageEngine(engine);
|
||||
})
|
||||
.catch(() => undefined);
|
||||
return () => {
|
||||
@@ -743,6 +745,12 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: StudioMo
|
||||
setNotice(null);
|
||||
try {
|
||||
const endpoint = generateMode === "image" ? "/api/generations/image" : "/api/generations/video";
|
||||
if (generateMode === "video" && videoEngine === "bailian") {
|
||||
const imageCount = materials.filter((material) => material.type === "image").length;
|
||||
if (imageCount < 1 || imageCount > 2 || materials.some((material) => material.type !== "image")) {
|
||||
throw new Error("百炼图生视频请上传 1 张首帧图,或按顺序上传 2 张首尾帧图。");
|
||||
}
|
||||
}
|
||||
const body = generateMode === "image"
|
||||
? {
|
||||
capability: "image.generate",
|
||||
@@ -757,12 +765,13 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: StudioMo
|
||||
force_single: true
|
||||
}
|
||||
: {
|
||||
engine: videoEngine,
|
||||
prompt,
|
||||
materials,
|
||||
settings: {
|
||||
ratio: videoRatio,
|
||||
...(videoEngine === "seedance" ? { ratio: videoRatio } : {}),
|
||||
duration: videoDuration,
|
||||
resolution: videoResolution
|
||||
resolution: videoEngine === "bailian" ? videoResolution.toUpperCase() : videoResolution
|
||||
}
|
||||
};
|
||||
const response = await fetch(endpoint, {
|
||||
@@ -1056,6 +1065,7 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: StudioMo
|
||||
<select id="imageEngine" value={imageEngine} onChange={(event) => setImageEngine(normalizeImageEngine(event.target.value) || "jimeng")}>
|
||||
<option value="jimeng">即梦</option>
|
||||
<option value="evolink">Image2</option>
|
||||
<option value="bailian">阿里云百炼</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="field inline-field">
|
||||
@@ -1069,11 +1079,13 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: StudioMo
|
||||
</select>
|
||||
</div>
|
||||
<div className="field inline-field">
|
||||
<label htmlFor="imageEngineTuning">{imageEngine === "evolink" ? "生成质量" : "文本影响"}</label>
|
||||
<label htmlFor="imageEngineTuning">{imageEngine === "evolink" ? "生成质量" : imageEngine === "bailian" ? "生成模式" : "文本影响"}</label>
|
||||
{imageEngine === "evolink" ? (
|
||||
<select id="imageEngineTuning" value={evolinkQuality} onChange={(event) => setEvolinkQuality(event.target.value)}>
|
||||
{evolinkQualityOptions.map((option) => <option key={option.id} value={option.id}>{option.label}</option>)}
|
||||
</select>
|
||||
) : imageEngine === "bailian" ? (
|
||||
<select id="imageEngineTuning" value="thinking" disabled><option value="thinking">智能推理</option></select>
|
||||
) : (
|
||||
<select id="imageEngineTuning" value={jimengInfluence} onChange={(event) => setJimengInfluence(event.target.value)}>
|
||||
{jimengInfluenceOptions.map((option) => <option key={option.id} value={option.id}>{option.label}</option>)}
|
||||
@@ -1083,6 +1095,14 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: StudioMo
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="field inline-field">
|
||||
<label htmlFor="videoEngine">视频引擎</label>
|
||||
<select id="videoEngine" value={videoEngine} onChange={(event) => setVideoEngine(event.target.value === "bailian" ? "bailian" : "seedance")}>
|
||||
<option value="seedance">Seedance</option>
|
||||
<option value="bailian">阿里云百炼</option>
|
||||
</select>
|
||||
</div>
|
||||
{videoEngine === "seedance" ? (
|
||||
<div className="field inline-field">
|
||||
<label htmlFor="videoRatio">比例</label>
|
||||
<select id="videoRatio" value={videoRatio} onChange={(event) => setVideoRatio(event.target.value)}>
|
||||
@@ -1091,6 +1111,7 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: StudioMo
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="field inline-field">
|
||||
<label htmlFor="videoDuration">时长</label>
|
||||
<select
|
||||
@@ -1098,13 +1119,13 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: StudioMo
|
||||
value={videoDuration}
|
||||
onChange={(event) => setVideoDuration(clampVideoDuration(event.target.value, videoDuration, { allowAuto: false }))}
|
||||
>
|
||||
{VIDEO_DURATION_OPTIONS.map((seconds) => <option key={seconds} value={seconds}>{seconds} 秒</option>)}
|
||||
{(videoEngine === "bailian" ? Array.from({ length: 14 }, (_, index) => index + 2) : VIDEO_DURATION_OPTIONS).map((seconds) => <option key={seconds} value={seconds}>{seconds} 秒</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div className="field inline-field">
|
||||
<label htmlFor="videoResolution">清晰度</label>
|
||||
<select id="videoResolution" value={videoResolution} onChange={(event) => setVideoResolution(event.target.value)}>
|
||||
{VIDEO_RESOLUTIONS.map((resolution) => <option key={resolution} value={resolution}>{resolution}</option>)}
|
||||
{(videoEngine === "bailian" ? ["720p", "1080p"] : VIDEO_RESOLUTIONS).map((resolution) => <option key={resolution} value={resolution}>{resolution}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</>
|
||||
@@ -1537,12 +1558,12 @@ function sizeLabelFromSettings(width?: number, height?: number) {
|
||||
}
|
||||
|
||||
function normalizeImageEngine(value?: string): ImageGenerateEngine | undefined {
|
||||
if (value === "jimeng" || value === "evolink") return value;
|
||||
if (value === "jimeng" || value === "evolink" || value === "bailian") return value;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function imageEngineLabel(engine: ImageGenerateEngine) {
|
||||
return engine === "evolink" ? "Image2" : "即梦";
|
||||
return engine === "evolink" ? "Image2" : engine === "bailian" ? "百炼" : "即梦";
|
||||
}
|
||||
|
||||
function sortTemplates(a: ImageTemplate, b: ImageTemplate) {
|
||||
|
||||
@@ -28,6 +28,7 @@ type SettingsPayload = {
|
||||
visual: string;
|
||||
evolink: string;
|
||||
seedance: string;
|
||||
bailian: string;
|
||||
auth: string;
|
||||
organization: string;
|
||||
data: string;
|
||||
@@ -226,6 +227,7 @@ export function SettingsPanel() {
|
||||
<ServiceBadge label="即梦" value={payload?.modes.visual === "real" ? "真实接口" : "Mock"} ready={payload?.modes.visual === "real"} />
|
||||
<ServiceBadge label="EvoLink" value={payload?.modes.evolink === "real" ? "真实接口" : "Mock"} ready={payload?.modes.evolink === "real"} />
|
||||
<ServiceBadge label="Seedance" value={payload?.modes.seedance === "real" ? "真实接口" : "Mock"} ready={payload?.modes.seedance === "real"} />
|
||||
<ServiceBadge label="百炼" value={payload?.modes.bailian === "real" ? "真实接口" : payload?.modes.bailian === "mock" ? "Mock" : "未配置"} ready={payload?.modes.bailian === "real"} />
|
||||
<ServiceBadge label="账户" value={authModeLabel(payload?.modes.auth)} ready={payload?.modes.auth === "configured"} />
|
||||
<ServiceBadge label="组织" value={payload?.modes.organization === "configured" ? "已配置" : "待配置"} ready={payload?.modes.organization === "configured"} />
|
||||
<ServiceBadge label="数据层" value={payload?.modes.data === "supabase" ? "Supabase" : "本地 JSON"} ready={false} />
|
||||
|
||||
Reference in New Issue
Block a user