fix: send required name and prompt fields for AI Video Effects
The generate_wan_ai_effects endpoint requires both `name` (effect type) and `prompt` (str) fields. The client was sending neither — `name` had no state/UI/payload entry, and `prompt` was omitted when blank — causing a 422 Unprocessable Entity error. - Add selectedEffectName state and getEffectNamesForModel() helper - Add Effect dropdown button (visible only for ai-video-effects / motion-controls) with the full enum list from the model definition - Wire updateControlsForModel to initialize/reset selectedEffectName - Pass name in i2vParams at generate time - Always send prompt (defaults to '') so the required str field is present - Forward params.name in muapi.js generateI2V payload Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6c47f0dca3
commit
1c33c1be7b
2 changed files with 52 additions and 3 deletions
|
|
@ -17,6 +17,7 @@ export function VideoStudio() {
|
||||||
let selectedResolution = defaultModel.inputs?.resolution?.default || '';
|
let selectedResolution = defaultModel.inputs?.resolution?.default || '';
|
||||||
let selectedQuality = defaultModel.inputs?.quality?.default || '';
|
let selectedQuality = defaultModel.inputs?.quality?.default || '';
|
||||||
let selectedMode = '';
|
let selectedMode = '';
|
||||||
|
let selectedEffectName = '';
|
||||||
let lastGenerationId = null;
|
let lastGenerationId = null;
|
||||||
let lastGenerationModel = null;
|
let lastGenerationModel = null;
|
||||||
let dropdownOpen = null;
|
let dropdownOpen = null;
|
||||||
|
|
@ -35,6 +36,10 @@ export function VideoStudio() {
|
||||||
const model = getCurrentModels().find(m => m.id === id);
|
const model = getCurrentModels().find(m => m.id === id);
|
||||||
return model?.inputs?.quality?.enum || [];
|
return model?.inputs?.quality?.enum || [];
|
||||||
};
|
};
|
||||||
|
const getEffectNamesForModel = (id) => {
|
||||||
|
const model = getCurrentModels().find(m => m.id === id);
|
||||||
|
return model?.inputs?.name?.enum || [];
|
||||||
|
};
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// 1. HERO SECTION
|
// 1. HERO SECTION
|
||||||
|
|
@ -291,11 +296,16 @@ export function VideoStudio() {
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" class="opacity-60 text-secondary"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" class="opacity-60 text-secondary"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||||||
`, selectedMode || 'normal', 'v-mode-btn');
|
`, selectedMode || 'normal', 'v-mode-btn');
|
||||||
|
|
||||||
|
const effectNameBtn = createControlBtn(`
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" class="opacity-60 text-secondary"><path d="M12 2l2.4 7.4H22l-6.2 4.5 2.4 7.4L12 17l-6.2 4.3 2.4-7.4L2 9.4h7.6L12 2z"/></svg>
|
||||||
|
`, 'Effect', 'v-effect-btn', 'Select effect type');
|
||||||
|
|
||||||
controlsLeft.appendChild(modelBtn);
|
controlsLeft.appendChild(modelBtn);
|
||||||
controlsLeft.appendChild(arBtn);
|
controlsLeft.appendChild(arBtn);
|
||||||
controlsLeft.appendChild(durationBtn);
|
controlsLeft.appendChild(durationBtn);
|
||||||
controlsLeft.appendChild(resolutionBtn);
|
controlsLeft.appendChild(resolutionBtn);
|
||||||
controlsLeft.appendChild(qualityBtn);
|
controlsLeft.appendChild(qualityBtn);
|
||||||
|
controlsLeft.appendChild(effectNameBtn);
|
||||||
|
|
||||||
// Advanced options toggle button
|
// Advanced options toggle button
|
||||||
const advancedBtn = createControlBtn(`
|
const advancedBtn = createControlBtn(`
|
||||||
|
|
@ -310,6 +320,7 @@ export function VideoStudio() {
|
||||||
resolutionBtn.style.display = initResolutions.length > 0 ? 'flex' : 'none';
|
resolutionBtn.style.display = initResolutions.length > 0 ? 'flex' : 'none';
|
||||||
qualityBtn.style.display = 'none';
|
qualityBtn.style.display = 'none';
|
||||||
modeBtn.style.display = getModesForModel(defaultModel.id).length > 0 ? 'flex' : 'none';
|
modeBtn.style.display = getModesForModel(defaultModel.id).length > 0 ? 'flex' : 'none';
|
||||||
|
effectNameBtn.style.display = 'none';
|
||||||
|
|
||||||
const generateBtn = document.createElement('button');
|
const generateBtn = document.createElement('button');
|
||||||
generateBtn.className = 'bg-primary text-black px-6 md:px-8 py-3 md:py-3.5 rounded-xl md:rounded-[1.5rem] font-black text-sm md:text-base hover:shadow-glow hover:scale-105 active:scale-95 transition-all flex items-center justify-center gap-2.5 w-full sm:w-auto shadow-lg';
|
generateBtn.className = 'bg-primary text-black px-6 md:px-8 py-3 md:py-3.5 rounded-xl md:rounded-[1.5rem] font-black text-sm md:text-base hover:shadow-glow hover:scale-105 active:scale-95 transition-all flex items-center justify-center gap-2.5 w-full sm:w-auto shadow-lg';
|
||||||
|
|
@ -338,6 +349,7 @@ export function VideoStudio() {
|
||||||
resolutionBtn.style.display = 'none';
|
resolutionBtn.style.display = 'none';
|
||||||
qualityBtn.style.display = 'none';
|
qualityBtn.style.display = 'none';
|
||||||
modeBtn.style.display = 'none';
|
modeBtn.style.display = 'none';
|
||||||
|
effectNameBtn.style.display = 'none';
|
||||||
extendBanner.classList.add('hidden');
|
extendBanner.classList.add('hidden');
|
||||||
extendBanner.classList.remove('flex');
|
extendBanner.classList.remove('flex');
|
||||||
return;
|
return;
|
||||||
|
|
@ -395,6 +407,17 @@ export function VideoStudio() {
|
||||||
modeBtn.style.display = 'none';
|
modeBtn.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Effect name (ai-video-effects / motion-controls)
|
||||||
|
const effectNames = getEffectNamesForModel(modelId);
|
||||||
|
if (effectNames.length > 0) {
|
||||||
|
selectedEffectName = model?.inputs?.name?.default || effectNames[0];
|
||||||
|
document.getElementById('v-effect-btn-label').textContent = selectedEffectName;
|
||||||
|
effectNameBtn.style.display = 'flex';
|
||||||
|
} else {
|
||||||
|
selectedEffectName = '';
|
||||||
|
effectNameBtn.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
// Extend banner (extend model only)
|
// Extend banner (extend model only)
|
||||||
if (model?.requiresRequestId) {
|
if (model?.requiresRequestId) {
|
||||||
extendBanner.classList.remove('hidden');
|
extendBanner.classList.remove('hidden');
|
||||||
|
|
@ -617,6 +640,29 @@ export function VideoStudio() {
|
||||||
list.appendChild(item);
|
list.appendChild(item);
|
||||||
});
|
});
|
||||||
dropdown.appendChild(list);
|
dropdown.appendChild(list);
|
||||||
|
|
||||||
|
} else if (type === 'effect') {
|
||||||
|
dropdown.classList.add('max-w-[240px]');
|
||||||
|
dropdown.classList.remove('max-w-[200px]');
|
||||||
|
dropdown.innerHTML = `<div class="text-[10px] font-bold text-secondary uppercase tracking-widest px-3 py-2 border-b border-white/5 mb-2">Effect Type</div>`;
|
||||||
|
const list = document.createElement('div');
|
||||||
|
list.className = 'flex flex-col gap-1 max-h-[50vh] overflow-y-auto custom-scrollbar';
|
||||||
|
getEffectNamesForModel(selectedModel).forEach(e => {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'flex items-center justify-between p-3 hover:bg-white/5 rounded-2xl cursor-pointer transition-all group';
|
||||||
|
item.innerHTML = `
|
||||||
|
<span class="text-xs font-bold text-white opacity-80 group-hover:opacity-100">${e}</span>
|
||||||
|
${selectedEffectName === e ? '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#d9ff00" stroke-width="4"><polyline points="20 6 9 17 4 12"/></svg>' : ''}
|
||||||
|
`;
|
||||||
|
item.onclick = (ev) => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
selectedEffectName = e;
|
||||||
|
document.getElementById('v-effect-btn-label').textContent = e;
|
||||||
|
closeDropdown();
|
||||||
|
};
|
||||||
|
list.appendChild(item);
|
||||||
|
});
|
||||||
|
dropdown.appendChild(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Position dropdown
|
// Position dropdown
|
||||||
|
|
@ -650,6 +696,7 @@ export function VideoStudio() {
|
||||||
resolutionBtn.onclick = toggleDropdown('resolution', resolutionBtn);
|
resolutionBtn.onclick = toggleDropdown('resolution', resolutionBtn);
|
||||||
qualityBtn.onclick = toggleDropdown('quality', qualityBtn);
|
qualityBtn.onclick = toggleDropdown('quality', qualityBtn);
|
||||||
modeBtn.onclick = toggleDropdown('mode', modeBtn);
|
modeBtn.onclick = toggleDropdown('mode', modeBtn);
|
||||||
|
effectNameBtn.onclick = toggleDropdown('effect', effectNameBtn);
|
||||||
|
|
||||||
window.addEventListener('click', closeDropdown);
|
window.addEventListener('click', closeDropdown);
|
||||||
container.appendChild(dropdown);
|
container.appendChild(dropdown);
|
||||||
|
|
@ -977,7 +1024,7 @@ export function VideoStudio() {
|
||||||
image_url: uploadedImageUrl,
|
image_url: uploadedImageUrl,
|
||||||
onRequestId,
|
onRequestId,
|
||||||
};
|
};
|
||||||
if (prompt) i2vParams.prompt = prompt;
|
i2vParams.prompt = prompt || '';
|
||||||
i2vParams.aspect_ratio = selectedAr;
|
i2vParams.aspect_ratio = selectedAr;
|
||||||
const durations = getCurrentDurations(selectedModel);
|
const durations = getCurrentDurations(selectedModel);
|
||||||
if (durations.length > 0) i2vParams.duration = selectedDuration;
|
if (durations.length > 0) i2vParams.duration = selectedDuration;
|
||||||
|
|
@ -985,6 +1032,7 @@ export function VideoStudio() {
|
||||||
if (resolutions.length > 0) i2vParams.resolution = selectedResolution;
|
if (resolutions.length > 0) i2vParams.resolution = selectedResolution;
|
||||||
if (selectedQuality) i2vParams.quality = selectedQuality;
|
if (selectedQuality) i2vParams.quality = selectedQuality;
|
||||||
if (selectedMode) i2vParams.mode = selectedMode;
|
if (selectedMode) i2vParams.mode = selectedMode;
|
||||||
|
if (selectedEffectName) i2vParams.name = selectedEffectName;
|
||||||
|
|
||||||
const res = await muapi.generateI2V(i2vParams);
|
const res = await muapi.generateI2V(i2vParams);
|
||||||
console.log('[VideoStudio] I2V response:', res);
|
console.log('[VideoStudio] I2V response:', res);
|
||||||
|
|
|
||||||
|
|
@ -245,7 +245,7 @@ export class MuapiClient {
|
||||||
const finalPayload = {};
|
const finalPayload = {};
|
||||||
|
|
||||||
// Only include prompt if the model supports it and one was provided
|
// Only include prompt if the model supports it and one was provided
|
||||||
if (params.prompt) finalPayload.prompt = params.prompt;
|
finalPayload.prompt = params.prompt || '';
|
||||||
|
|
||||||
// Place the uploaded image(s) in the correct field for this model
|
// Place the uploaded image(s) in the correct field for this model
|
||||||
const imageField = modelInfo?.imageField || 'image_url';
|
const imageField = modelInfo?.imageField || 'image_url';
|
||||||
|
|
@ -331,6 +331,7 @@ export class MuapiClient {
|
||||||
if (params.resolution) finalPayload.resolution = params.resolution;
|
if (params.resolution) finalPayload.resolution = params.resolution;
|
||||||
if (params.quality) finalPayload.quality = params.quality;
|
if (params.quality) finalPayload.quality = params.quality;
|
||||||
if (params.mode) finalPayload.mode = params.mode;
|
if (params.mode) finalPayload.mode = params.mode;
|
||||||
|
if (params.name) finalPayload.name = params.name;
|
||||||
|
|
||||||
console.log('[Muapi] I2V Request:', url);
|
console.log('[Muapi] I2V Request:', url);
|
||||||
console.log('[Muapi] I2V Payload:', finalPayload);
|
console.log('[Muapi] I2V Payload:', finalPayload);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue