1/** 2 * Standalone VidGen2 composer â completely separate from TrainingVideoMaker / 3 * tv-maker-tools.js state. Own fields, own IDs, own submit. Only wiring it 4 * shares with the rest of the page is being shown/hidden by tv-maker-tools.js 5 * when the "AI Video" tab is active (#vg2Composer visibility only). 6 */ 7(function ($) { 8 'use strict'; 9 10 var uploadedMedia = []; // [{file, url, type, uploading, previewSrc}] 11 var selectedCharacters = []; // [{avatar_id, name, image_url, voice_id, uploading, previewSrc}] 12 var isPickingCharacter = false; 13 var characterPickerSource = null; 14 var aspectRatio = '9:16'; 15 var activeTemplate = null; 16 var AI_VOICE_FALLBACK_ID = 'ai_voice'; 17 var CHARACTER_FALLBACK_IMAGE = '/images/v-thumbnail.webp'; 18 var inputSelectorByType = { 19 image: '#vg2ImageInput' 20 }; 21 22 function csrfToken() { 23 return $('meta[name="csrf-token"]').attr('content') || ''; 24 } 25 26 function showError(message) { 27 var $el = $('#vg2ComposerError'); 28 var $modalEl = $('#vg2CtaComposerError'); 29 if (message) { 30 $el.text(message).show(); 31 $modalEl.text(message).show(); 32 } else { 33 $el.hide(); 34 $modalEl.hide(); 35 } 36 } 37 38 /** 39 * INSUFFICIENT_CREDITS carries required/available/is_premium/has_topped_up/ 40 * has_payment_method/auto_topup_enabled (see VidGen2Controller:: 41 * insufficientCreditsResponse()) so the CTA can point at top-up for 42 * anyone who's already paid for credits before, offer auto-topup when a 43 * card is already on file, or send a free user to upgrade â same 44 * distinction the result page's in-chat credit gate makes. 45 */ 46 function showInsufficientCreditsError(resp) { 47 var required = (resp && resp.needed) || 0; 48 var available = (resp && resp.balance) || 0; 49 var hasPaymentMethod = !!(resp && resp.has_payment_method); 50 var autoTopupEnabled = !!(resp && resp.auto_topup_enabled); 51 var offerAutoTopup = hasPaymentMethod && !autoTopupEnabled; 52 53 var html = 'You need ' + required + ' credits for this (you have ' + available + '). '; 54 if (offerAutoTopup) { 55 html += '<button type="button" class="vg2-error-cta" data-vg2-enable-auto-topup="' + required + '">Set up auto top-up</button> ' + 56 '<button type="button" class="vg2-error-cta" data-vg2-buy-credits="' + required + '">Buy credits once</button>'; 57 } else if (autoTopupEnabled) { 58 html += 'The auto top-up charge on your card didn\'t go through â check your payment method, or ' + 59 '<button type="button" class="vg2-error-cta" data-vg2-buy-credits="' + required + '">buy credits manually</button>.'; 60 } else { 61 html += '<button type="button" class="vg2-error-cta" data-vg2-buy-credits="' + required + '">Buy credits</button>'; 62 } 63 64 $('#vg2ComposerError').html(html).show(); 65 $('#vg2CtaComposerError').html(html).show(); 66 } 67 68 function enableAutoTopup($btn) { 69 if (typeof window.openCreditTopupModal !== 'function') { 70 window.location.href = '/billing/details'; 71 return; 72 } 73 74 var requiredCredits = parseInt($btn.attr('data-vg2-enable-auto-topup') || '0', 10) || undefined; 75 window.openCreditTopupModal({ 76 defaultCredits: requiredCredits, 77 onAutoTopupSuccess: function () { 78 var successHtml = 'Auto top-up settings saved â try creating your video again.'; 79 $('#vg2ComposerError').html(successHtml).show(); 80 $('#vg2CtaComposerError').html(successHtml).show(); 81 } 82 }); 83 } 84 85 function renderMediaPreviews() { 86 var $list = $('#vg2MediaPreviewList'); 87 $list.empty(); 88 var $modalList = $('#vg2CtaMediaPreviewList'); 89 $modalList.empty(); 90 91 uploadedMedia.forEach(function (item, idx) { 92 var $item = $('<div class="vg2-media-preview-item"></div>').attr('data-media-type', item.type || 'image'); 93 if (item.uploading) $item.addClass('is-uploading'); 94 95 $item.append($('<img>').attr('src', item.previewSrc || item.url || '')); 96 97 $item.append($('<span class="vg2-media-preview-type"></span>').text(item.type || 'image')); 98 99 var $remove = $('<button type="button" class="vg2-media-preview-remove" aria-label="Remove media">×</button>'); 100 $remove.on('click', function () { 101 uploadedMedia.splice(idx, 1); 102 renderMediaPreviews(); 103 }); 104 105 $item.append($remove); 106 $list.append($item); 107 $modalList.append($item.clone(true, true)); 108 }); 109 110 $('#vg2MediaPreviewContainer').toggle(uploadedMedia.length > 0); 111 $('#vg2CtaMediaPreviewList').toggle(uploadedMedia.length > 0); 112 } 113 114 function detectMediaType(file) { 115 if (file && file.type && file.type.indexOf('image/') === 0) return 'image'; 116 return 'image'; 117 } 118 119 function uploadMedia(file) { 120 if (file && file.type && file.type.indexOf('image/') !== 0) { 121 showError('Only reference images are supported for now.'); 122 return; 123 } 124 125 var item = { 126 file: file, 127 url: null, 128 type: detectMediaType(file), 129 uploading: true, 130 previewSrc: URL.createObjectURL(file) 131 }; 132 uploadedMedia.push(item); 133 renderMediaPreviews(); 134 135 var formData = new FormData(); 136 formData.append('media_file', file); 137 138 $.ajax({ 139 url: '/ai-ads-generator/presentations/upload-image', 140 type: 'POST', 141 data: formData, 142 processData: false, 143 contentType: false, 144 headers: { 'X-CSRF-TOKEN': csrfToken() } 145 }).done(function (resp) { 146 item.uploading = false;
147 if (resp && resp.status === 'success' && resp.media_url) { 148 item.type = resp.media_type || item.type; 149 item.url = resp.media_url; 150 } else { 151 var idx = uploadedMedia.indexOf(item); 152 if (idx !== -1) uploadedMedia.splice(idx, 1); 153 showError('Failed to upload one of the reference files.'); 154 } 155 renderMediaPreviews(); 156 }).fail(function () { 157 var idx = uploadedMedia.indexOf(item); 158 if (idx !== -1) uploadedMedia.splice(idx, 1); 159 showError('Failed to upload one of the reference files.'); 160 renderMediaPreviews(); 161 }); 162 } 163 164 function characterImage(selection) { 165 return String((selection && (selection.image_url || selection.image || selection.gif || selection.avatar_url || selection.video)) || '').trim(); 166 } 167 168 function characterVoiceId(selection) { 169 return String((selection && (selection.voice_id || selection.cloned_voice_id)) || AI_VOICE_FALLBACK_ID).trim(); 170 } 171 172 function characterId(selection) { 173 return String((selection && (selection.avatar_id || selection.id || selection.look_id || selection.image_url || selection.image || selection.gif)) || '').trim(); 174 } 175 176 /** 177 * Selected characters render as the same small square thumbnail + 178 * corner remove button as uploaded reference images (vg2-media-preview- 179 * item), in their own preview strip above the option buttons â not as 180 * wide pill chips sitting inline in the button row, which read like an 181 * extra tab appearing next to "Character". 182 */ 183 function renderCharacters() { 184 var $list = $('#vg2CharacterList'); 185 var $container = $('#vg2CharacterPreviewContainer'); 186 var $modalList = $('#vg2CtaCharacterList'); 187 var $label = $('#vg2CharacterSelectLabel'); 188 var $modalLabel = $('#vg2CtaCharacterSelectLabel'); 189 $list.empty(); 190 $modalList.empty(); 191 192 selectedCharacters.forEach(function (character, idx) { 193 var image = character.previewSrc || character.image_url || CHARACTER_FALLBACK_IMAGE; 194 var name = character.name || 'Character'; 195 var $item = $('<div class="vg2-media-preview-item vg2-character-preview-item"></div>'); 196 if (character.uploading) $item.addClass('is-uploading'); 197 $item.append($('<img>').attr({ src: image, alt: name }).on('error', function () { 198 $(this).off('error').attr('src', CHARACTER_FALLBACK_IMAGE); 199 })); 200 $item.append($('<span class="vg2-media-preview-type"></span>').text(name).attr('title', name)); 201 $item.append($('<button type="button" class="vg2-media-preview-remove" aria-label="Remove character">×</button>').on('click', function () { 202 selectedCharacters.splice(idx, 1); 203 renderCharacters(); 204 })); 205 $list.append($item); 206 $modalList.append($item.clone(true, true)); 207 }); 208 209 if ($container.length) { 210 $container.toggle(selectedCharacters.length > 0); 211 } else { 212 $list.toggle(selectedCharacters.length > 0); 213 } 214 $modalList.toggle(selectedCharacters.length > 0); 215 $label.text(selectedCharacters.length ? 'Add Character' : 'Character'); 216 $modalLabel.text(selectedCharacters.length ? 'Add Character' : (String(activeTemplate || '').trim() === 'ugc_ads' ? 'Character Required' : 'Character')); 217 markSelectedAvatarCards(); 218 } 219 220 function selectedCharacterIds() { 221 return selectedCharacters.map(function (character) { 222 return String(character.avatar_id || '').trim(); 223 }).filter(Boolean); 224 } 225 226 function isCharacterRequiredForTemplateCreate(options) { 227 options = options || {}; 228 return String(options.buttonSelector || '').trim() === '#vg2CtaCreateBtn' 229 && String(activeTemplate || '').trim() === 'ugc_ads'; 230 } 231 232 function markSelectedAvatarCards() { 233 var ids = selectedCharacterIds(); 234 $('#wavelAvatarTooltipGrid .tv-avatar-flat-card, #wavelAvatarTooltipGrid .tv-avatar-group-card, #wavelAvatarTooltipGrid .tv-avatar-look-card').each(function () { 235 var $card = $(this); 236 var avatarId = String($card.attr('data-avatar-id') || $card.attr('data-vg2-character-id') || '').trim(); 237 $card.toggleClass('selected', avatarId !== '' && ids.indexOf(avatarId) !== -1); 238 }); 239 } 240
241 window.__vg2IsCharacterSelected = function (avatarId) { 242 avatarId = String(avatarId || '').trim(); 243 return avatarId !== '' && selectedCharacterIds().indexOf(avatarId) !== -1; 244 }; 245 246 function addCharacter(selection) { 247 var id = characterId(selection); 248 var imageUrl = characterImage(selection); 249 var voiceId = characterVoiceId(selection); 250 var name = String((selection && selection.name) || 'Character').trim() || 'Character'; 251 252 if (!id || !imageUrl) { 253 showError('This character is missing an avatar image.'); 254 return; 255 } 256 257 var exists = selectedCharacters.some(function (item) { 258 return item.avatar_id === id; 259 }); 260 if (!exists) { 261 selectedCharacters.push({ 262 avatar_id: id, 263 name: name, 264 image_url: imageUrl, 265 voice_id: voiceId 266 }); 267 } 268 269 renderCharacters(); 270 markSelectedAvatarCards(); 271 showError(null); 272 closeCharacterPickerAfterSelection(); 273 } 274 275 function closeCharacterPickerAfterSelection() { 276 var source = characterPickerSource; 277 278 isPickingCharacter = false; 279 characterPickerSource = null; 280 window.__vg2CharacterPicking = false; 281 window.__vg2KeepCharacterPickerOpen = false; 282 window.__vg2ForceCloseCharacterPicker = true; 283 284 if (window.TrainingVideoMaker && typeof window.TrainingVideoMaker.hideWavelAvatarTooltip === 'function') { 285 window.TrainingVideoMaker.hideWavelAvatarTooltip(); 286 } 287 288 window.__vg2ForceCloseCharacterPicker = false; 289 290 window.setTimeout(function () { 291 var selector = source === 'cta' ? '#vg2CtaCharacterSelectBtn' : '#vg2CharacterSelectBtn'; 292 var $target = $(selector); 293 if ($target.length) { 294 $target.trigger('focus'); 295 } 296 }, 0); 297 } 298 299 function uploadCharacterImage(file) { 300 if (file && file.type && file.type.indexOf('image/') !== 0) { 301 showError('Only character images are supported.'); 302 return; 303 } 304 305 var item = { 306 avatar_id: 'upload_' + Date.now() + '_' + Math.random().toString(36).slice(2), 307 name: file && file.name ? file.name.replace(/\.[^.]+$/, '') : 'Uploaded character', 308 image_url: '', 309 voice_id: AI_VOICE_FALLBACK_ID, 310 uploading: true, 311 previewSrc: URL.createObjectURL(file) 312 }; 313 selectedCharacters.push(item); 314 renderCharacters(); 315 316 var formData = new FormData(); 317 formData.append('media_file', file); 318 319 $.ajax({ 320 url: '/ai-ads-generator/presentations/upload-image', 321 type: 'POST', 322 data: formData, 323 processData: false, 324 contentType: false, 325 headers: { 'X-CSRF-TOKEN': csrfToken() } 326 }).done(function (resp) { 327 item.uploading = false; 328 if (resp && resp.status === 'success' && resp.media_url) { 329 item.image_url = resp.media_url; 330 } else { 331 var idx = selectedCharacters.indexOf(item); 332 if (idx !== -1) selectedCharacters.splice(idx, 1); 333 showError('Failed to upload one of the character images.'); 334 } 335 renderCharacters(); 336 }).fail(function () { 337 var idx = selectedCharacters.indexOf(item); 338 if (idx !== -1) selectedCharacters.splice(idx, 1); 339 showError('Failed to upload one of the character images.'); 340 renderCharacters(); 341 }); 342 } 343 344 function patchAvatarSelectionForCharacters() { 345 if (!window.TrainingVideoMaker || typeof window.TrainingVideoMaker.selectWavelAvatar !== 'function') { 346 window.setTimeout(patchAvatarSelectionForCharacters, 100); 347 return; 348 } 349 350 if (window.TrainingVideoMaker.__vg2CharacterPatchApplied) { 351 return; 352 } 353 354 var originalSelect = window.TrainingVideoMaker.selectWavelAvatar; 355 window.TrainingVideoMaker.selectWavelAvatar = function (selection) { 356 if (isPickingCharacter) { 357 addCharacter(selection); 358 return; 359 } 360 361 return originalSelect.apply(this, arguments); 362 }; 363 window.TrainingVideoMaker.__vg2CharacterPatchApplied = true; 364 } 365 366 function setCreateButtonState($btn, loading) { 367 $btn.prop('disabled', loading); 368 if (loading) { 369 $btn.text('Creating videoâ¦'); 370 return; 371 } 372 $btn.text($btn.data('default-label') || 'Create Video'); 373 } 374 375 function canCreateWithoutAuthPrompt() { 376 return window.wavelIsGuest === false || 377 window.trainingVideoMakerCanCreateScenes === true || 378 String($('#generateScenesBtn').data('can-create-scenes')) === '1'; 379 } 380 381 function promptLoginThenResumeCreate(options) { 382 options = options || {}; 383 var resumeOptions = { 384 promptSelector: options.promptSelector || '#vg2PromptInput', 385 buttonSelector: options.buttonSelector || '#vg2CreateBtn' 386 }; 387 388 window.wavelPendingPostAuthAction = function () { 389 if (resumeOptions.buttonSelector === '#vg2CtaCreateBtn') { 390 $('#vg2PromptInput').val($('#vg2CtaPromptInput').val()).trigger('input'); 391 } 392 handleCreate(resumeOptions); 393 }; 394 395 if (typeof window.openWavelAuthModal === 'function') { 396 window.openWavelAuthModal('login'); 397 } else { 398 window.location.href = '/ai-video?auth=login'; 399 } 400 } 401 402 function handleCreate(options) { 403 options = options || {}; 404 var $btn = $(options.buttonSelector || '#vg2CreateBtn'); 405 var $promptInput = $(options.promptSelector || '#vg2PromptInput'); 406 var prompt = String($promptInput.val() || '').trim(); 407 408 if (!prompt) { 409 showError('Please describe the video you want to create.'); 410 $promptInput.trigger('focus'); 411 return; 412 } 413 414 if (uploadedMedia.some(function (i) { return i.uploading; })) { 415 showError('Please wait for uploads to finish.'); 416 return; 417 } 418 419 if (selectedCharacters.some(function (i) { return i.uploading; })) { 420 showError('Please wait for character uploads to finish.'); 421 return; 422 } 423 424 if (isCharacterRequiredForTemplateCreate(options) && selectedCharacters.length === 0) { 425 showError('Please select or upload a character before creating this UGC ad.'); 426 $('#vg2CtaCharacterSelectBtn').trigger('focus'); 427 return; 428 } 429 430 showError(null); 431 432 if (!canCreateWithoutAuthPrompt()) { 433 promptLoginThenResumeCreate(options); 434 return; 435 } 436 437 var inputImages = uploadedMedia.filter(function (i) { return i.type === 'image'; }).map(function (i) { return i.url; }
437).filter(Boolean); 438 var avatarPairings = selectedCharacters.map(function (character) { 439 return { 440 image_url: character.image_url, 441 voice_id: character.voice_id || AI_VOICE_FALLBACK_ID 442 }; 443 }).filter(function (pairing) { 444 return pairing.image_url; 445 }); 446 447 var payload = { 448 user_prompt: prompt, 449 aspect_ratio: aspectRatio 450 }; 451 if (String(options.buttonSelector || '').trim() === '#vg2CtaCreateBtn' && activeTemplate) { 452 payload.template = String(activeTemplate).trim(); 453 } 454 if (inputImages.length) payload.input_images = inputImages; 455 if (avatarPairings.length) payload.avatar_pairings = avatarPairings; 456 457 setCreateButtonState($btn, true); 458 459 $.ajax({ 460 url: '/ai-ads-generator/vidgen2/create', 461 method: 'POST', 462 contentType: 'application/json', 463 dataType: 'json', 464 headers: { 'X-CSRF-TOKEN': csrfToken() }, 465 data: JSON.stringify(payload) 466 }).done(function (resp) { 467 if (resp && resp.status === 'SUCCESS' && resp.clip_id) { 468 window.location.href = '/ai-ads-generator/vidgen2/result/' + encodeURIComponent(resp.clip_id); 469 return; 470 } 471 if (resp && resp.status === 'INSUFFICIENT_CREDITS') { 472 showInsufficientCreditsError(resp); 473 setCreateButtonState($btn, false); 474 return; 475 } 476 showError((resp && resp.message) || 'Failed to create video.'); 477 setCreateButtonState($btn, false); 478 }).fail(function (xhr) { 479 var body = xhr && xhr.responseJSON; 480 if (xhr && xhr.status === 401) { 481 setCreateButtonState($btn, false); 482 promptLoginThenResumeCreate(options); 483 return; 484 } 485 if (body && body.status === 'INSUFFICIENT_CREDITS') { 486 showInsufficientCreditsError(body); 487 setCreateButtonState($btn, false); 488 return; 489 } 490 showError((body && body.message) || 'Failed to create video.'); 491 setCreateButtonState($btn, false); 492 }); 493 } 494 495 function syncAspectControls() { 496 $('#vg2AspectRatioGroup .vg2-aspect-toggle').each(function () { 497 $(this).toggleClass('is-active', String($(this).data('value') || '') === aspectRatio); 498 }); 499 $('#vg2CtaAspectDropdownLabel').text(aspectRatio); 500 $('#vg2CtaAspectDropdownMenu .vg2-template-dropdown-item').each(function () { 501 var active = String($(this).data('value') || '') === aspectRatio; 502 $(this).toggleClass('is-active', active).attr('aria-selected', active ? 'true' : 'false'); 503 }); 504 var isHorizontal = aspectRatio === '16:9'; 505 $('#vg2CtaAspectDropdownBtn .vg2-template-dropdown-icon').html( 506 isHorizontal 507 ? '<svg viewBox="0 0 24 24" fill="none"><rect x="3.5" y="6.5" width="17" height="11" rx="2" /></svg>' 508 : '<svg viewBox="0 0 24 24" fill="none"><rect x="7.5" y="3.5" width="9" height="17" rx="2" /></svg>' 509 ); 510 } 511 512 function openTemplateModal($card) { 513 var title = String($card.attr('data-vg2-title') || $card.find('span').first().text() || 'AI Video').trim(); 514 var accent = String($card.attr('data-vg2-accent') || '').trim(); 515 var subtitle = String($card.attr('data-vg2-subtitle') || '').trim(); 516 var prompt = String($card.attr('data-vg2-prompt') || '').trim(); 517 var preview = String($card.attr('data-vg2-visual') || $card.attr('data-vg2-preview') || $card.find('img').attr('src') || '').trim(); 518 var attachmentLabel = String($card.attr('data-vg2-attachment-label') || 'Add image(s)').trim(); 519 var detailsLabel = String($card.attr('data-vg2-details-label') || 'What should this video include?').trim(); 520 var detailsPlaceholder = String($card.attr('data-vg2-details-placeholder') || 'Describe the video you want to create.').trim(); 521 var buttonLabel = String($card.attr('data-vg2-button-label') || 'Create Video').trim(); 522 523 activeTemplate = String($card.attr('data-vg2-template') || '').trim(); 524 if (accent && title.indexOf(accent) !== -1) { 525 $('#vg2TemplateModalTitle').html(title.replace(accent, '<span>' + accent + '</span>')); 526 } else { 527 $('#vg2TemplateModalTitle').text(title); 528 } 529 $('#vg2TemplateSubtitle').text(subtitle); 530 $('#vg2TemplatePreviewImage').attr({ src: preview, alt: title }); 531 $('#vg2TemplateAttachmentLabel').text(attachmentLabel); 532 $('#vg2TemplateUploadTitle').text(attachmentLabel); 533 $('#vg2TemplateDetailsLabel').text(detailsLabel); 534 $('#vg2CtaPromptInput').val(prompt).trigger('input'); 535 $('#vg2CtaPromptInput').attr('placeholder', detailsPlaceholder); 536 $('#vg2CtaCreateBtn').data('default-label', buttonLabel).text(buttonLabel); 537 $('.vg2-template-optional').text(activeTemplate === 'ugc_ads' ? 'Required' : 'Optional'); 538 $('.tv-vidgen2-template-card').removeClass('is-selected'); 539 $card.addClass('is-selected'); 540 syncAspectControls(); 541 renderMediaPreviews(); 542 renderCharacters(); 543 showError(null); 544 $('#vg2TemplateModal').attr('aria-hidden', 'false').css('display', 'flex').hide().fadeIn(120); 545 $('body').addClass('vg2-template-modal-open'); 546 window.setTimeout(function () { 547 $('#vg2CtaPromptInput').trigger('focus'); 548 }, 130); 549 } 550 551 function closeTemplateModal() { 552 $('#vg2TemplateModal').attr('aria-hidden', 'true').fadeOut(100); 553 $('body').removeClass('vg2-template-modal-open'); 554 isPickingCharacter = false;
555 characterPickerSource = null; 556 window.__vg2ForceCloseCharacterPicker = true; 557 window.__vg2CharacterPicking = false; 558 window.__vg2KeepCharacterPickerOpen = false; 559 if (window.TrainingVideoMaker && typeof window.TrainingVideoMaker.hideWavelAvatarTooltip === 'function') { 560 window.TrainingVideoMaker.hideWavelAvatarTooltip(); 561 } 562 } 563 564 function init() { 565 // Relocate the panel to be a guaranteed sibling of the tab bar itself, 566 // regardless of whatever conditional wrapper it was statically nested 567 // under in the blade template â same pattern this codebase already uses 568 // for floating menus (see tv-maker-tools.js appendTo('body')). Without 569 // this, the panel can end up inside an ancestor that's hidden/toggled 570 // independently of tv-maker-tools.js's #vg2Composer visibility toggle, 571 // which is why the tab appeared to do nothing. 572 var $panel = $('#vg2Composer'); 573 var $tabs = $('.tvai-tool-tabs'); 574 if ($panel.length && $tabs.length) { 575 $panel.insertAfter($tabs); 576 } 577 578 patchAvatarSelectionForCharacters(); 579 580 $(document).on('wavel-avatar-tooltip:closed', function () { 581 isPickingCharacter = false; 582 characterPickerSource = null; 583 window.__vg2CharacterPicking = false; 584 window.__vg2KeepCharacterPickerOpen = false; 585 }); 586 587 $(document).on('click', '.vg2-media-upload-control', function (e) { 588 if ($(e.target).is('input[type="file"]')) { 589 return; 590 } 591 e.preventDefault(); 592 e.stopPropagation(); 593 var type = $(this).data('vg2-upload') || 'image'; 594 if ($(this).closest('#vg2TemplateModal').length) { 595 $('#vg2TemplateImageInput').trigger('click'); 596 return; 597 } 598 $(inputSelectorByType[type] || '#vg2ImageInput').trigger('click'); 599 }); 600 601 $(document).on('change', '#vg2ImageInput, #vg2TemplateImageInput', function () { 602 var files = Array.prototype.slice.call(this.files || []); 603 files.forEach(function (file) { 604 uploadMedia(file); 605 }); 606 this.value = ''; 607 }); 608 609 $(document).on('click', '#vg2CtaCharacterUploadBtn', function (e) { 610 e.preventDefault(); 611 e.stopPropagation(); 612 $('#vg2CtaCharacterImageInput').trigger('click'); 613 }); 614 615 $(document).on('change', '#vg2CtaCharacterImageInput', function () { 616 var files = Array.prototype.slice.call(this.files || []); 617 files.forEach(function (file) { 618 uploadCharacterImage(file); 619 }); 620 this.value = ''; 621 }); 622 623 $(document).on('dragenter dragover', '.vg2-template-upload-card', function (e) { 624 e.preventDefault(); 625 e.stopPropagation(); 626 $(this).addClass('is-dragover'); 627 }); 628 629 $(document).on('dragleave dragend drop', '.vg2-template-upload-card', function (e) { 630 e.preventDefault(); 631 e.stopPropagation(); 632 $(this).removeClass('is-dragover'); 633 }); 634 635 $(document).on('drop', '.vg2-template-upload-card', function (e) { 636 var files = Array.prototype.slice.call((e.originalEvent && e.originalEvent.dataTransfer && e.originalEvent.dataTransfer.files) || []); 637 files.forEach(function (file) { 638 uploadMedia(file); 639 }); 640 }); 641 642 $(document).on('click', '#vg2CharacterSelectBtn, #vg2CtaCharacterSelectBtn', function (e) { 643 e.preventDefault(); 644 e.stopPropagation(); 645 if (!window.TrainingVideoMaker || typeof window.TrainingVideoMaker.showWavelAvatarTooltip !== 'function') { 646 showError('Character picker is not ready yet.'); 647 return; 648 } 649 isPickingCharacter = true; 650 characterPickerSource = this.id === 'vg2CtaCharacterSelectBtn' ? 'cta' : 'composer'; 651 window.__vg2CharacterPicking = true; 652 window.__vg2KeepCharacterPickerOpen = true; 653 $('#wavel-avatar-tooltip .tip-header-label').text('Select Character'); 654 window.TrainingVideoMaker.showWavelAvatarTooltip('#' + this.id); 655 }); 656 657 $(document).on('click', '#tvAvatarSelectBtn, #upload-avatar-btn', function () { 658 isPickingCharacter = false;
659 characterPickerSource = null; 660 window.__vg2CharacterPicking = false; 661 window.__vg2KeepCharacterPickerOpen = false; 662 }); 663 664 $(document).on('click', '#vg2AspectRatioGroup .vg2-aspect-toggle', function () { 665 aspectRatio = $(this).data('value') || '9:16'; 666 syncAspectControls(); 667 }); 668 669 $(document).on('click', '#vg2CtaAspectDropdownBtn', function (e) { 670 e.preventDefault(); 671 e.stopPropagation(); 672 var $menu = $('#vg2CtaAspectDropdownMenu'); 673 var willOpen = !$menu.is(':visible'); 674 $('.vg2-template-dropdown-menu').hide(); 675 $menu.toggle(willOpen); 676 $(this).attr('aria-expanded', willOpen ? 'true' : 'false'); 677 }); 678 679 $(document).on('click', '#vg2CtaAspectDropdownMenu .vg2-template-dropdown-item', function (e) { 680 e.preventDefault(); 681 e.stopPropagation(); 682 aspectRatio = $(this).data('value') || '9:16'; 683 $('#vg2CtaAspectDropdownMenu').hide(); 684 $('#vg2CtaAspectDropdownBtn').attr('aria-expanded', 'false'); 685 syncAspectControls(); 686 }); 687 688 $(document).on('click', function (e) { 689 if (!$(e.target).closest('#vg2CtaAspectDropdown').length) { 690 $('#vg2CtaAspectDropdownMenu').hide(); 691 $('#vg2CtaAspectDropdownBtn').attr('aria-expanded', 'false'); 692 } 693 }); 694 695 $(document).on('input', '#vg2PromptInput', function () { 696 $('#vg2CharCount').text(String(this.value || '').length); 697 }); 698 699 $(document).on('input', '#vg2CtaPromptInput', function () { 700 $('#vg2CtaCharCount').text(String(this.value || '').length); 701 }); 702 703 $(document).on('click', '.tv-vidgen2-template-card', function () { 704 openTemplateModal($(this)); 705 }); 706 707 // Poster image by default, hover swaps to the matching preview clip 708 // (muted/looping, no controls) â mouseenter/mouseleave are already 709 // non-bubbling in jQuery so this is safe to delegate directly. 710 $(document).on('mouseenter', '.tv-vidgen2-template-card', function () { 711 var video = this.querySelector('.tv-vidgen2-template-card-video'); 712 if (!video) return; 713 try { video.currentTime = 0; } catch (e) {} 714 var playPromise = video.play(); 715 if (playPromise && typeof playPromise.catch === 'function') { 716 playPromise.catch(function () {}); 717 } 718 }); 719 720 $(document).on('mouseleave', '.tv-vidgen2-template-card', function () { 721 var video = this.querySelector('.tv-vidgen2-template-card-video'); 722 if (video) video.pause(); 723 }); 724 725 $(document).on('click', '#vg2TemplateModalClose', closeTemplateModal); 726 $(document).on('click', '#vg2TemplateModal', function (e) { 727 if (e.target === this) closeTemplateModal(); 728 }); 729 $(document).on('keydown', function (e) { 730 if (e.key === 'Escape' && $('#vg2TemplateModal').is(':visible')) { 731 closeTemplateModal(); 732 } 733 }); 734 735 $(document).on('click', '[data-vg2-enable-auto-topup]', function () { 736 enableAutoTopup($(this)); 737 }); 738 739 $(document).on('click', '[data-vg2-buy-credits]', function () { 740 var requiredCredits = parseInt($(this).attr('data-vg2-buy-credits'), 10) || undefined; 741 742 if (typeof window.openCreditTopupModal !== 'function') { 743 window.location.href = '/billing/recharge'; 744 return; 745 } 746 747 window.openCreditTopupModal({ 748 defaultCredits: requiredCredits, 749 onSuccess: function () { 750 var successHtml = 'Credits added â try creating your video again.'; 751 $('#vg2ComposerError').html(successHtml).show(); 752 $('#vg2CtaComposerError').html(successHtml).show(); 753 } 754 }); 755 }); 756 757 $(document).on('click', '#vg2CreateBtn', function () { 758 handleCreate(); 759 }); 760 $(document).on('click', '#vg2CtaCreateBtn', function () { 761 $('#vg2PromptInput').val($('#vg2CtaPromptInput').val()).trigger('input'); 762 handleCreate({ promptSelector: '#vg2CtaPromptInput', buttonSelector: '#vg2CtaCreateBtn' }); 763 }); 764 765 syncAspectControls(); 766 } 767 768 if (document.readyState === 'loading') { 769 $(document).ready(init); 770 } else { 771 init(); 772 } 773})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.