PageSourceSearch

https://tagshop.ai/blog/wp-content/plugins/tagshop-agent/tagshop-agent.js?ver=1.6.0

js tagshop.ai collected 2026-10-02 03:33:50 UTC 18,420 bytes, 463 lines download raw bytes

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.