From e36f28a6687e819328cb14dc6796cdcb877062c4 Mon Sep 17 00:00:00 2001 From: inman Date: Fri, 29 May 2026 14:32:02 +0800 Subject: [PATCH] feat: adapt image tuning by engine --- app/api/generations/image/route.ts | 4 ++- app/api/v1/openapi.json/route.ts | 3 +- components/create-studio.tsx | 57 +++++++++++++++++++++++++++--- docs/API.md | 4 +++ findings.md | 5 +++ lib/evolink/image-client.ts | 3 +- lib/server/generation-service.ts | 1 + lib/server/public-api-jobs.ts | 8 +++++ progress.md | 21 +++++++++++ task_plan.md | 8 +++++ tests/evolink-image-client.test.ts | 7 ++-- tests/task-management.test.ts | 27 ++++++++++++++ 12 files changed, 138 insertions(+), 10 deletions(-) diff --git a/app/api/generations/image/route.ts b/app/api/generations/image/route.ts index c592106..884da00 100644 --- a/app/api/generations/image/route.ts +++ b/app/api/generations/image/route.ts @@ -31,6 +31,7 @@ export async function POST(request: Request) { min_ratio?: number; max_ratio?: number; force_single?: boolean; + quality?: string; }>(request); const capability = body.capability || "image.generate"; const assembled = body.promptAssembly @@ -49,7 +50,8 @@ export async function POST(request: Request) { height: asNumber(body.height), min_ratio: asNumber(body.min_ratio), max_ratio: asNumber(body.max_ratio), - force_single: Boolean(body.force_single) + force_single: Boolean(body.force_single), + quality: typeof body.quality === "string" ? body.quality : undefined }, requestOrigin(request)); return jsonOk({ job: await getGenerationJob(job.id) }, { status: 202 }); } catch (error) { diff --git a/app/api/v1/openapi.json/route.ts b/app/api/v1/openapi.json/route.ts index e5a4ccf..47eb9d3 100644 --- a/app/api/v1/openapi.json/route.ts +++ b/app/api/v1/openapi.json/route.ts @@ -120,8 +120,9 @@ export async function GET(request: Request) { }, width: { type: "integer", example: 1440 }, height: { type: "integer", example: 2560 }, - scale: { type: "number", minimum: 1, maximum: 100 }, + scale: { type: "number", minimum: 1, maximum: 100, description: "Jimeng text influence for image.generate and detail strength for image.upscale." }, force_single: { type: "boolean" }, + quality: { type: "string", enum: ["low", "medium", "high"], description: "EvoLink image quality for image.generate and image.inpaint." }, resolution: { type: "string", enum: ["4k", "8k"], description: "Upscale resolution for image.upscale." }, seed: { type: "integer" }, priority: { type: "integer", minimum: -100, maximum: 100 }, diff --git a/components/create-studio.tsx b/components/create-studio.tsx index ba8cd96..f92af35 100644 --- a/components/create-studio.tsx +++ b/components/create-studio.tsx @@ -13,12 +13,31 @@ import type { PromptMaterial } from "@/lib/prompt/assembler"; type GenerateMode = "image" | "video"; type StudioMode = GenerateMode | ImageEditMode; type MaterialKind = PromptMaterial["type"]; +type ImageGenerateEngine = "jimeng" | "evolink"; type MentionState = { start: number; query: string; }; +type HealthCapability = { + id: string; + engine?: string; +}; + +const jimengInfluenceOptions = [ + { id: "creative", label: "创意 35", scale: 35 }, + { id: "balanced", label: "均衡 50", scale: 50 }, + { id: "precise", label: "贴合 70", scale: 70 }, + { id: "strict", label: "严格 85", scale: 85 } +]; + +const evolinkQualityOptions = [ + { id: "low", label: "快速", quality: "low" }, + { id: "medium", label: "标准", quality: "medium" }, + { id: "high", label: "精细", quality: "high" } +]; + const imageSizePresets = [ { label: "1:1", width: 2048, height: 2048 }, { label: "4:3", width: 2304, height: 1728 }, @@ -41,7 +60,9 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: StudioMo const [error, setError] = useState(null); const [notice, setNotice] = useState(null); const [imageSize, setImageSize] = useState(imageSizePresets[0]); - const [imageScale, setImageScale] = useState(50); + const [imageEngine, setImageEngine] = useState("jimeng"); + const [jimengInfluence, setJimengInfluence] = useState(jimengInfluenceOptions[1].id); + const [evolinkQuality, setEvolinkQuality] = useState(evolinkQualityOptions[1].id); const [forceSingle, setForceSingle] = useState(true); const [videoRatio, setVideoRatio] = useState("9:16"); const [videoDuration, setVideoDuration] = useState(VIDEO_DURATION_DEFAULT); @@ -59,6 +80,8 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: StudioMo const isImageEditMode = mode === "inpaint" || mode === "upscale"; const generateMode: GenerateMode = mode === "video" ? "video" : "image"; const prompt = promptByMode[generateMode]; + const selectedJimengInfluence = jimengInfluenceOptions.find((option) => option.id === jimengInfluence) || jimengInfluenceOptions[1]; + const selectedEvolinkQuality = evolinkQualityOptions.find((option) => option.id === evolinkQuality) || evolinkQualityOptions[1]; const visibleMaterials = pageItems(materials, materialPage, MATERIAL_PAGE_SIZE); const materialPageOffset = (clampPage(materialPage, materials.length, MATERIAL_PAGE_SIZE) - 1) * MATERIAL_PAGE_SIZE; const mentionSuggestions = useMemo(() => { @@ -90,6 +113,20 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: StudioMo if (materialBoardRef.current) revealChildren(materialBoardRef.current, ".material-card"); }, [materials.length, materialPage]); + useEffect(() => { + let active = true; + void fetch("/api/health") + .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); + }) + .catch(() => undefined); + return () => { + active = false; + }; + }, []); + function setPrompt(value: string) { setPromptByMode((items) => ({ ...items, [generateMode]: value })); } @@ -229,7 +266,9 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: StudioMo materials: materials.filter((material) => material.type === "image"), width: imageSize.width, height: imageSize.height, - scale: imageScale, + ...(imageEngine === "evolink" + ? { quality: selectedEvolinkQuality.quality } + : { scale: selectedJimengInfluence.scale }), force_single: forceSingle } : { @@ -432,9 +471,17 @@ export function CreateStudio({ initialMode = "image" }: { initialMode?: StudioMo {imageSizePresets.map((preset) => )} -
- - setImageScale(Number(event.target.value))} /> +
+ + {imageEngine === "evolink" ? ( + + ) : ( + + )}