1/* Tagshop AI Agent â matches home-agent.tsx (agentic tab), light theme */ 2(function () { 3 'use strict'; 4 5 var PLACEHOLDERS = [ 6 'Create a viral UGC ad for skincare', 7 'Turn my bestseller into a TikTok-style UGC', 8 'Create a sleek driving showcase video', 9 'Turn my product into unboxing UGC', 10 'Create a steamy coffee pour video', 11 'Create a 90s VHS-style nostalgic video for my vintage collection', 12 'Make a 15-second UGC video ad for my product', 13 'Create an energetic reel for my gym brand', 14 ]; 15 16 /* ââ AspectRatio SVG ââ */ 17 function ratioSvgInner(ratio) { 18 if (!ratio) return ''; 19 var parts = ratio.split(':'); 20 var w = parseInt(parts[0]) || 1; 21 var h = parseInt(parts[1]) || 1; 22 var S = 14, rw, rh, x, y; 23 if (w >= h) { rw = S; rh = Math.round(S * h / w); } 24 else { rh = S; rw = Math.round(S * w / h); } 25 x = Math.floor((S - rw) / 2); 26 y = Math.floor((S - rh) / 2); 27 return '<rect x="' + x + '" y="' + y + '" width="' + rw + '" height="' + rh + 28 '" rx="1" stroke="currentColor" stroke-width="1.5" fill="none"/>'; 29 } 30 31 /* ââ Models helpers ââ */ 32 function getModelsData(modelsData, section, type) { 33 if (!modelsData || !section) return []; 34 var src = type === 'video' ? 'Video' : 'Image'; 35 return (modelsData[section] || []).filter(function (m) { return m.source === src; }); 36 } 37 38 function modelGetDefault(list) { 39 return list.find(function (m) { return m.make_default === 1; }); 40 } 41 42 /* ââ Init each shortcode instance ââ */ 43 function initInstance(wrap) { 44 var relayUrl = (window.TagshopAgent && window.TagshopAgent.relayUrl) || wrap.dataset.redirect || 'https://tagshop.ai/wp-content/plugins/tagshop-agent/relay.php'; 45 var ajaxUrl = (window.TagshopAgent && window.TagshopAgent.ajaxUrl) || '/wp-admin/admin-ajax.php'; 46 47 /* DOM refs */ 48 var assetsRow = wrap.querySelector('#ts-assets-row'); 49 var textarea = wrap.querySelector('#ts-textarea'); 50 var placeholder = wrap.querySelector('#ts-placeholder'); 51 var typedSpan = wrap.querySelector('#ts-typed'); 52 var productBtn = wrap.querySelector('#ts-product-btn'); 53 var ratioBtn = wrap.querySelector('#ts-ratio-btn'); 54 var ratioIcon = wrap.querySelector('#ts-ratio-icon'); 55 var ratioLabel = wrap.querySelector('#ts-ratio-label'); 56 var ratioChev = wrap.querySelector('#ts-ratio-chev'); 57 var ratioMenu = wrap.querySelector('#ts-ratio-menu'); 58 var qualityDD = wrap.querySelector('#ts-quality-dd'); 59 var qualityBtn = wrap.querySelector('#ts-quality-btn'); 60 var qualityLabel = wrap.querySelector('#ts-quality-label'); 61 var qualityChev = wrap.querySelector('#ts-quality-chev'); 62 var qualityMenu = wrap.querySelector('#ts-quality-menu'); 63 var durationBtn = wrap.querySelector('#ts-duration-btn'); 64 var durationLabel= wrap.querySelector('#ts-duration-label'); 65 var durationChev = wrap.querySelector('#ts-duration-chev'); 66 var durationMenu = wrap.querySelector('#ts-duration-menu'); 67 var sendBtn = wrap.querySelector('#ts-send-btn'); 68 69 /* ââ State ââ */ 70 var models = {}; 71 var agenticModel = ''; /* picked from API, NOT shown as UI dropdown in agentic tab */ 72 var ratioValue = ''; 73 var qualityValue = ''; 74 var durationValue = ''; 75 var agenticImages = []; 76 var agenticVideos = []; 77 var agenticAudios = []; 78 var textVal = ''; 79 var isGenerating = false; 80 81 /* Clear textarea on every page load (prevent browser form-state restoration) */ 82 textarea.value = ''; 83 textVal = ''; 84 updateSendBtn(); 85 86 /* ââ Typing animation ââ */ 87 var typingTimer, clearTimer, textIdx = 0, charIdx = 0; 88 89 function startTyping() { 90 if (textVal.length > 0) return; 91 placeholder.classList.remove('ts-hide'); 92 typedSpan.textContent = ''; 93 charIdx = 0; 94 var full = PLACEHOLDERS[textIdx]; 95 function typeChar() { 96 if (charIdx < full.length) { 97 typedSpan.textContent = full.slice(0, ++charIdx); 98 typingTimer = setTimeout(typeChar, 50); 99 } else { 100 clearTimer = setTimeout(function () { 101 typedSpan.textContent = ''; 102 textIdx = (textIdx + 1) % PLACEHOLDERS.length; 103 startTyping(); 104 }, 2000); 105 } 106 } 107 typeChar(); 108 } 109 110 function stopTyping() { 111 clearTimeout(typingTimer); 112 clearTimeout(clearTimer); 113 placeholder.classList.add('ts-hide'); 114 } 115 116 textarea.addEventListener('input', function () { 117 textVal = textarea.value; 118 if (textVal.length > 0) stopTyping(); 119 else startTyping(); 120 updateSendBtn(); 121 }); 122 textarea.addEventListener('focus', function () { if (!textVal.length) placeholder.classList.add('ts-hide'); }); 123 textarea.addEventListener('blur', function () { if (!textVal.length) startTyping(); }); 124 125 /* ââ Send button ââ */ 126 function updateSendBtn() { 127 var active = textVal.trim().length > 0 && !isGenerating; 128 sendBtn.disabled = !active; 129 sendBtn.classList.toggle('ts-active', active); 130 } 131 132 /* ââ Dropdown helpers ââ */ 133 function closeAll() { 134 wrap.querySelectorAll('.ts-dd-menu').forEach(function (m) { m.classList.remove('ts-open'); }); 135 wrap.querySelectorAll('.ts-chevron').forEach(function (c) { c.classList.remove('ts-open'); }); 136 var bd = document.querySelector('.ts-backdrop'); 137 if (bd) bd.remove(); 138 } 139 140 function openDD(menu, chev) { 141 closeAll(); 142 menu.classList.add('ts-open'); 143 chev.classList.add('ts-open'); 144 var bd = document.createElement('div'); 145 bd.className = 'ts-backdrop'; 146 bd.addEventListener('click', closeAll); 147 document.body.appendChild(bd); 148 } 149 150 function toggleDD(menu, chev) { 151 if (menu.classList.contains('ts-open')) closeAll(); 152 else openDD(menu, chev); 153 } 154 155 ratioBtn.addEventListener('click', function () { toggleDD(ratioMenu, ratioChev); }); 156 qualityBtn.addEventListener('click', function () { toggleDD(qualityMenu, qualityChev); }); 157 durationBtn.addEventListener('click', function () { toggleDD(durationMenu, durationChev); }); 158 159 /* ââ Model/data helpers (same logic as home-agent.tsx) ââ */ 160 function getModelList() { 161 return getModelsData(models, 'ai_agent', 'video'); 162 } 163 164 function getSelectedModel() { 165 return getModelList().find(function (m) { return m.name === agenticModel; }) || null; 166 } 167
168 function getCreditConfig() { 169 var m = getSelectedModel(); 170 return (m && m.creditConfig) ? m.creditConfig : []; 171 } 172 173 function getRatioOptions() { 174 var m = getSelectedModel(); 175 return m ? (m.allowed_ratio || []) : []; 176 } 177 178 function getDefaultRatio() { 179 var m = getSelectedModel(); 180 return m ? (m.default_ratio || '') : ''; 181 } 182 183 function getDurationOptions() { 184 var cfg = getCreditConfig(); 185 var set = {}; 186 cfg.forEach(function (c) { if (c.duration > 0) set[c.duration] = 1; }); 187 return Object.keys(set).map(Number).sort(function (a, b) { return a - b; }).map(String); 188 } 189 190 function getQualityOptions() { 191 var cfg = getCreditConfig(); 192 var set = {}; 193 cfg.forEach(function (c) { if (c.quality) set[c.quality] = 1; }); 194 return Object.keys(set); 195 } 196 197 function getValidDurationsForQuality(q) { 198 if (!q) return getDurationOptions(); 199 var cfg = getCreditConfig(); 200 var set = {}; 201 cfg.forEach(function (c) { if (c.quality === q && c.duration > 0) set[c.duration] = 1; }); 202 var arr = Object.keys(set).map(Number).sort(function (a, b) { return a - b; }).map(String); 203 return arr.length ? arr : getDurationOptions(); 204 } 205 206 function getValidQualitiesForDuration(dur) { 207 if (!dur) return getQualityOptions(); 208 var dNum = parseInt(dur) || 5; 209 var cfg = getCreditConfig(); 210 var set = {}; 211 cfg.forEach(function (c) { if (c.duration === dNum && c.quality) set[c.quality] = 1; }); 212 var arr = Object.keys(set); 213 return arr.length ? arr : getQualityOptions(); 214 } 215 216 function getModelSlug() { 217 var m = getSelectedModel(); 218 return m ? (m.slug || agenticModel.toLowerCase().replace(/\s+/g, '-')) : ''; 219 } 220 221 /* ââ Apply defaults after model loads ââ */ 222 function applyDefaults() { 223 var ratios = getRatioOptions(); 224 var defRatio = getDefaultRatio(); 225 ratioValue = (defRatio && ratios.indexOf(defRatio) !== -1) ? defRatio : (ratios[0] || ''); 226 227 var durations = getDurationOptions(); 228 durationValue = durations[0] || ''; 229 230 var qualities = getQualityOptions(); 231 qualityValue = qualities[0] || ''; 232 233 ratioLabel.textContent = ratioValue || 'â'; 234 ratioIcon.setAttribute('viewBox', '0 0 14 14'); 235 ratioIcon.innerHTML = ratioSvgInner(ratioValue); 236 durationLabel.textContent = durationValue ? durationValue + 's' : 'â'; 237 qualityLabel.textContent = qualityValue || 'â'; 238 239 renderRatioMenu(); 240 renderQualityMenu(); 241 renderDurationMenu(); 242 } 243 244 /* ââ Render menus ââ */ 245 function renderRatioMenu() { 246 ratioMenu.innerHTML = ''; 247 getRatioOptions().forEach(function (opt) { 248 var btn = document.createElement('button'); 249 btn.type = 'button'; 250 btn.className = 'ts-dd-item' + (opt === ratioValue ? ' ts-sel' : ''); 251 btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 14 14">' + ratioSvgInner(opt) + '</svg>' + opt; 252 btn.addEventListener('click', function () { 253 ratioValue = opt; 254 ratioLabel.textContent = opt; 255 ratioIcon.innerHTML = ratioSvgInner(opt); 256 renderRatioMenu(); 257 closeAll(); 258 }); 259 ratioMenu.appendChild(btn); 260 }); 261 } 262 263 function renderQualityMenu() { 264 qualityMenu.innerHTML = ''; 265 var opts = getValidQualitiesForDuration(durationValue); 266 if (!opts.length) { qualityDD.style.display = 'none'; return; } 267 qualityDD.style.display = ''; 268 opts.forEach(function (opt) { 269 var btn = document.createElement('button'); 270 btn.type = 'button'; 271 btn.className = 'ts-dd-item' + (opt === qualityValue ? ' ts-sel' : ''); 272 btn.textContent = opt; 273 btn.addEventListener('click', function () { 274 qualityValue = opt; 275 qualityLabel.textContent = opt; 276 var validDurs = getValidDurationsForQuality(opt); 277 if (validDurs.length && validDurs.indexOf(durationValue) === -1) { 278 durationValue = validDurs[0]; 279 durationLabel.textContent = durationValue + 's'; 280 } 281 renderQualityMenu(); 282 renderDurationMenu(); 283 closeAll(); 284 }); 285 qualityMenu.appendChild(btn); 286 }); 287 } 288 289 function renderDurationMenu() { 290 durationMenu.innerHTML = ''; 291 var opts = getValidDurationsForQuality(qualityValue); 292 if (!opts.length) opts = getDurationOptions();
293 opts.forEach(function (opt) { 294 var btn = document.createElement('button'); 295 btn.type = 'button'; 296 btn.className = 'ts-dd-item' + (opt === durationValue ? ' ts-sel' : ''); 297 btn.textContent = opt + 's'; 298 btn.addEventListener('click', function () { 299 durationValue = opt; 300 durationLabel.textContent = opt + 's'; 301 var validQuals = getValidQualitiesForDuration(opt); 302 if (validQuals.length && validQuals.indexOf(qualityValue) === -1) { 303 qualityValue = validQuals[0]; 304 qualityLabel.textContent = qualityValue; 305 } 306 renderDurationMenu(); 307 renderQualityMenu(); 308 closeAll(); 309 }); 310 durationMenu.appendChild(btn); 311 }); 312 } 313 314 /* ââ Fetch models via PHP proxy ââ */ 315 fetch(ajaxUrl + '?action=tagshop_models') 316 .then(function (r) { return r.json(); }) 317 .then(function (data) { 318 if (data && data.success) { 319 models = data.data || {}; 320 var list = getModelList(); 321 var def = modelGetDefault(list); 322 agenticModel = (def || list[0] || {}).name || ''; 323 console.log('[TagshopAgent] models loaded, agenticModel:', agenticModel, 'list:', list.length); 324 applyDefaults(); 325 } else { 326 console.warn('[TagshopAgent] models proxy error:', data); 327 } 328 startTyping(); 329 }) 330 .catch(function (err) { 331 console.error('[TagshopAgent] models fetch failed:', err); 332 startTyping(); 333 }); 334 335 /* ââ Product button â same as original: set flag + redirect ââ */ 336 productBtn.addEventListener('click', function () { 337 localStorage.setItem('ts_agentic_flow', 'true'); 338 window.location.href = 'https://tagshop.ai/app'; 339 }); 340 341 /* ââ Assets rendering ââ */ 342 function renderAssets() { 343 assetsRow.innerHTML = ''; 344 var all = agenticImages.concat(agenticVideos, agenticAudios); 345 if (!all.length) return; 346 var counts = { image: 0, video: 0, audio: 0 }; 347 all.forEach(function (asset) { 348 counts[asset.type]++; 349 var displayName = asset.type + counts[asset.type]; 350 var item = document.createElement('div'); 351 item.className = 'ts-asset-item'; 352 var thumb = document.createElement('div'); 353 thumb.className = 'ts-asset-thumb'; 354 var inner = document.createElement('div'); 355 inner.className = 'ts-asset-thumb-inner'; 356 if (asset.type === 'video') { 357 var vid = document.createElement('video'); 358 vid.src = asset.path; vid.preload = 'metadata'; 359 inner.appendChild(vid); 360 } else if (asset.type === 'audio') { 361 inner.innerHTML = '<div class="ts-asset-audio-icon"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9ca3af" stroke-width="2"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg></div>'; 362 } else { 363 var img = document.createElement('img'); 364 img.src = asset.path; img.alt = displayName; 365 img.onerror = function () { img.src = 'https://placehold.co/64x64/eee/999?text=?'; }; 366 inner.appendChild(img); 367 } 368 var removeBtn = document.createElement('button'); 369 removeBtn.className = 'ts-asset-remove'; 370 removeBtn.innerHTML = '<svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>'; 371 ;(function (a) { 372 removeBtn.addEventListener('click', function () { 373 if (a.type === 'image') agenticImages = agenticImages.filter(function (x) { return x !== a; }); 374 else if (a.type === 'video') agenticVideos = agenticVideos.filter(function (x) { return x !== a; }); 375 else agenticAudios = agenticAudios.filter(function (x) { return x !== a; }); 376 renderAssets(); 377 }); 378 })(asset); 379 var label = document.createElement('div'); 380 label.className = 'ts-asset-name'; 381 label.textContent = '@' + displayName; 382 thumb.appendChild(inner); 383 thumb.appendChild(removeBtn); 384 item.appendChild(thumb); 385 item.appendChild(label); 386 assetsRow.appendChild(item); 387 }); 388 } 389 390 /* ââ Generate â exact same flow as home-agent.tsx handleGenerate ââ */ 391 sendBtn.addEventListener('click', function () { 392 if (!textVal.trim() || isGenerating) return; 393 isGenerating = true; 394 updateSendBtn(); 395 var origHTML = sendBtn.innerHTML; 396 sendBtn.innerHTML = '<span class="ts-spinner"></span>'; 397 398 var slug = getModelSlug(); 399 400 var referenceMedia = agenticImages.map(function (img) { 401 return { path: img.path, type: 'image' }; 402 }).concat(agenticAudios.map(function (aud) { 403 return { path: aud.path, type: 'audio', duration: aud.duration }; 404 })); 405 406 /* 407 * Build requestData (same structure Zustand useAgenticRequestStore persists). 408 * relay.php will write this to localStorage[agentic-request-storage]. 409 */ 410 var requestData = { 411 prompt: textVal, 412 ratio: ratioValue, 413 model: slug, 414 duration: String(durationValue || '5'), 415 product_title: '', 416 product_description: '', 417 sourceUrl: (agenticImages[0] || agenticVideos[0] || {}).path || '', 418 }; 419 if (qualityValue) requestData.quality = qualityValue; 420 if (referenceMedia.length) requestData.reference_media = referenceMedia; 421 422 /* 423 * Build the store/product payload (same as home-agent.tsx line 768-807). 424 * relay.php will call /api/store/product same-origin so browser sends cookies automatically. 425 */ 426 var storePayload = { 427 prompt: textVal, 428 model: slug, 429 ratio: ratioValue, 430 duration: String(durationValue || '5'), 431 sourceUrl: (agenticImages[0] || agenticVideos[0] || {}).path || '', 432 }; 433 if (qualityValue) storePayload.quality = qualityValue; 434 if (referenceMedia.length) storePayload.reference_media = referenceMedia; 435 436 /* base64-encode both blobs so they survive as URL params */ 437 var encoded = btoa(unescape(encodeURIComponent(JSON.stringify(requestData)))); 438 var encodedPayload = btoa(unescape(encodeURIComponent(JSON.stringify(storePayload)))); 439 440 /* 441 * Redirect to relay.php (served from tagshop.ai). 442 * relay.php JS will: 443 * 1. Write agentic-request-storage to localStorage (same origin as tagshop.ai/app) 444 * 2. Call /api/store/product same-origin (cookies auto-sent) â get product_key 445 * 3. Write product_key to localStorage 446 * 4. Redirect to /app 447 * This mirrors home-agent.tsx handleGenerate() exactly. 448 */ 449 var dest = relayUrl
450 + '?ts_data=' + encodeURIComponent(encoded) 451 + '&ts_payload=' + encodeURIComponent(encodedPayload); 452 window.location.href = dest; 453 }); 454 } 455 456 /* ââ Boot ââ */ 457 function boot() { 458 document.querySelectorAll('.ts-agent-wrap').forEach(initInstance); 459 } 460 461 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot); 462 else boot(); 463})();
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.