feat(lipsync): add max duration limit display per model

Adds maxDuration field to all 9 lipsync models and surfaces it in the
UI as a badge in the model dropdown and a live pill in the control bar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gal Inbar 2026-04-29 10:46:47 +03:00
commit 6ea0d82400
5 changed files with 56 additions and 4 deletions

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "open-generative-ai",
"version": "1.0.8",
"version": "1.0.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "open-generative-ai",
"version": "1.0.8",
"version": "1.0.9",
"workspaces": [
"packages/studio",
"packages/Vibe-Workflow/packages/workflow-builder",

View file

@ -217,7 +217,14 @@ function Dropdown({ isOpen, items, selectedId, onSelect, onClose, anchorRef }) {
: "text-white font-medium"
}`}
>
<div>{item.name}</div>
<div className="flex items-center gap-1.5">
<span>{item.name}</span>
{item.maxDuration && (
<span className="px-1.5 py-0.5 rounded bg-white/5 border border-white/10 text-[10px] font-bold text-white/40 leading-none">
Max {item.maxDuration}s
</span>
)}
</div>
{item.description && (
<div className="text-xs text-muted mt-0.5">
{item.description.slice(0, 60)}...
@ -996,6 +1003,16 @@ export default function LipSyncStudio({
/>
</div>
{/* Duration limit pill */}
{selectedModel?.maxDuration && (
<span className="flex items-center gap-1 px-2 py-1 rounded-md bg-white/[0.03] border border-white/[0.03] text-[10px] font-bold text-white/30 whitespace-nowrap">
<svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>
</svg>
Max {selectedModel.maxDuration}s
</span>
)}
{/* Resolution selector */}
{showResolution && (
<div className="relative">

View file

@ -8166,6 +8166,7 @@ export const lipsyncModels = [
"family": "infinitetalk",
"category": "image",
"hasPrompt": true,
"maxDuration": 60,
"description": "Animate a portrait image into a talking video driven by audio.",
"inputs": {
"resolution": {
@ -8184,6 +8185,7 @@ export const lipsyncModels = [
"family": "wan",
"category": "image",
"hasPrompt": true,
"maxDuration": 60,
"description": "Generate a talking portrait video from an image and audio using Wan 2.2.",
"inputs": {
"resolution": {
@ -8203,6 +8205,7 @@ export const lipsyncModels = [
"category": "image",
"hasPrompt": true,
"hasSeed": true,
"maxDuration": 20,
"description": "High-quality lipsync from portrait image and audio using LTX 2.3.",
"inputs": {
"resolution": {
@ -8221,6 +8224,7 @@ export const lipsyncModels = [
"family": "ltx",
"category": "image",
"hasPrompt": true,
"maxDuration": 20,
"description": "Lipsync from portrait image and audio using LTX 2 19B model.",
"inputs": {
"resolution": {
@ -8240,6 +8244,7 @@ export const lipsyncModels = [
"family": "lipsync",
"category": "video",
"hasPrompt": false,
"maxDuration": 60,
"description": "Generate realistic lipsync animations from audio using Sync's advanced algorithms."
},
{
@ -8249,6 +8254,7 @@ export const lipsyncModels = [
"family": "lipsync",
"category": "video",
"hasPrompt": false,
"maxDuration": 30,
"description": "Video-to-video lipsync using LatentSync for high-quality audio-driven lip animations."
},
{
@ -8258,6 +8264,7 @@ export const lipsyncModels = [
"family": "lipsync",
"category": "video",
"hasPrompt": false,
"maxDuration": 60,
"description": "Realistic lipsync video optimized for speed, quality, and consistency by Creatify."
},
{
@ -8267,6 +8274,7 @@ export const lipsyncModels = [
"family": "lipsync",
"category": "video",
"hasPrompt": false,
"maxDuration": 120,
"description": "Generate realistic lipsync from any audio using VEED's latest model."
},
{
@ -8276,6 +8284,7 @@ export const lipsyncModels = [
"family": "infinitetalk",
"category": "video",
"hasPrompt": true,
"maxDuration": 60,
"description": "Apply audio-driven lipsync to an existing video using Infinite Talk.",
"inputs": {
"resolution": {

View file

@ -303,8 +303,16 @@ export function LipSyncStudio() {
generateBtn.className = 'ml-auto px-6 py-2.5 bg-primary text-black font-black text-sm rounded-2xl hover:scale-105 active:scale-95 transition-all shadow-glow disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100';
generateBtn.textContent = 'Generate ✨';
// Duration limit pill — updates when model changes
const durationPill = document.createElement('span');
durationPill.id = 'ls-duration-pill';
durationPill.className = 'px-2.5 py-1 rounded-lg bg-white/5 border border-white/10 text-xs font-bold text-muted flex items-center gap-1';
const currentModelInitial = getCurrentModel();
durationPill.innerHTML = `<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" class="text-muted"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg><span id="ls-duration-pill-text">Max: ${currentModelInitial?.maxDuration ? currentModelInitial.maxDuration + 's' : '—'}</span>`;
bottomRow.appendChild(modelBtn);
bottomRow.appendChild(resolutionBtn);
bottomRow.appendChild(durationPill);
bottomRow.appendChild(generateBtn);
bar.appendChild(bottomRow);
@ -333,7 +341,10 @@ export function LipSyncStudio() {
const item = document.createElement('button');
item.type = 'button';
item.className = `w-full text-left px-4 py-2.5 rounded-xl text-sm transition-all hover:bg-white/10 ${m.id === selectedModel ? 'text-primary font-bold bg-primary/5' : 'text-white font-medium'}`;
item.innerHTML = `<div>${m.name}</div><div class="text-xs text-muted mt-0.5">${m.description?.slice(0, 60)}...</div>`;
const durationTag = m.maxDuration
? `<span class="ml-1.5 px-1.5 py-0.5 rounded-md bg-white/5 border border-white/10 text-[10px] font-bold text-muted">Max ${m.maxDuration}s</span>`
: '';
item.innerHTML = `<div class="flex items-center gap-1">${m.name}${durationTag}</div><div class="text-xs text-muted mt-0.5">${m.description?.slice(0, 60)}...</div>`;
item.onclick = () => {
selectedModel = m.id;
document.getElementById('ls-model-btn-label').textContent = m.name;
@ -346,6 +357,8 @@ export function LipSyncStudio() {
resolutionBtn.classList.add('hidden');
}
textarea.style.display = m.hasPrompt ? '' : 'none';
const pillText = document.getElementById('ls-duration-pill-text');
if (pillText) pillText.textContent = `Max: ${m.maxDuration ? m.maxDuration + 's' : '—'}`;
closeDropdown();
};
dropdown.appendChild(item);
@ -435,6 +448,10 @@ export function LipSyncStudio() {
// Show/hide prompt
textarea.style.display = models[0].hasPrompt ? '' : 'none';
// Update duration pill
const pillText = document.getElementById('ls-duration-pill-text');
if (pillText) pillText.textContent = `Max: ${models[0].maxDuration ? models[0].maxDuration + 's' : '—'}`;
};
imageModeBtn.onclick = () => {

View file

@ -8166,6 +8166,7 @@ export const lipsyncModels = [
"family": "infinitetalk",
"category": "image",
"hasPrompt": true,
"maxDuration": 60,
"description": "Animate a portrait image into a talking video driven by audio.",
"inputs": {
"resolution": {
@ -8184,6 +8185,7 @@ export const lipsyncModels = [
"family": "wan",
"category": "image",
"hasPrompt": true,
"maxDuration": 60,
"description": "Generate a talking portrait video from an image and audio using Wan 2.2.",
"inputs": {
"resolution": {
@ -8203,6 +8205,7 @@ export const lipsyncModels = [
"category": "image",
"hasPrompt": true,
"hasSeed": true,
"maxDuration": 20,
"description": "High-quality lipsync from portrait image and audio using LTX 2.3.",
"inputs": {
"resolution": {
@ -8221,6 +8224,7 @@ export const lipsyncModels = [
"family": "ltx",
"category": "image",
"hasPrompt": true,
"maxDuration": 20,
"description": "Lipsync from portrait image and audio using LTX 2 19B model.",
"inputs": {
"resolution": {
@ -8240,6 +8244,7 @@ export const lipsyncModels = [
"family": "lipsync",
"category": "video",
"hasPrompt": false,
"maxDuration": 60,
"description": "Generate realistic lipsync animations from audio using Sync's advanced algorithms."
},
{
@ -8249,6 +8254,7 @@ export const lipsyncModels = [
"family": "lipsync",
"category": "video",
"hasPrompt": false,
"maxDuration": 30,
"description": "Video-to-video lipsync using LatentSync for high-quality audio-driven lip animations."
},
{
@ -8258,6 +8264,7 @@ export const lipsyncModels = [
"family": "lipsync",
"category": "video",
"hasPrompt": false,
"maxDuration": 60,
"description": "Realistic lipsync video optimized for speed, quality, and consistency by Creatify."
},
{
@ -8267,6 +8274,7 @@ export const lipsyncModels = [
"family": "lipsync",
"category": "video",
"hasPrompt": false,
"maxDuration": 120,
"description": "Generate realistic lipsync from any audio using VEED's latest model."
},
{
@ -8276,6 +8284,7 @@ export const lipsyncModels = [
"family": "infinitetalk",
"category": "video",
"hasPrompt": true,
"maxDuration": 60,
"description": "Apply audio-driven lipsync to an existing video using Infinite Talk.",
"inputs": {
"resolution": {