1(function () { 2 var ASSISTANT_SESSION_MESSAGE_LIMIT = 60; 3 var ASSISTANT_REQUEST_HISTORY_LIMIT = 30; 4 var ASSISTANT_HISTORY_TEXT_LIMIT = 1500; 5 6 function ready(fn) { 7 if (document.readyState !== 'loading') { 8 fn(); 9 } else { 10 document.addEventListener('DOMContentLoaded', fn); 11 } 12 } 13 14 function escapeHtml(value) { 15 return String(value || '').replace(/[&<>"']/g, function (char) { 16 return { 17 '&': '&', 18 '<': '<', 19 '>': '>', 20 '"': '"', 21 "'": ''' 22 }[char]; 23 }); 24 } 25 26 function money(value) { 27 var number = parseFloat(value); 28 if (isNaN(number)) { 29 return ''; 30 } 31 return '$' + number.toFixed(2); 32 } 33 34 function renderReply(text, options) { 35 var clean = formatReplyText(String(text || '').trim(), options || {}); 36 if (!clean) { 37 return ''; 38 } 39 40 return clean.split(/\n{2,}/).map(function (block) { 41 var lines = block.split(/\n/).map(function (line) { 42 return line.trim(); 43 }).filter(Boolean); 44 if (lines.length && lines.every(isBulletLine)) { 45 return '<ul>' + lines.map(function (line) { 46 return '<li>' + renderInlineMarkdown(line.replace(/^[-*â¢]\s+/, '')) + '</li>'; 47 }).join('') + '</ul>'; 48 } 49 if (lines.length && lines.every(isNumberedLine)) { 50 return '<ol>' + lines.map(function (line) { 51 return '<li>' + renderInlineMarkdown(line.replace(/^[0-9]+[.)]\s+/, '')) + '</li>'; 52 }).join('') + '</ol>'; 53 } 54 return '<p>' + renderInlineMarkdown(block).replace(/\n/g, '<br>') + '</p>'; 55 }).join(''); 56 } 57 58 function renderInlineMarkdown(text) { 59 var html = escapeHtml(String(text || '')); 60 return html.replace(/(\*\*|__)(.+?)\1/g, '<strong>$2</strong>'); 61 } 62 63 function isBulletLine(line) { 64 return /^[-*â¢]\s+/.test(line); 65 } 66 67 function isNumberedLine(line) { 68 return /^[0-9]+[.)]\s+/.test(line); 69 } 70 71 function formatReplyText(text, options) { 72 var clean = text.replace(/\r\n?/g, '\n').replace(/[ \t]+\n/g, '\n').trim(); 73 if (!clean) { 74 return ''; 75 } 76 if (options && options.streaming) { 77 return clean; 78 } 79 if (/\n\s*\n/.test(clean) || /^\s*([-*â¢]|\d+[.)])\s+/m.test(clean) || clean.length < 360) { 80 return clean; 81 } 82 83 var sentences = clean.match(/[^.!?]+[.!?]+(?=\s|$)|[^.!?]+$/g); 84 if (!sentences || sentences.length < 4) { 85 return clean; 86 } 87 88 var paragraphs = []; 89 var current = ''; 90 sentences.forEach(function (sentence) { 91 sentence = sentence.trim(); 92 if (!sentence) { 93 return; 94 } 95 if (current && (current.length + sentence.length > 230 || current.split(/[.!?]+/).length > 3)) { 96 paragraphs.push(current); 97 current = ''; 98 } 99 current += (current ? ' ' : '') + sentence; 100 }); 101 if (current) { 102 paragraphs.push(current); 103 } 104 105 return paragraphs.join('\n\n'); 106 } 107 108 function toolLabel(name) { 109 return { 110 get_current_offer: "Today's offers", 111 search_products: 'Inventory', 112 search_last_chance_offers: 'Last Chance', 113 search_cellar_inventory: 'Cellar inventory', 114 get_product_details: 'Product', 115 check_shipping_eligibility: 'Shipping', 116 get_customer_cellar_summary: 'Cellar', 117 get_customer_taste_profile: 'Taste profile', 118 get_case_club_status: 'Case Club', 119 search_case_club_bank: 'Case Club bank', 120 prepare_case_club_ship_all: 'Case Club shipment', 121 get_case_club_addresses: 'Case Club addresses', 122 get_case_club_payment_methods: 'Case Club cards', 123 prepare_case_club_custom_shipment: 'Case Club shipment', 124 plan_party_wines: 'Party plan', 125 get_recent_orders_summary: 'Orders', 126 get_customer_orders: 'Orders', 127 get_customer_order_details: 'Order details', 128 get_customer_order_tracking: 'Tracking', 129 get_order_or_shipment_status: 'Order / shipment', 130 prepare_cancel_order: 'Cancellation', 131 prepare_change_order_ship_date: 'Ship date', 132 prepare_customer_service_email: 'Customer Service', 133 prepare_add_to_cart: 'Cart' 134 }[name] || 'Site data'; 135 } 136 137 function storageAvailable() { 138 try { 139 var key = '__wtso_assistant_test__'; 140 window.localStorage.setItem(key, '1'); 141 window.localStorage.removeItem(key); 142 return true; 143 } catch (error) { 144 return false;
145 } 146 } 147 148 ready(function () { 149 var root = document.getElementById('wtso-assistant'); 150 if (!root) { 151 return; 152 } 153 154 var endpoint = root.getAttribute('data-endpoint'); 155 var streamEndpoint = root.getAttribute('data-stream-endpoint'); 156 var actionEndpoint = root.getAttribute('data-action-endpoint'); 157 var cartEndpoint = root.getAttribute('data-cart-endpoint'); 158 var assistantCartType = root.getAttribute('data-cart-type') || '999'; 159 var initialCartQty = root.getAttribute('data-initial-cart-qty'); 160 var launcher = document.getElementById('wtso-assistant-launcher'); 161 var close = document.getElementById('wtso-assistant-close'); 162 var backdrop = document.getElementById('wtso-assistant-backdrop'); 163 var reset = document.getElementById('wtso-assistant-reset'); 164 var cartButton = document.getElementById('wtso-assistant-cart'); 165 var cartCount = document.getElementById('wtso-assistant-cart-count'); 166 var panel = document.getElementById('wtso-assistant-panel'); 167 var body = document.getElementById('wtso-assistant-body'); 168 var form = document.getElementById('wtso-assistant-form'); 169 var input = document.getElementById('wtso-assistant-input'); 170 var sendButton = form.querySelector('button[type="submit"]'); 171 var initialBodyHtml = body.innerHTML; 172 var initialStarter = document.getElementById('wtso-assistant-starter'); 173 var initialStarterHtml = initialStarter ? initialStarter.outerHTML : ''; 174 var storageKey = 'wtsoAssistantSession:v1'; 175 var canStore = storageAvailable(); 176 var savedSession = loadSession(); 177 var conversation = savedSession.messages || []; 178 var defaultSuggestions = readSuggestionButtons(initialStarter); 179 var activeSuggestions = savedSession.suggestions && savedSession.suggestions.length 180 ? savedSession.suggestions.slice(0, 5) 181 : defaultSuggestions.slice(0, 5); 182 var pendingActions = {}; 183 var nextActionId = 1; 184 var lastCartSync = { source: '', value: null, at: 0 }; 185 var sseRetryDelay = 0; 186 187 function setOpen(isOpen) { 188 root.classList.toggle('is-open', isOpen); 189 launcher.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); 190 panel.setAttribute('aria-hidden', isOpen ? 'false' : 'true'); 191 saveSession(); 192 if (isOpen) { 193 setTimeout(function () { input.focus(); }, 50); 194 } 195 } 196 197 function setLoading(isLoading) { 198 root.classList.toggle('wtso-assistant-loading', isLoading); 199 input.disabled = isLoading; 200 sendButton.disabled = isLoading; 201 sendButton.textContent = isLoading ? 'Working' : 'Send'; 202 } 203 204 function scrollToBottom() { 205 body.scrollTop = body.scrollHeight; 206 } 207 208 function syncAssistantCartBadge(value, source) { 209 if (!cartCount) { 210 return; 211 } 212 var count = parseInt(String(value || '').replace(/[^0-9]/g, ''), 10) || 0; 213 lastCartSync = { 214 source: source || 'unknown', 215 value: count, 216 at: Date.now() 217 }; 218 var label = count > 99 ? '99+' : String(count); 219 if (cartCount.textContent !== label) { 220 cartCount.textContent = label; 221 } 222 if (cartCount.classList.contains('is-empty') !== (count <= 0)) { 223 cartCount.classList.toggle('is-empty', count <= 0); 224 } 225 } 226 227 function pageCartCount() { 228 var badges = document.querySelectorAll('[id^="cart_qty"]'); 229 if (!badges.length) { 230 return null; 231 } 232 var found = false; 233 var count = 0; 234 Array.prototype.forEach.call(badges, function (badge) { 235 if (cartCount && badge === cartCount) { 236 return; 237 } 238 var value = parseInt(String(badge.textContent || '').replace(/[^0-9]/g, ''), 10); 239 if (!isNaN(value)) { 240 found = true; 241 count = Math.max(count, value); 242 } 243 }); 244 return found ? count : null; 245 } 246 247 function refreshAssistantCartBadge() { 248 var pageCount = pageCartCount(); 249 if (pageCount !== null) { 250 syncAssistantCartBadge(pageCount, 'page'); 251 return; 252 } 253 if (!cartEndpoint) { 254 return; 255 } 256 var cartUrl = new URL(cartEndpoint, window.location.href); 257 if (cartUrl.origin !== window.location.origin) { 258 cartUrl = new URL(cartUrl.pathname + cartUrl.search, window.location.href); 259 } 260 cartUrl.searchParams.set('v', Date.now()); 261 fetch(cartUrl.toString(), { 262 method: 'GET', 263 cache: 'no-store', 264 headers: { 265 'X-Requested-With': 'XMLHttpRequest', 266 'Accept': 'application/json' 267 }, 268 credentials: 'same-origin' 269 }).then(function (response) { 270 return response.json(); 271 }).then(function (data) { 272 var quantity = data && data.cart ? data.cart.total_quantity : 0; 273 if (pageCartCount() === null) { 274 syncAssistantCartBadge(quantity, 'server'); 275 } 276 }).catch(function () { 277 // The header cart badge is helpful, but the assistant should still work if this fails. 278 }); 279 } 280 281 function watchSiteCartBadge() { 282 if (!window.MutationObserver) { 283 return; 284 } 285 var observer = new MutationObserver(function () { 286 var count = pageCartCount(); 287 if (count !== null) { 288 if (lastCartSync.source === 'server' && Date.now() - lastCartSync.at < 1500 && count !== lastCartSync.value) { 289 window.setTimeout(refreshAssistantCartBadge, 250); 290 return; 291 } 292 syncAssistantCartBadge(count, 'page'); 293 } 294 }); 295 Array.prototype.forEach.call(document.querySelectorAll('[id^="cart_qty"]'), function (badge) { 296 if (!cartCount || badge !== cartCount) { 297 observer.observe(badge, { childList: true, characterData: true, subtree: true }); 298 } 299 }); 300 if (document.body) { 301 observer.observe(document.body, { childList: true, subtree: true }); 302 } 303 } 304 305 function primeAssistantCartBadge() { 306 var attempts = 0; 307 if (initialCartQty !== null) { 308 syncAssistantCartBadge(initialCartQty, 'initial'); 309 } 310 refreshAssistantCartBadge(); 311 var timer = window.setInterval(function () { 312 attempts++; 313 refreshAssistantCartBadge(); 314 if (attempts >= 24) { 315 window.clearInterval(timer); 316 } 317 }, 500); 318 } 319 320 function loadSession() { 321 if (!canStore) { 322 return { messages: [], open: false, suggestions: [] }; 323 } 324 try { 325 var parsed = JSON.parse(window.localStorage.getItem(storageKey) || '{}'); 326 return { 327 messages: Array.isArray(parsed.messages) ? parsed.messages.filter(validStoredMessage).slice(-ASSISTANT_SESSION_MESSAGE_LIMIT) : [], 328 open: !!parsed.open, 329 suggestions: Array.isArray(parsed.suggestions) ? parsed.suggestions.filter(function (prompt) { 330 return typeof prompt === 'string' && prompt.trim(); 331 }).slice(0, 5) : [] 332 }; 333 } catch (error) { 334 return { messages: [], open: false, suggestions: [] }; 335 } 336 } 337 338 function saveSession() { 339 if (!canStore) { 340 return; 341 } 342 try { 343 window.localStorage.setItem(storageKey, JSON.stringify({ 344 messages: conversation.slice(-ASSISTANT_SESSION_MESSAGE_LIMIT).map(compactMessage),
345 open: root.classList.contains('is-open'), 346 suggestions: activeSuggestions.slice(0, 5) 347 })); 348 } catch (error) { 349 // Ignore storage quota/privacy failures; the assistant still works for the current page. 350 } 351 } 352 353 function compactMessage(message) { 354 return { 355 role: message.role === 'user' ? 'user' : 'assistant', 356 text: String(message.text || '').slice(0, 2500), 357 tools: Array.isArray(message.tools) ? message.tools.slice(0, 8) : [], 358 cards: Array.isArray(message.cards) ? message.cards.slice(0, 4) : [], 359 morePrompt: String(message.morePrompt || '').slice(0, 80), 360 createdAt: message.createdAt || Date.now() 361 }; 362 } 363 364 function validStoredMessage(message) { 365 return message && (message.role === 'user' || message.role === 'assistant') && typeof message.text === 'string'; 366 } 367 368 function readSuggestionButtons(container) { 369 if (!container) { 370 return []; 371 } 372 return Array.prototype.map.call(container.querySelectorAll('[data-prompt]'), function (button) { 373 return String(button.getAttribute('data-prompt') || '').trim(); 374 }).filter(Boolean); 375 } 376 377 function ensureStarter() { 378 var starter = document.getElementById('wtso-assistant-starter'); 379 if (starter) { 380 return starter; 381 } 382 if (!initialStarterHtml) { 383 return null; 384 } 385 var wrapper = document.createElement('div'); 386 wrapper.innerHTML = initialStarterHtml; 387 starter = wrapper.firstElementChild; 388 if (!starter) { 389 return null; 390 } 391 body.insertBefore(starter, body.firstChild); 392 return starter; 393 } 394 395 function setSuggestions(prompts) { 396 var starter = ensureStarter(); 397 var suggestions = []; 398 if (Array.isArray(prompts)) { 399 suggestions = prompts.filter(function (prompt) { 400 return typeof prompt === 'string' && prompt.trim(); 401 }).slice(0, 5); 402 } 403 if (!suggestions.length) { 404 suggestions = defaultSuggestions.slice(0, 5); 405 } 406 activeSuggestions = suggestions.slice(0); 407 if (!starter) { 408 saveSession(); 409 return; 410 } 411 412 var container = starter.querySelector('#wtso-assistant-suggestions'); 413 if (!container) { 414 saveSession(); 415 return; 416 } 417 container.innerHTML = activeSuggestions.map(function (prompt) { 418 var clean = String(prompt || '').trim(); 419 return '<button type="button" data-prompt="' + escapeHtml(clean) + '">' + escapeHtml(clean) + '</button>'; 420 }).join(''); 421 saveSession(); 422 } 423 424 function createRecord(role, text) { 425 var record = { 426 role: role === 'user' ? 'user' : 'assistant', 427 text: String(text || ''), 428 tools: [], 429 cards: [], 430 createdAt: Date.now() 431 }; 432 conversation.push(record); 433 if (conversation.length > ASSISTANT_SESSION_MESSAGE_LIMIT) { 434 conversation = conversation.slice(-ASSISTANT_SESSION_MESSAGE_LIMIT); 435 } 436 saveSession(); 437 return record; 438 } 439 440 function recentHistory(prompt) { 441 var recent = conversation.slice(-ASSISTANT_REQUEST_HISTORY_LIMIT); 442 if (recent.length) { 443 var last = recent[recent.length - 1]; 444 if (last.role === 'user' && last.text === prompt) { 445 recent = recent.slice(0, -1); 446 } 447 } 448 return recent.filter(function (message) { 449 return message.text && message.text.trim(); 450 }).map(function (message) { 451 return { 452 role: message.role, 453 text: message.text.slice(0, ASSISTANT_HISTORY_TEXT_LIMIT) 454 }; 455 }); 456 } 457 458 function appendMessage(role, text, options) { 459 options = options || {}; 460 var record = options.record || null; 461 if (options.persist !== false && !record) { 462 record = createRecord(role, text); 463 } 464 465 var wrapper = document.createElement('div'); 466 wrapper.className = 'wtso-assistant-message wtso-assistant-message-' + role; 467 468 var bubble = document.createElement('div'); 469 bubble.className = 'wtso-assistant-bubble'; 470 471 var reply = document.createElement('div'); 472 reply.className = 'wtso-assistant-reply'; 473 reply.innerHTML = renderReply(text); 474 475 var status = document.createElement('div'); 476 status.className = 'wtso-assistant-status'; 477 status.hidden = true; 478 479 var tools = document.createElement('div'); 480 tools.className = 'wtso-assistant-tool-list'; 481 tools.hidden = true; 482 483 var cards = document.createElement('div'); 484 cards.className = 'wtso-assistant-cards'; 485 cards.hidden = true; 486 487 var more = document.createElement('div'); 488 more.className = 'wtso-assistant-more'; 489 more.hidden = true; 490 491 var actions = document.createElement('div'); 492 actions.className = 'wtso-assistant-actions'; 493 actions.hidden = true; 494 495 bubble.appendChild(reply); 496 bubble.appendChild(status); 497 bubble.appendChild(tools); 498 bubble.appendChild(cards); 499 bubble.appendChild(more); 500 bubble.appendChild(actions); 501 wrapper.appendChild(bubble); 502 body.appendChild(wrapper); 503 scrollToBottom(); 504 505 return { 506 wrapper: wrapper, 507 bubble: bubble, 508 reply: reply, 509 status: status, 510 tools: tools, 511 cards: cards, 512 more: more, 513 actions: actions, 514 text: String(text || ''), 515 toolNames: [],
516 record: record, 517 streamEnded: false, 518 setText: function (value) { 519 this.text = String(value || ''); 520 this.reply.innerHTML = renderReply(this.text); 521 if (this.record) { 522 this.record.text = this.text; 523 saveSession(); 524 } 525 scrollToBottom(); 526 }, 527 appendText: function (value) { 528 this.text += String(value || ''); 529 this.reply.innerHTML = renderReply(this.text, { streaming: true }); 530 if (this.record) { 531 this.record.text = this.text; 532 saveSession(); 533 } 534 scrollToBottom(); 535 }, 536 setThinking: function (label, message) { 537 this.wrapper.classList.add('is-thinking'); 538 this.setStatus(label || 'Thinking', message || 'Reading your question'); 539 }, 540 clearThinking: function () { 541 this.wrapper.classList.remove('is-thinking'); 542 this.status.hidden = true; 543 }, 544 setStatus: function (label, message) { 545 this.status.hidden = false; 546 this.status.innerHTML = '<span class="wtso-assistant-status-label">' + escapeHtml(label) + '</span>' + 547 '<span>' + escapeHtml(message || '') + '</span>' + 548 '<span class="wtso-assistant-dots" aria-hidden="true"><i></i><i></i><i></i></span>'; 549 scrollToBottom(); 550 }, 551 addTool: function (name) { 552 if (!name || this.toolNames.indexOf(name) !== -1) { 553 return; 554 } 555 this.toolNames.push(name); 556 if (this.record && this.record.tools.indexOf(name) === -1) { 557 this.record.tools.push(name); 558 saveSession(); 559 } 560 this.renderTools(); 561 }, 562 setTools: function (calls) { 563 var self = this; 564 self.toolNames = []; 565 (calls || []).forEach(function (call) { 566 if (call && call.name && self.toolNames.indexOf(call.name) === -1) { 567 self.toolNames.push(call.name); 568 } 569 }); 570 if (this.record) { 571 this.record.tools = this.toolNames.slice(0, 8); 572 saveSession(); 573 } 574 this.renderTools(); 575 }, 576 renderTools: function () { 577 if (!this.toolNames.length) { 578 this.tools.hidden = true; 579 return; 580 } 581 this.tools.hidden = false; 582 this.tools.innerHTML = this.toolNames.map(function (name) { 583 return '<span>' + escapeHtml(toolLabel(name)) + '</span>'; 584 }).join(''); 585 scrollToBottom(); 586 }, 587 setCards: function (items) { 588 if (!items || !items.length) { 589 this.cards.hidden = true; 590 this.cards.innerHTML = ''; 591 return; 592 } 593 this.cards.hidden = false; 594 this.cards.innerHTML = renderCards(items); 595 if (this.record) { 596 this.record.cards = items.slice(0, 4); 597 saveSession(); 598 } 599 scrollToBottom(); 600 }, 601 setMorePrompt: function (prompt) { 602 if (!this.more) { 603 return; 604 } 605 prompt = String(prompt || '').trim(); 606 if (!prompt) { 607 this.more.hidden = true; 608 this.more.innerHTML = ''; 609 if (this.record) { 610 this.record.morePrompt = ''; 611 saveSession(); 612 } 613 return; 614 } 615 this.more.hidden = false;
616 this.more.innerHTML = '<button type="button" data-prompt="' + escapeHtml(prompt) + '">' + escapeHtml(prompt) + '</button>'; 617 if (this.record) { 618 this.record.morePrompt = prompt; 619 saveSession(); 620 } 621 scrollToBottom(); 622 }, 623 setActions: function (items) { 624 var html = renderActions(items || []); 625 if (!html) { 626 this.actions.hidden = true; 627 this.actions.innerHTML = ''; 628 return; 629 } 630 this.actions.hidden = false; 631 this.actions.innerHTML = html; 632 scrollToBottom(); 633 } 634 }; 635 } 636 637 function renderActions(actions) { 638 return actions.filter(function (action) { 639 return action && ( 640 action.type === 'customer_service_email' 641 || action.type === 'open_cart' 642 || action.type === 'open_login' 643 || action.type === 'case_club_ship_all' 644 || action.type === 'case_club_show_bank' 645 || action.type === 'case_club_clear_selection' 646 || action.type === 'case_club_prepare_selection_shipment' 647 || action.type === 'case_club_create_shipment' 648 || action.type === 'taste_profile_question' 649 || action.type === 'change_order_ship_date' 650 || action.type === 'change_order_address' 651 || action.type === 'cancel_order' 652 // Was missing, so prepare_add_to_cart queued an action that nothing could 653 // draw: the reply said "click the Add to Cart button below" and no button 654 // ever appeared. 655 || action.type === 'add_to_cart' 656 ); 657 }).map(function (action) { 658 var id = 'a' + nextActionId++; 659 pendingActions[id] = action; 660 if (action.type === 'taste_profile_question') { 661 var profileTitle = String(action.title || 'Taste profile').slice(0, 120); 662 var profileSummary = String(action.summary || 'Choose the closest answer.').slice(0, 180); 663 var optionHtml = ''; 664 if (Array.isArray(action.options)) { 665 optionHtml = action.options.map(function (option) { 666 if (!option || !option.type) { 667 return ''; 668 } 669 var optionId = 'a' + nextActionId++; 670 pendingActions[optionId] = option; 671 return '<button type="button" data-assistant-confirm-action="' + escapeHtml(optionId) + '">' + escapeHtml(String(option.label || 'Choose').slice(0, 48)) + '</button>'; 672 }).join(''); 673 } 674 return '<div class="wtso-assistant-action wtso-assistant-action-taste-profile" data-assistant-action-id="' + escapeHtml(id) + '">' + 675 '<div><strong>' + escapeHtml(profileTitle) + '</strong><span>' + escapeHtml(profileSummary) + '</span></div>' + 676 '<div class="wtso-assistant-choice-grid">' + optionHtml + '</div>' + 677 '</div>'; 678 } 679 if (action.type === 'open_cart') { 680 return '<div class="wtso-assistant-action wtso-assistant-action-cart" data-assistant-action-id="' + escapeHtml(id) + '">' + 681 '<div><strong>Ready when you are</strong><span>Review cart contents and continue checkout.</span></div>' + 682 '<button type="button" data-assistant-confirm-action="' + escapeHtml(id) + '">View Cart</button>' + 683 '</div>'; 684 } 685 if (action.type === 'open_login') { 686 return '<div class="wtso-assistant-action" data-assistant-action-id="' + escapeHtml(id) + '">' + 687 '<div><strong>Sign in to your account</strong><span>
687Access your orders, cellar, and personalized recommendations.</span></div>' + 688 '<button type="button" data-assistant-confirm-action="' + escapeHtml(id) + '">Sign In</button>' + 689 '</div>'; 690 } 691 if (action.type === 'add_to_cart') { 692 var addQty = Math.max(1, parseInt(action.quantity, 10) || 1); 693 var addName = String(action.product_name || 'this wine').slice(0, 90); 694 return '<div class="wtso-assistant-action wtso-assistant-action-cart" data-assistant-action-id="' + escapeHtml(id) + '">' + 695 '<div><strong>Add to cart</strong><span>' + 696 escapeHtml(addQty + (addQty === 1 ? ' bottle' : ' bottles') + ' of ' + addName) + 697 '</span></div>' + 698 '<button type="button" data-assistant-confirm-action="' + escapeHtml(id) + '">Add to Cart</button>' + 699 '</div>'; 700 } 701 if (action.type === 'case_club_ship_all' || action.type === 'case_club_show_bank' || action.type === 'case_club_clear_selection' || action.type === 'case_club_prepare_selection_shipment' || action.type === 'case_club_create_shipment') { 702 var caseTitle = String(action.title || 'Build Case Club shipment').slice(0, 120); 703 var caseSummary = String(action.summary || 'Create a shipment plan and review it before checkout.').slice(0, 160); 704 var caseLabel = String(action.label || (action.type === 'case_club_create_shipment' ? 'Create Shipment' : 'Build Shipment')).slice(0, 40); 705 return '<div class="wtso-assistant-action wtso-assistant-action-case-club" data-assistant-action-id="' + escapeHtml(id) + '">' + 706 '<div><strong>' + escapeHtml(caseTitle) + '</strong><span>' + escapeHtml(caseSummary) + '</span></div>' + 707 '<button type="button" data-assistant-confirm-action="' + escapeHtml(id) + '">' + escapeHtml(caseLabel) + '</button>' + 708 '</div>'; 709 } 710 if (action.type === 'change_order_ship_date') { 711 var shipDate = String(action.ship_date || '').slice(0, 20); 712 var shipOrderId = String(action.order_id || '').slice(0, 20); 713 return '<div class="wtso-assistant-action" data-assistant-action-id="' + escapeHtml(id) + '">' + 714 '<div><strong>Change ship date â Order #' + escapeHtml(shipOrderId) + '</strong><span>Ship on ' + escapeHtml(shipDate) + '</span></div>' + 715 '<button type="button" data-assistant-confirm-action="' + escapeHtml(id) + '">Confirm</button>' + 716 '</div>'; 717 } 718 if (action.type === 'change_order_address') { 719 var addrLabel = String(action.address_label || 'Selected address').slice(0, 160); 720 var addrOrderId = String(action.order_id || '').slice(0, 20); 721 return '<div class="wtso-assistant-action" data-assistant-action-id="' + escapeHtml(id) + '">' + 722 '<div><strong>Ship order #' + escapeHtml(addrOrderId) + ' to</strong><span>' + escapeHtml(addrLabel) + '</span></div>' + 723 '<button type="button" data-assistant-confirm-action="' + escapeHtml(id) + '">Confirm</button>' + 724 '</div>'; 725 } 726 if (action.type === 'cancel_order') { 727 var cancelOrderId = String(action.order_id || '').slice(0, 20); 728 return '<div class="wtso-assistant-action" data-assistant-action-id="' + escapeHtml(id) + '">' + 729 '<div><strong>Cancel order #' + escapeHtml(cancelOrderId) + '</strong><span>This cannot be undone once confirmed.</span></div>' + 730 '<button type="button" data-assistant-confirm-action="' + escapeHtml(id) + '">Cancel Order</button>' + 731 '</div>'; 732 } 733 var subject = String(action.subject || 'Customer service request').slice(0, 120); 734 return '<div class="wtso-assistant-action" data-assistant-action-id="' + escapeHtml(id) + '">' + 735 '<div><strong>Ready to send</strong><span>' + escapeHtml(subject) + '</span></div>' + 736 '<button type="button" data-assistant-confirm-action="' + escapeHtml(id) + '">Send to Customer Service</button>' + 737 '</div>'; 738 }).join(''); 739 } 740 741 function renderCards(cards) { 742 var expandedCaseClub = cards.every(function (card) { 743 var source = String(card.catalog_source || card.source || ''); 744 return source === 'case_club_selection' || source === 'case_club_shipment'; 745 }); 746 return cards.slice(0, expandedCaseClub ? 12 : 4).map(function (card) { 747 var details = [card.varietal, card.appellation, card.region, card.country].filter(
747Boolean).join(' / '); 748 var price = money(card.price); 749 var retail = money(card.retail_price); 750 var source = String(card.catalog_source || card.source || ''); 751 var isCaseClubBank = source === 'case_club_bank'; 752 var isCaseClubSelection = source === 'case_club_selection'; 753 // A flash deal or Collector's Drop wine has to be bought as its own order, and the 754 // chat cart posts to a bucket that is neither one. Send the customer to the real 755 // offer page instead of adding here or opening a detail popup bound to that cart. 756 var isLiveOffer = source === 'current_offer' || source === 'collectors_drop'; 757 var offerUrl = isLiveOffer ? String(card.url || '') : ''; 758 var canAdd = !!price && !isCaseClubBank && !isCaseClubSelection && !isLiveOffer; 759 var badges = []; 760 if (Array.isArray(card.personalization_badges)) { 761 card.personalization_badges.slice(0, 3).forEach(function (badge) { 762 if (badge) { 763 badges.push(badge); 764 } 765 }); 766 } 767 if (card.party_role) { 768 badges.push(card.party_role); 769 } 770 if (card.recommended_quantity) { 771 badges.push(parseInt(card.recommended_quantity, 10) + ' bottles'); 772 } 773 if (card.percent_off) { 774 badges.push(card.percent_off + '% off'); 775 } 776 if (card.rating) { 777 badges.push(card.rating + ' pts'); 778 } 779 // card.inventory is now withheld above a dozen bottles, so the badge reads the 780 // low_inventory flag instead; the old inventory < 100 test would have shown the 781 // badge on almost nothing. 782 if (card.low_inventory) { 783 badges.push('Low inventory'); 784 } 785 786 var ratings = Array.isArray(card.ratings) ? card.ratings.slice(0, 3) : []; 787 var productId = parseInt(card.product_id, 10) || 0; 788 var storeId = parseInt(card.store_id, 10) || 0; 789 var html = '<article class="wtso-assistant-card" data-product-id="' + escapeHtml(productId) + '" data-store-id="' + escapeHtml(storeId) + '" data-card-source="' + escapeHtml(source) + '">'; 790 if (offerUrl) { 791 html += '<a class="wtso-assistant-card-image" href="' + escapeHtml(offerUrl) + '" aria-label="View this offer"><img src="' + escapeHtml(card.image) + '" alt=""></a>'; 792 } else { 793 html += '<button type="button" class="wtso-assistant-card-image" data-assistant-action="details" aria-label="View wine details"><img src="' + escapeHtml(card.image) + '" alt=""></button>'; 794 } 795 html += '<div class="wtso-assistant-card-copy">'; 796 if (badges.length) { 797 html += '<div class="wtso-assistant-card-badges">' + badges.map(function (badge) { 798 return '<span>' + escapeHtml(badge) + '</span>'; 799 }).join('') + '</div>'; 800 } 801 html += '<h3>' + escapeHtml(card.name) + '</h3>'; 802 if (card.party_reason) { 803 html += '<p class="wtso-assistant-card-role">' + escapeHtml(card.party_reason) + '</p>'; 804 } 805 if (details) { 806 html += '<p class="wtso-assistant-card-meta">' + escapeHtml(details) + '</p>'; 807 } 808 if (ratings.length) { 809 html += '<div class="wtso-assistant-card-ratings">' + ratings.map(function (rating) { 810 var ratingText = String(rating.text || '').trim(); 811 var initials = String(rating.initials || '').trim(); 812 var label = ratingText || initials; 813 return '<span>' + escapeHtml(label) + '</span>'; 814 }).join('') + '</div>'; 815 } 816 html += '<div class="wtso-assistant-card-row">'; 817 html += '<div>'; 818 if (isCaseClubBank) { 819 html += '<span class="wtso-assistant-card-bank-count">' + escapeHtml(parseInt(card.bank_quantity, 10) || 0) + ' banked</span>'; 820 } else if (isCaseClubSelection) { 821 html += '<span class="wtso-assistant-card-bank-count">' + escapeHtml(parseInt(card.selected_quantity, 10) || parseInt(card.bank_quantity, 10) || 0) + ' selected</span>'; 822 } else { 823 html += '<span class="wtso-assistant-card-price">' + escapeHtml(price) + '</span>'; 824 } 825 if (retail) { 826 html += '<span class="wtso-assistant-card-retail">' + escapeHtml(retail) + '</span>'; 827 } 828 html += '</div>'; 829 html += '<div class="wtso-assistant-card-actions">'; 830 if (offerUrl) { 831 html += '<a class="wtso-assistant-card-add" href="' + escapeHtml(offerUrl) + '">' 832 + escapeHtml(source === 'collectors_drop' ? 'Shop the Drop' : 'Shop this offer') + '</a>'; 833 } else { 834 html += '<button type="button" class="wtso-assistant-card-link" data-assistant-action="details">View</button>'; 835 if (isCaseClubBank) { 836 html += '<button type="button" class="wtso-assistant-card-add wtso-assistant-card-select" data-assistant-action="case-club-select" data-quantity="1">Select</button>'; 837 } else if (isCaseClubSelection) { 838 html += '<button type="button" class="wtso-assistant-card-add wtso-assistant-card-select" data-assistant-action="case-club-select" data-quantity="1">+1</button>'; 839 html += '<button type="button" class="wtso-assistant-card-link wtso-assistant-card-remove" data-assistant-action="case-club-remove" data-quantity="1">Remove</button>'; 840 } 841 if (canAdd) { 842 var qty = Math.max(1, parseInt(card.recommended_quantity, 10) || 1); 843 var addLabel = qty > 1 ? 'Add ' + qty : 'Add'; 844 html += '<button type="button" class="wtso-assistant-card-add" data-assistant-action="add" data-quantity="' + escapeHtml(qty) + '">' + escapeHtml(addLabel) + '</button>'; 845 } 846 } 847 html += '</div>'; 848 html += '</div>'; 849 html += '</div>'; 850 html += '</article>'; 851 return html; 852 }).join(''); 853 } 854 855 function assistantOpenDetails(productId) { 856 if (!productId) { 857 return; 858 } 859 if (typeof window.open_details === 'function') { 860 window.open_details(productId, assistantCartType); 861 return; 862 } 863 if (!window.jQuery) { 864 window.location.href = '/wine-search?terms=' + encodeURIComponent(productId); 865 return; 866 } 867 868 var $ = window.jQuery; 869 if (!$('#product_detail').length || !$('#returnDisplay').length) { 870 window.location.href = '/wine-search?terms=' + encodeURIComponent(productId); 871 return; 872 } 873 $('#returnDisplay').html('<center><img src="' + baseUrl() + 'assets/images/winebottle-loading.gif" /></center>'); 874 $('.cart_checkout_btn').css('display', 'none'); 875 $('#product_detail').modal('show'); 876 $.getJSON(baseUrl() + 'cart/product_detail_pop/' + productId + '/' + encodeURIComponent(assistantCartType) + '?v=' + Date.now(), function (data) {
877 $('#modal-title').html('Wine Details'); 878 $('#returnDisplay').html(data && data.reply ? data.reply : 'Product details are unavailable.'); 879 }); 880 } 881 882 function assistantAddToCart(productId, button) { 883 if (!productId || button.disabled) { 884 return; 885 } 886 887 var quantity = Math.min(99, Math.max(1, parseInt(button.getAttribute('data-quantity'), 10) || 1)); 888 var originalText = button.textContent; 889 button.disabled = true; 890 button.textContent = 'Adding'; 891 892 addCartQuantity(productId, quantity).then(function (data) { 893 if (!data || data.cart_total === null || data.error) { 894 throw new Error(data && data.error ? data.error : 'Unable to add this wine.'); 895 } 896 syncCartDisplays(data); 897 button.textContent = quantity > 1 ? 'Added ' + quantity : 'Added'; 898 setTimeout(function () { 899 button.disabled = false; 900 button.textContent = originalText; 901 }, 1400); 902 }).catch(function (error) { 903 button.disabled = false; 904 button.textContent = originalText; 905 window.alert(error && error.message ? error.message : 'Unable to add this wine.'); 906 }); 907 } 908 909 function addCartQuantity(productId, quantity) { 910 var lastData = null; 911 var chain = Promise.resolve(); 912 for (var i = 0; i < quantity; i++) { 913 chain = chain.then(function () { 914 return fetch(baseUrl() + 'cart/update_cart?products_id=' + encodeURIComponent(productId) + '&type=' + encodeURIComponent(assistantCartType) + '&action=inc', { 915 method: 'GET', 916 headers: { 917 'X-Requested-With': 'XMLHttpRequest', 918 'Accept': 'application/json' 919 }, 920 credentials: 'same-origin' 921 }).then(function (response) { 922 return response.json(); 923 }).then(function (data) { 924 lastData = data; 925 if (!data || data.cart_total === null || data.error) { 926 throw new Error(data && data.error ? data.error : 'Unable to add this wine.'); 927 } 928 return data; 929 }); 930 }); 931 } 932 return chain.then(function () { 933 return lastData; 934 }); 935 } 936 937 function syncCartDisplays(data) { 938 if (window.jQuery) { 939 var $ = window.jQuery; 940 $('input[id^="bottles_' + data.products_id + '"]').val(data.new_qty); 941 $('select[id^="' + data.products_id + '_qty"]').val(data.new_qty); 942 if (typeof window.update_displays === 'function') { 943 window.update_displays(data); 944 syncAssistantCartBadge(data.cart_qty, 'server'); 945 return; 946 } 947 $('div[id^="cart_total"]').html(data.cart_total); 948 $('div[id^="cart_qty"]').html(data.cart_qty); 949 syncAssistantCartBadge(data.cart_qty, 'server'); 950 if (typeof window.async_crdl_cart === 'function') { 951 window.async_crdl_cart(); 952 } 953 } 954 } 955 956 function assistantOpenCart() { 957 refreshAssistantCartBadge(); 958 if (typeof window.view_cart === 'function') { 959 window.view_cart(); 960 return; 961 } 962 if (window.jQuery && window.jQuery('#product_detail').length && window.jQuery('#returnDisplay').length) { 963 var $ = window.jQuery; 964 $('#returnDisplay').html(''); 965 $('#product_detail').modal('show'); 966 $.getJSON(baseUrl() + 'cart/view_cart', function (data) {
967 $('#modal-title').html('My Cart'); 968 $('#returnDisplay').html(data && data.reply ? data.reply : 'Your cart is unavailable.'); 969 $('#cart_checkout_btn').css('display', 'inline-block'); 970 }); 971 return; 972 } 973 window.location.href = baseUrl() + 'cart/checkout'; 974 } 975 976 function sendAssistantAction(actionId, button) { 977 var action = pendingActions[actionId]; 978 if (!action || button.disabled) { 979 return; 980 } 981 if (action.type === 'open_cart') { 982 assistantOpenCart(); 983 return; 984 } 985 if (action.type === 'open_login') { 986 if (typeof window.openAccountDrawer === 'function') { 987 window.openAccountDrawer(); 988 } 989 return; 990 } 991 if (!actionEndpoint) { 992 return; 993 } 994 995 var originalText = button.textContent; 996 button.disabled = true; 997 button.textContent = (action.type.indexOf('taste_profile_') === 0) 998 ? (action.type === 'taste_profile_save' ? 'Saving' : 'Selected') 999 : (action.type.indexOf('case_club_') === 0) 1000 ? (action.type === 'case_club_create_shipment' ? 'Creating' : 'Working') 1001 : 'Sending'; 1002 1003 fetch(actionEndpoint, { 1004 method: 'POST', 1005 headers: { 1006 'Content-Type': 'application/json', 1007 'X-Requested-With': 'XMLHttpRequest' 1008 }, 1009 credentials: 'same-origin', 1010 body: JSON.stringify({ action: action }) 1011 }).then(function (response) { 1012 return response.json().then(function (data) { 1013 if (!response.ok || !data || !data.ok) { 1014 throw new Error(data && data.error ? data.error : 'Unable to send this message.'); 1015 } 1016 return data; 1017 }); 1018 }).then(function (data) { 1019 delete pendingActions[actionId]; 1020 button.textContent = data.redirect_url ? 'Opening' : 'Done'; 1021 button.closest('.wtso-assistant-action').classList.add('is-complete'); 1022 appendAssistantResult(data, data.message || 'Done.'); 1023 if (action.type === 'add_to_cart') { 1024 // The cart changed on the server, so the header badge is now stale. 1025 refreshAssistantCartBadge(); 1026 } 1027 if (data.redirect_url) { 1028 window.setTimeout(function () { 1029 window.location.href = data.redirect_url; 1030 }, 350); 1031 } 1032 }).catch(function (error) { 1033 button.disabled = false; 1034 button.textContent = originalText; 1035 window.alert(error && error.message ? error.message : 'Unable to send this message.'); 1036 }); 1037 } 1038 1039 function sendCaseClubSelection(card, mode, button) { 1040 if (!actionEndpoint || !card || !button || button.disabled) { 1041 return; 1042 } 1043 1044 var productId = parseInt(card.getAttribute('data-product-id'), 10) || 0; 1045 var storeId = parseInt(card.getAttribute('data-store-id'), 10) || 0; 1046 if (!productId && !storeId) { 1047 return; 1048 } 1049 1050 var originalText = button.textContent; 1051 button.disabled = true; 1052 button.textContent = mode === 'remove' ? 'Removing' : 'Selecting'; 1053 1054 fetch(actionEndpoint, { 1055 method: 'POST', 1056 headers: { 1057 'Content-Type': 'application/json', 1058 'X-Requested-With': 'XMLHttpRequest' 1059 }, 1060 credentials: 'same-origin', 1061 body: JSON.stringify({ 1062 action: { 1063 type: 'case_club_select_bank_item', 1064 mode: mode, 1065 product_id: productId, 1066 store_id: storeId, 1067 quantity: Math.max(1, parseInt(button.getAttribute('data-quantity'), 10) || 1) 1068 } 1069 }) 1070 }).then(function (response) { 1071 return response.json().then(function (data) { 1072 if (!response.ok || !data || !data.ok) { 1073 throw new Error(data && data.error ? data.error : 'Unable to update this shipment selection.'); 1074 } 1075 return data; 1076 }); 1077 }).then(function (data) { 1078 button.textContent = mode === 'remove' ? 'Removed' : 'Selected'; 1079 appendAssistantResult(data, 'Updated your Case Club shipment selection.'); 1080 }).catch(function (error) { 1081 button.disabled = false;
1082 button.textContent = originalText; 1083 window.alert(error && error.message ? error.message : 'Unable to update this shipment selection.'); 1084 }); 1085 } 1086 1087 function appendAssistantResult(data, fallbackText) { 1088 var message = appendMessage('assistant', data && data.message ? data.message : fallbackText); 1089 if (data && data.tool_calls) { 1090 message.setTools(data.tool_calls); 1091 } 1092 if (data && data.products) { 1093 message.setCards(data.products); 1094 } else if (data && data.cards) { 1095 message.setCards(data.cards); 1096 } 1097 var actions = []; 1098 if (data && Array.isArray(data.actions)) { 1099 actions = data.actions; 1100 } else if (data && data.action) { 1101 actions = [data.action]; 1102 } 1103 message.setActions(actions); 1104 return message; 1105 } 1106 1107 function baseUrl() { 1108 return typeof window.BASEURL === 'string' && window.BASEURL ? window.BASEURL : '/'; 1109 } 1110 1111 function requestBody(prompt) { 1112 return JSON.stringify({ 1113 message: prompt, 1114 context: { 1115 url: window.location.href, 1116 path: window.location.pathname, 1117 history: recentHistory(prompt) 1118 } 1119 }); 1120 } 1121 1122 function applyFinal(message, data) { 1123 message.clearThinking(); 1124 if (data && data.reply) { 1125 message.setText(data.reply); 1126 } else if (!message.text.trim()) { 1127 message.setText(data.reply || 'Done.'); 1128 } 1129 message.setTools(data.tool_calls || []); 1130 message.setCards(data.cards || []); 1131 message.setMorePrompt(data.more_prompt || ''); 1132 message.setActions(data.actions || []); 1133 setSuggestions(data.suggestions || []); 1134 } 1135 1136 function sendJson(prompt, assistantMessage) { 1137 return fetch(endpoint, { 1138 method: 'POST', 1139 headers: { 1140 'Content-Type': 'application/json', 1141 'X-Requested-With': 'XMLHttpRequest' 1142 }, 1143 credentials: 'same-origin', 1144 body: requestBody(prompt) 1145 }).then(function (response) { 1146 return response.json(); 1147 }).then(function (data) { 1148 if (!data || !data.ok) { 1149 assistantMessage.clearThinking(); 1150 assistantMessage.setText(data && data.error ? data.error : 'The assistant endpoint did not return a usable response.'); 1151 return; 1152 } 1153 applyFinal(assistantMessage, data); 1154 }); 1155 } 1156 1157 function sendStream(prompt, assistantMessage) { 1158 if (!streamEndpoint || !window.ReadableStream || !window.TextDecoder) { 1159 return Promise.reject(new Error('Streaming is not supported.')); 1160 } 1161 assistantMessage.streamEnded = false; 1162 1163 return fetch(streamEndpoint, { 1164 method: 'POST', 1165 headers: { 1166 'Content-Type': 'application/json', 1167 'X-Requested-With': 'XMLHttpRequest', 1168 'Accept': 'text/event-stream' 1169 }, 1170 credentials: 'same-origin', 1171 body: requestBody(prompt) 1172 }).then(function (response) { 1173 if (!response.ok || !response.body) { 1174 throw new Error('Streaming endpoint failed.'); 1175 } 1176 1177 var reader = response.body.getReader(); 1178 var decoder = new TextDecoder(); 1179 var buffer = ''; 1180 1181 function read() { 1182 return reader.read().then(function (result) { 1183 if (result.done) { 1184 buffer += decoder.decode(); 1185 if (buffer.trim()) { 1186 processSseBlock(buffer, assistantMessage); 1187 } 1188 if (!assistantMessage.streamEnded) { 1189 throw new Error('Streaming ended before the final event.'); 1190 } 1191 return; 1192 } 1193 buffer += decoder.decode(result.value, { stream: true }); 1194 var parts = buffer.split(/\r?\n\r?\n/); 1195 buffer = parts.pop();
1196 parts.forEach(function (part) { 1197 processSseBlock(part, assistantMessage); 1198 }); 1199 return read(); 1200 }); 1201 } 1202 1203 return read(); 1204 }); 1205 } 1206 1207 function processSseBlock(block, assistantMessage) { 1208 var eventName = 'message'; 1209 var dataLines = []; 1210 block.split(/\r?\n/).forEach(function (line) { 1211 if (!line || line.charAt(0) === ':') { 1212 return; 1213 } 1214 var colon = line.indexOf(':'); 1215 var field = colon === -1 ? line : line.slice(0, colon); 1216 var value = colon === -1 ? '' : line.slice(colon + 1); 1217 if (value.charAt(0) === ' ') { 1218 value = value.slice(1); 1219 } 1220 if (field === 'event') { 1221 eventName = value.trim(); 1222 } else if (field === 'data') { 1223 dataLines.push(value); 1224 } else if (field === 'retry') { 1225 var retry = parseInt(value, 10); 1226 if (!isNaN(retry)) { 1227 sseRetryDelay = retry; 1228 } 1229 } 1230 }); 1231 1232 if (!dataLines.length) { 1233 return; 1234 } 1235 1236 var data; 1237 try { 1238 data = JSON.parse(dataLines.join('\n')); 1239 } catch (error) { 1240 try { 1241 data = JSON.parse(dataLines.join('')); 1242 } catch (secondError) { 1243 if (window.console && window.console.warn) { 1244 window.console.warn('Assistant stream sent malformed JSON.', secondError); 1245 } 1246 return; 1247 } 1248 } 1249 1250 if (eventName === 'status') { 1251 assistantMessage.setStatus(data.label || 'Working', data.message || ''); 1252 } else if (eventName === 'tool') { 1253 assistantMessage.addTool(data.name); 1254 } else if (eventName === 'delta') { 1255 assistantMessage.wrapper.classList.remove('is-thinking'); 1256 assistantMessage.status.hidden = true; 1257 assistantMessage.appendText(data.text || ''); 1258 } else if (eventName === 'final') { 1259 assistantMessage.streamEnded = true; 1260 applyFinal(assistantMessage, data); 1261 } else if (eventName === 'error') { 1262 assistantMessage.streamEnded = true; 1263 assistantMessage.clearThinking(); 1264 assistantMessage.setText(data.message || 'The assistant could not complete the request.'); 1265 } 1266 } 1267 1268 function sendMessage(text) { 1269 var prompt = String(text || '').trim(); 1270 if (!prompt || root.classList.contains('wtso-assistant-loading')) { 1271 return; 1272 } 1273 1274 appendMessage('user', prompt); 1275 var assistantMessage = appendMessage('assistant', ''); 1276 assistantMessage.setThinking('Thinking', 'Reading your question'); 1277 input.value = ''; 1278 setLoading(true); 1279
1280 sendStream(prompt, assistantMessage).catch(function () { 1281 if (assistantMessage.streamEnded) { 1282 return; 1283 } 1284 assistantMessage.clearThinking(); 1285 assistantMessage.setText(''); 1286 assistantMessage.setTools([]); 1287 assistantMessage.setCards([]); 1288 assistantMessage.setMorePrompt(''); 1289 assistantMessage.setActions([]); 1290 assistantMessage.setThinking('Working', 'Using the standard assistant endpoint'); 1291 return sendJson(prompt, assistantMessage); 1292 }).catch(function () { 1293 assistantMessage.clearThinking(); 1294 assistantMessage.setText('The assistant could not reach the site tools. Try again in a moment.'); 1295 }).finally(function () { 1296 setLoading(false); 1297 input.focus(); 1298 }); 1299 } 1300 1301 launcher.addEventListener('click', function () { 1302 setOpen(!root.classList.contains('is-open')); 1303 }); 1304 close.addEventListener('click', function () { 1305 setOpen(false); 1306 }); 1307 if (backdrop) { 1308 backdrop.addEventListener('click', function () { 1309 setOpen(false); 1310 }); 1311 } 1312 reset.addEventListener('click', function () { 1313 conversation = []; 1314 saveSession(); 1315 body.innerHTML = initialBodyHtml; 1316 activeSuggestions = defaultSuggestions.slice(0, 5); 1317 setSuggestions(activeSuggestions); 1318 input.value = ''; 1319 input.focus(); 1320 }); 1321 if (cartButton) { 1322 cartButton.addEventListener('click', function () { 1323 assistantOpenCart(); 1324 }); 1325 } 1326 form.addEventListener('submit', function (event) { 1327 event.preventDefault(); 1328 sendMessage(input.value); 1329 }); 1330 body.addEventListener('click', function (event) { 1331 var promptButton = event.target.closest('[data-prompt]'); 1332 if (promptButton) { 1333 sendMessage(promptButton.getAttribute('data-prompt')); 1334 return; 1335 } 1336 1337 var confirmActionButton = event.target.closest('[data-assistant-confirm-action]'); 1338 if (confirmActionButton) { 1339 sendAssistantAction(confirmActionButton.getAttribute('data-assistant-confirm-action'), confirmActionButton); 1340 return; 1341 } 1342 1343 var actionButton = event.target.closest('[data-assistant-action]'); 1344 if (actionButton) { 1345 var card = actionButton.closest('.wtso-assistant-card'); 1346 var productId = card ? parseInt(card.getAttribute('data-product-id'), 10) : 0; 1347 if (actionButton.getAttribute('data-assistant-action') === 'details') { 1348 assistantOpenDetails(productId); 1349 } else if (actionButton.getAttribute('data-assistant-action') === 'add') { 1350 assistantAddToCart(productId, actionButton); 1351 } else if (actionButton.getAttribute('data-assistant-action') === 'case-club-select') { 1352 sendCaseClubSelection(card, 'add', actionButton); 1353 } else if (actionButton.getAttribute('data-assistant-action') === 'case-club-remove') { 1354 sendCaseClubSelection(card, 'remove', actionButton); 1355 } 1356 } 1357 }); 1358 1359 if (conversation.length) { 1360 body.innerHTML = ''; 1361 setSuggestions(activeSuggestions); 1362 conversation.forEach(function (message) { 1363 var restored = appendMessage(message.role, message.text, { persist: false }); 1364 restored.setTools((message.tools || []).map(function (name) { return { name: name }; })); 1365 restored.setCards(message.cards || []); 1366 restored.setMorePrompt(message.morePrompt || ''); 1367 }); 1368 } else { 1369 setSuggestions(activeSuggestions); 1370 } 1371 if (savedSession.open) { 1372 setOpen(true); 1373 } 1374 primeAssistantCartBadge(); 1375 watchSiteCartBadge(); 1376 }); 1377})();
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.