#
tokens: 23038/50000 1/45 files (page 4/5)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 4 of 5. Use http://codebase.md/Xyborg/ChatGPT-Product-Info?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .gitignore
├── assets
│   ├── chatgpt-product-info-reviews.png
│   └── chatgpt-product-info.png
├── chatgpt-product-info.js
├── chrome-extension
│   ├── assets
│   │   ├── flags
│   │   │   ├── all.svg
│   │   │   ├── ar.svg
│   │   │   ├── at.svg
│   │   │   ├── be.svg
│   │   │   ├── br.svg
│   │   │   ├── ch.svg
│   │   │   ├── de.svg
│   │   │   ├── es.svg
│   │   │   ├── fr.svg
│   │   │   ├── gb.svg
│   │   │   ├── it.svg
│   │   │   ├── mx.svg
│   │   │   ├── nl.svg
│   │   │   ├── pt.svg
│   │   │   ├── se.svg
│   │   │   └── us.svg
│   │   └── icons-ui
│   │       ├── analysis.svg
│   │       ├── check.svg
│   │       ├── database.svg
│   │       ├── down-arrow.svg
│   │       ├── edit.svg
│   │       ├── error.svg
│   │       ├── github.svg
│   │       ├── history.svg
│   │       ├── linkedin.svg
│   │       ├── negative.svg
│   │       ├── neutral.svg
│   │       ├── positive.svg
│   │       ├── project.svg
│   │       ├── search.svg
│   │       ├── settings.svg
│   │       ├── tag.svg
│   │       ├── up-arrow.svg
│   │       ├── warning.svg
│   │       └── x.svg
│   ├── content-script.js
│   ├── icons
│   │   ├── icon128.png
│   │   ├── icon16.png
│   │   ├── icon19.png
│   │   ├── icon32.png
│   │   ├── icon48.png
│   │   └── logobubble.svg
│   ├── manifest.json
│   ├── popup.html
│   ├── popup.js
│   ├── README.md
│   └── styles.css
├── LICENSE
└── README.md
```

# Files

--------------------------------------------------------------------------------
/chatgpt-product-info.js:
--------------------------------------------------------------------------------

```javascript
   1 | // ChatGPT Product Info Search
   2 | // Paste this entire script into ChatGPT's browser console and it will create a floating button
   3 | 
   4 | (function() {
   5 |     // Remove existing modal and button if present
   6 |     const existingModal = document.getElementById('chatgpt-product-search-modal');
   7 |     const existingButton = document.getElementById('openProductSearchModalBtn');
   8 |     if (existingModal) {
   9 |         existingModal.remove();
  10 |     }
  11 |     if (existingButton) {
  12 |         existingButton.remove();
  13 |     }
  14 | 
  15 |     // Add floating button styles
  16 |     const floatingButtonCSS = `
  17 |         #openProductSearchModalBtn {
  18 |             position: fixed;
  19 |             right: 20px;
  20 |             top: 40%;
  21 |             transform: translateY(-50%);
  22 |             background-color: #007bff;
  23 |             color: white;
  24 |             border: none;
  25 |             border-radius: 50%;
  26 |             width: 60px;
  27 |             height: 60px;
  28 |             font-size: 24px;
  29 |             box-shadow: 0 3px 10px rgba(0,0,0,0.2);
  30 |             cursor: pointer;
  31 |             z-index: 9990;
  32 |             display: flex;
  33 |             align-items: center;
  34 |             justify-content: center;
  35 |             transition: all 0.3s ease;
  36 |         }
  37 |         #openProductSearchModalBtn:hover {
  38 |             background-color: #0056b3;
  39 |             box-shadow: 0 5px 15px rgba(0,0,0,0.3);
  40 |         }
  41 |     `;
  42 | 
  43 |     // Create modal HTML
  44 |     const modalHTML = `
  45 |         <div id="chatgpt-product-search-modal" style="
  46 |             position: fixed;
  47 |             top: 0;
  48 |             left: 0;
  49 |             width: 100%;
  50 |             height: 100%;
  51 |             background: rgb(230 237 248 / 80%);
  52 |             display: flex;
  53 |             align-items: center;
  54 |             justify-content: center;
  55 |             z-index: 10000;
  56 |             font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  57 |         ">
  58 |             <div style="
  59 |                 background: white;
  60 |                 width: 90%;
  61 |                 height:85%;
  62 |                 border-radius: 8px;
  63 |                 display: flex;
  64 |                 flex-direction: column;
  65 |                 overflow: hidden;
  66 |                 box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
  67 |             ">
  68 |                 <div style="
  69 |                     background: #f8f9fa;
  70 |                     padding: 12px 20px;
  71 |                     display: flex;
  72 |                     justify-content: space-between;
  73 |                     align-items: center;
  74 |                     border-bottom: 1px solid #e9ecef;
  75 |                 ">
  76 |                     <h1 style="
  77 |                         font-size: 18px;
  78 |                         font-weight: 600;
  79 |                         margin: 0;
  80 |                         color: #495057;
  81 |                     ">🔍 ChatGPT Product Info Search</h1>
  82 |                     <button id="close-modal-btn" style="
  83 |                         background: none;
  84 |                         border: none;
  85 |                         color: #6c757d;
  86 |                         font-size: 20px;
  87 |                         width: 30px;
  88 |                         height: 30px;
  89 |                         border-radius: 4px;
  90 |                         cursor: pointer;
  91 |                         display: flex;
  92 |                         align-items: center;
  93 |                         justify-content: center;
  94 |                     ">&times;</button>
  95 |                 </div>
  96 |                 
  97 |                 <div style="
  98 |                     flex: 1;
  99 |                     display: flex;
 100 |                     flex-direction: column;
 101 |                     overflow: hidden;
 102 |                 ">
 103 |                     <div id="search-area" style="
 104 |                         position: relative;
 105 |                         padding: 20px;
 106 |                         border-bottom: 1px solid #e9ecef;
 107 |                         background: white;
 108 |                         transition: all 0.3s ease;
 109 |                     ">
 110 |                         <!-- Collapse/Expand Button - positioned absolutely -->
 111 |                         <div id="collapse-toggle" style="
 112 |                             display: none;
 113 |                             position: absolute;
 114 |                             top: 8px;
 115 |                             right: 20px;
 116 |                             cursor: pointer;
 117 |                             color: #007bff;
 118 |                             font-size: 12px;
 119 |                             font-weight: 500;
 120 |                             transition: all 0.2s ease;
 121 |                             border-radius: 4px;
 122 |                             padding: 4px 8px;
 123 |                             background: rgba(0, 123, 255, 0.1);
 124 |                             border: 1px solid rgba(0, 123, 255, 0.2);
 125 |                             z-index: 10;
 126 |                         ">
 127 |                             <span id="collapse-text">▲ Hide</span>
 128 |                         </div>
 129 |                         
 130 |                         <div id="search-controls">
 131 |                         <!-- Multi-product toggle -->
 132 |                         <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px; padding: 8px 0;">
 133 |                             <label style="
 134 |                                 display: flex;
 135 |                                 align-items: center;
 136 |                                 gap: 8px;
 137 |                                 font-size: 14px;
 138 |                                 color: #495057;
 139 |                                 font-weight: 500;
 140 |                                 cursor: pointer;
 141 |                             ">
 142 |                                 <div style="
 143 |                                     position: relative;
 144 |                                     width: 44px;
 145 |                                     height: 24px;
 146 |                                     background: #dee2e6;
 147 |                                     border-radius: 12px;
 148 |                                     transition: background 0.3s ease;
 149 |                                     cursor: pointer;
 150 |                                 " id="toggle-background">
 151 |                                     <input type="checkbox" id="multi-product-toggle" style="
 152 |                                         position: absolute;
 153 |                                         opacity: 0;
 154 |                                         width: 100%;
 155 |                                         height: 100%;
 156 |                                         margin: 0;
 157 |                                         cursor: pointer;
 158 |                                     " />
 159 |                                     <div style="
 160 |                                         position: absolute;
 161 |                                         top: 2px;
 162 |                                         left: 2px;
 163 |                                         width: 20px;
 164 |                                         height: 20px;
 165 |                                         background: white;
 166 |                                         border-radius: 50%;
 167 |                                         transition: transform 0.3s ease;
 168 |                                         box-shadow: 0 2px 4px rgba(0,0,0,0.2);
 169 |                                     " id="toggle-slider"></div>
 170 |                                 </div>
 171 |                                 Multi-product search
 172 |                             </label>
 173 |                             <div style="
 174 |                                 font-size: 12px;
 175 |                                 color: #6c757d;
 176 |                                 font-style: italic;
 177 |                             ">Search multiple products at once</div>
 178 |                         </div>
 179 |                         
 180 |                         <!-- Single product input -->
 181 |                         <div id="single-product-input" style="display: flex; gap: 12px; margin-bottom: 12px;">
 182 |                             <input type="text" id="search-query" placeholder="Search query (e.g., iPhone 17, Nike shoes, Pets Deli Hundefutter)" style="
 183 |                                 flex: 1;
 184 |                                 padding: 8px 12px;
 185 |                                 border: 1px solid #dee2e6;
 186 |                                 border-radius: 4px;
 187 |                                 font-size: 14px;
 188 |                                 box-sizing: border-box;
 189 |                             " />
 190 |                             <button id="search-btn" style="
 191 |                                 background: #007bff;
 192 |                                 color: white;
 193 |                                 border: none;
 194 |                                 padding: 8px 16px;
 195 |                                 border-radius: 4px;
 196 |                                 font-size: 14px;
 197 |                                 font-weight: 500;
 198 |                                 cursor: pointer;
 199 |                                 white-space: nowrap;
 200 |                             ">Search</button>
 201 |                         </div>
 202 |                         
 203 |                         <!-- Multi product input -->
 204 |                         <div id="multi-product-input" style="display: none; margin-bottom: 12px;">
 205 |                             <textarea id="multi-search-query" placeholder="Enter product names, one per line:&#10;iPhone 17 Pro&#10;Samsung Galaxy S25&#10;Google Pixel 9" style="
 206 |                                 width: 100%;
 207 |                                 min-height: 100px;
 208 |                                 padding: 8px 12px;
 209 |                                 border: 1px solid #dee2e6;
 210 |                                 border-radius: 4px;
 211 |                                 font-size: 14px;
 212 |                                 box-sizing: border-box;
 213 |                                 resize: vertical;
 214 |                                 font-family: inherit;
 215 |                                 margin-bottom: 8px;
 216 |                             "></textarea>
 217 |                             <div style="display: flex; gap: 12px;">
 218 |                                 <button id="multi-search-btn" style="
 219 |                                     background: #007bff;
 220 |                                     color: white;
 221 |                                     border: none;
 222 |                                     padding: 8px 16px;
 223 |                                     border-radius: 4px;
 224 |                                     font-size: 14px;
 225 |                                     font-weight: 500;
 226 |                                     cursor: pointer;
 227 |                                     white-space: nowrap;
 228 |                                 ">Search All Products</button>
 229 |                                 <div style="
 230 |                                     font-size: 12px;
 231 |                                     color: #6c757d;
 232 |                                     align-self: center;
 233 |                                     font-style: italic;
 234 |                                 ">Results will be shown in a table format</div>
 235 |                             </div>
 236 |                         </div>
 237 |                         </div> <!-- End search-controls -->
 238 |                         
 239 |                         <!-- Hidden token field for status display -->
 240 |                         <input type="password" id="auth-token" placeholder="Token will be fetched automatically" readonly style="
 241 |                             display: none;
 242 |                             padding: 8px 12px;
 243 |                             border: 1px solid #dee2e6;
 244 |                             border-radius: 4px;
 245 |                             font-size: 14px;
 246 |                             box-sizing: border-box;
 247 |                             background-color: #f9f9f9;
 248 |                             cursor: not-allowed;
 249 |                         " />
 250 |                     </div>
 251 |                     
 252 |                     <div id="results-container" style="
 253 |                         flex: 1;
 254 |                         overflow-y: auto;
 255 |                         padding: 20px;
 256 |                     ">
 257 |                         <div id="welcome-state" style="
 258 |                             text-align: center; 
 259 |                             padding: 60px 40px; 
 260 |                             color: #6c757d;
 261 |                             display: flex;
 262 |                             flex-direction: column;
 263 |                             align-items: center;
 264 |                             justify-content: center;
 265 |                             height: 100%;
 266 |                             min-height: 300px;
 267 |                         ">
 268 |                             <div style="
 269 |                                 font-size: 48px;
 270 |                                 margin-bottom: 20px;
 271 |                                 opacity: 0.7;
 272 |                             ">🔍</div>
 273 |                             <h3 style="
 274 |                                 margin: 0 0 12px 0;
 275 |                                 font-size: 20px;
 276 |                                 font-weight: 600;
 277 |                                 color: #495057;
 278 |                             ">Product Search</h3>
 279 |                             <p style="
 280 |                                 margin: 0 0 24px 0;
 281 |                                 font-size: 16px;
 282 |                                 line-height: 1.5;
 283 |                                 max-width: 400px;
 284 |                             ">Search for product reviews, comparisons, and detailed information from across the web</p>
 285 |                             <div style="
 286 |                                 padding: 16px 20px;
 287 |                                 border-left: 4px solid #007bff;
 288 |                                 max-width: 500px;
 289 |                                 text-align: left;
 290 |                             ">
 291 |                                 <div style="font-weight: 600; margin-bottom: 8px; color: #495057;">Try searching for:</div>
 292 |                                 <div style="color: #6c757d; font-size: 14px; line-height: 1.4;">
 293 |                                     • "iPhone 17 Pro camera quality"<br>
 294 |                                     • "Nike Air Max running shoes"<br>
 295 |                                     • "MacBook Air M3 performance"<br>
 296 |                                     • "Tesla Model 3 reviews"
 297 |                                 </div>
 298 |                             </div>
 299 |                             <div id="auth-status" style="
 300 |                                 margin-top: 20px;
 301 |                                 padding: 8px 16px;
 302 |                                 border-radius: 20px;
 303 |                                 font-size: 13px;
 304 |                                 font-weight: 500;
 305 |                                 background: #e3f2fd;
 306 |                                 color: #1565c0;
 307 |                                 border: 1px solid #bbdefb;
 308 |                             ">🔐 Checking authentication...</div>
 309 |                         </div>
 310 |                     </div>
 311 |                 </div>
 312 |                 
 313 |                 <!-- Fixed Footer -->
 314 |                 <div style="
 315 |                     position: fixed;
 316 |                     bottom: 0;
 317 |                     left: 0;
 318 |                     right: 0;
 319 |                     background: #f8f9fa;
 320 |                     border-top: 1px solid #e9ecef;
 321 |                     padding: 8px 0;
 322 |                     text-align: center;
 323 |                     font-size: 14px;
 324 |                     z-index: 10001;
 325 |                 ">
 326 |                     Created by <a href="https://www.martinaberastegue.com/" target="_blank" rel="noopener noreferrer">Martin Aberastegue (@Xyborg)</a>
 327 |                 </div>
 328 |             </div>
 329 |         </div>
 330 |     `;
 331 | 
 332 |     // Function to create and show modal
 333 |     function createModal() {
 334 |         // Remove existing modal if present
 335 |         const existingModal = document.getElementById('chatgpt-product-search-modal');
 336 |         if (existingModal) {
 337 |             existingModal.remove();
 338 |         }
 339 | 
 340 |         // Inject modal into page
 341 |         document.body.insertAdjacentHTML('beforeend', modalHTML);
 342 | 
 343 |         // Get modal elements
 344 |         const modal = document.getElementById('chatgpt-product-search-modal');
 345 |         const closeBtn = document.getElementById('close-modal-btn');
 346 |         const searchBtn = document.getElementById('search-btn');
 347 |         const multiSearchBtn = document.getElementById('multi-search-btn');
 348 |         const searchQuery = document.getElementById('search-query');
 349 |         const multiSearchQuery = document.getElementById('multi-search-query');
 350 |         const authToken = document.getElementById('auth-token');
 351 |         const resultsContainer = document.getElementById('results-container');
 352 |         const multiProductToggle = document.getElementById('multi-product-toggle');
 353 |         const toggleBackground = document.getElementById('toggle-background');
 354 |         const toggleSlider = document.getElementById('toggle-slider');
 355 |         const singleProductInput = document.getElementById('single-product-input');
 356 |         const multiProductInput = document.getElementById('multi-product-input');
 357 |         const collapseToggle = document.getElementById('collapse-toggle');
 358 |         const collapseText = document.getElementById('collapse-text');
 359 |         const searchControls = document.getElementById('search-controls');
 360 | 
 361 |         // Close modal functionality
 362 |         closeBtn.addEventListener('click', () => {
 363 |             modal.remove();
 364 |         });
 365 | 
 366 |         // Close on outside click
 367 |         modal.addEventListener('click', (e) => {
 368 |             if (e.target === modal) {
 369 |                 modal.remove();
 370 |             }
 371 |         });
 372 | 
 373 |         // Close on Escape key
 374 |         document.addEventListener('keydown', (e) => {
 375 |             if (e.key === 'Escape') {
 376 |                 modal.remove();
 377 |             }
 378 |         });
 379 | 
 380 |         // Toggle functionality
 381 |         multiProductToggle.addEventListener('change', () => {
 382 |             const isMultiMode = multiProductToggle.checked;
 383 |             
 384 |             if (isMultiMode) {
 385 |                 // Switch to multi-product mode
 386 |                 singleProductInput.style.display = 'none';
 387 |                 multiProductInput.style.display = 'block';
 388 |                 toggleBackground.style.background = '#007bff';
 389 |                 toggleSlider.style.transform = 'translateX(20px)';
 390 |             } else {
 391 |                 // Switch to single-product mode
 392 |                 singleProductInput.style.display = 'flex';
 393 |                 multiProductInput.style.display = 'none';
 394 |                 toggleBackground.style.background = '#dee2e6';
 395 |                 toggleSlider.style.transform = 'translateX(0px)';
 396 |             }
 397 |         });
 398 | 
 399 |         // Collapse/Expand functionality
 400 |         collapseToggle.addEventListener('click', () => {
 401 |             const isCollapsed = searchControls.style.display === 'none';
 402 |             
 403 |             if (isCollapsed) {
 404 |                 // Expand
 405 |                 searchControls.style.display = 'block';
 406 |                 collapseText.textContent = '▲ Hide';
 407 |                 collapseToggle.style.background = 'rgba(0, 123, 255, 0.1)';
 408 |                 collapseToggle.style.border = '1px solid rgba(0, 123, 255, 0.2)';
 409 |                 collapseToggle.style.color = '#007bff';
 410 |             } else {
 411 |                 // Collapse
 412 |                 searchControls.style.display = 'none';
 413 |                 collapseText.textContent = '▼ Show';
 414 |                 collapseToggle.style.background = 'rgba(40, 167, 69, 0.1)';
 415 |                 collapseToggle.style.border = '1px solid rgba(40, 167, 69, 0.2)';
 416 |                 collapseToggle.style.color = '#28a745';
 417 |             }
 418 |         });
 419 | 
 420 |         // Add hover effects to collapse toggle
 421 |         collapseToggle.addEventListener('mouseenter', () => {
 422 |             const isCollapsed = searchControls.style.display === 'none';
 423 |             if (isCollapsed) {
 424 |                 collapseToggle.style.background = 'rgba(40, 167, 69, 0.2)';
 425 |                 collapseToggle.style.transform = 'scale(1.05)';
 426 |             } else {
 427 |                 collapseToggle.style.background = 'rgba(0, 123, 255, 0.2)';
 428 |                 collapseToggle.style.transform = 'scale(1.05)';
 429 |             }
 430 |         });
 431 | 
 432 |         collapseToggle.addEventListener('mouseleave', () => {
 433 |             const isCollapsed = searchControls.style.display === 'none';
 434 |             if (isCollapsed) {
 435 |                 collapseToggle.style.background = 'rgba(40, 167, 69, 0.1)';
 436 |             } else {
 437 |                 collapseToggle.style.background = 'rgba(0, 123, 255, 0.1)';
 438 |             }
 439 |             collapseToggle.style.transform = 'scale(1)';
 440 |         });
 441 | 
 442 |         // Search functionality
 443 |         searchBtn.addEventListener('click', performSearch);
 444 |         multiSearchBtn.addEventListener('click', performMultiSearch);
 445 | 
 446 |         // Enter key support
 447 |         searchQuery.addEventListener('keydown', (e) => {
 448 |             if (e.key === 'Enter') {
 449 |                 performSearch();
 450 |             }
 451 |         });
 452 | 
 453 |         // Initialize token status
 454 |         initializeTokenStatus();
 455 |     }
 456 | 
 457 |     // Function to show the collapse toggle after results are displayed
 458 |     function showCollapseToggle() {
 459 |         const collapseToggle = document.getElementById('collapse-toggle');
 460 |         if (collapseToggle) {
 461 |             collapseToggle.style.display = 'block';
 462 |         }
 463 |     }
 464 | 
 465 |     async function performSearch() {
 466 |         const searchQuery = document.getElementById('search-query');
 467 |         const searchBtn = document.getElementById('search-btn');
 468 |         const resultsContainer = document.getElementById('results-container');
 469 |         
 470 |         if (!searchQuery || !searchBtn || !resultsContainer) {
 471 |             alert('Modal elements not found. Please try again.');
 472 |             return;
 473 |         }
 474 |         
 475 |         const query = searchQuery.value.trim();
 476 |         
 477 |         if (!query) {
 478 |             alert('Please enter a search query');
 479 |             return;
 480 |         }
 481 |         
 482 |         // Get token automatically
 483 |         let token;
 484 |         try {
 485 |             token = await getAutomaticToken();
 486 |         } catch (error) {
 487 |             alert('Failed to get authentication token. Please make sure you\'re logged in to ChatGPT.');
 488 |             return;
 489 |         }
 490 |         
 491 |         // Show loading state
 492 |         searchBtn.disabled = true;
 493 |         searchBtn.textContent = 'Searching...';
 494 |         resultsContainer.innerHTML = `
 495 |             <div style="text-align: center; padding: 40px; color: #666;">
 496 |                 <div style="
 497 |                     display: inline-block;
 498 |                     width: 32px;
 499 |                     height: 32px;
 500 |                     border: 3px solid #f3f3f3;
 501 |                     border-top: 3px solid #667eea;
 502 |                     border-radius: 50%;
 503 |                     animation: spin 1s linear infinite;
 504 |                     margin-bottom: 10px;
 505 |                 "></div>
 506 |                 <p>Searching for "${query}"...</p>
 507 |             </div>
 508 |             <style>
 509 |                 @keyframes spin {
 510 |                     0% { transform: rotate(0deg); }
 511 |                     100% { transform: rotate(360deg); }
 512 |                 }
 513 |             </style>
 514 |         `;
 515 |         
 516 |         try {
 517 |             const result = await searchProduct(query, token);
 518 |             displayResults(result, query);
 519 |         } catch (error) {
 520 |             displayError(error.message);
 521 |         } finally {
 522 |             searchBtn.disabled = false;
 523 |             searchBtn.textContent = 'Search';
 524 |             // Show collapse toggle after results are displayed
 525 |             showCollapseToggle();
 526 |         }
 527 |     }
 528 | 
 529 |     async function performMultiSearch() {
 530 |         const multiSearchQuery = document.getElementById('multi-search-query');
 531 |         const multiSearchBtn = document.getElementById('multi-search-btn');
 532 |         const resultsContainer = document.getElementById('results-container');
 533 |         
 534 |         if (!multiSearchQuery || !multiSearchBtn || !resultsContainer) {
 535 |             alert('Modal elements not found. Please try again.');
 536 |             return;
 537 |         }
 538 |         
 539 |         const queries = multiSearchQuery.value.trim().split('\n').filter(q => q.trim());
 540 |         
 541 |         if (queries.length === 0) {
 542 |             alert('Please enter at least one product name');
 543 |             return;
 544 |         }
 545 |         
 546 |         if (queries.length > 10) {
 547 |             alert('Maximum 10 products allowed at once to avoid rate limiting');
 548 |             return;
 549 |         }
 550 |         
 551 |         // Get token automatically
 552 |         let token;
 553 |         try {
 554 |             token = await getAutomaticToken();
 555 |         } catch (error) {
 556 |             alert('Failed to get authentication token. Please make sure you\'re logged in to ChatGPT.');
 557 |             return;
 558 |         }
 559 |         
 560 |         // Show loading state
 561 |         multiSearchBtn.disabled = true;
 562 |         multiSearchBtn.textContent = 'Searching...';
 563 |         resultsContainer.innerHTML = `
 564 |             <div style="text-align: center; padding: 40px; color: #666;">
 565 |                 <div style="
 566 |                     display: inline-block;
 567 |                     width: 32px;
 568 |                     height: 32px;
 569 |                     border: 3px solid #f3f3f3;
 570 |                     border-top: 3px solid #667eea;
 571 |                     border-radius: 50%;
 572 |                     animation: spin 1s linear infinite;
 573 |                     margin-bottom: 10px;
 574 |                 "></div>
 575 |                 <p>Searching ${queries.length} products...</p>
 576 |                 <div id="progress-status" style="font-size: 14px; color: #999; margin-top: 10px;">
 577 |                     Starting searches...
 578 |                 </div>
 579 |             </div>
 580 |             <style>
 581 |                 @keyframes spin {
 582 |                     0% { transform: rotate(0deg); }
 583 |                     100% { transform: rotate(360deg); }
 584 |                 }
 585 |             </style>
 586 |         `;
 587 |         
 588 |         const results = [];
 589 |         const progressStatus = document.getElementById('progress-status');
 590 |         
 591 |         try {
 592 |             // Search products one by one
 593 |             for (let i = 0; i < queries.length; i++) {
 594 |                 const query = queries[i].trim();
 595 |                 if (progressStatus) {
 596 |                     progressStatus.textContent = `Searching "${query}" (${i + 1}/${queries.length})...`;
 597 |                 }
 598 |                 
 599 |                 try {
 600 |                     const result = await searchProduct(query, token);
 601 |                     results.push({
 602 |                         query: query,
 603 |                         success: true,
 604 |                         data: result
 605 |                     });
 606 |                 } catch (error) {
 607 |                     results.push({
 608 |                         query: query,
 609 |                         success: false,
 610 |                         error: error.message
 611 |                     });
 612 |                 }
 613 |                 
 614 |                 // Add a small delay between requests to be respectful
 615 |                 if (i < queries.length - 1) {
 616 |                     await new Promise(resolve => setTimeout(resolve, 1000));
 617 |                 }
 618 |             }
 619 |             
 620 |             displayMultiResults(results);
 621 |         } catch (error) {
 622 |             displayError(error.message);
 623 |         } finally {
 624 |             multiSearchBtn.disabled = false;
 625 |             multiSearchBtn.textContent = 'Search All Products';
 626 |             // Show collapse toggle after results are displayed
 627 |             showCollapseToggle();
 628 |         }
 629 |     }
 630 | 
 631 |     async function getAutomaticToken() {
 632 |         try {
 633 |             const response = await fetch("/api/auth/session");
 634 |             if (!response.ok) {
 635 |                 throw new Error(`Session API responded with: ${response.status} ${response.statusText}`);
 636 |             }
 637 |             const sessionData = await response.json();
 638 |             
 639 |             if (!sessionData.accessToken) {
 640 |                 throw new Error("No access token found in session. Please make sure you're logged in to ChatGPT.");
 641 |             }
 642 |             
 643 |             // Update the token input field to show it's been fetched
 644 |             const tokenInput = document.getElementById('auth-token');
 645 |             if (tokenInput) {
 646 |                 tokenInput.placeholder = "✅ Token fetched from session";
 647 |                 tokenInput.style.backgroundColor = "#f0f8ff";
 648 |                 tokenInput.style.borderColor = "#007bff";
 649 |             }
 650 |             
 651 |             return sessionData.accessToken;
 652 |         } catch (error) {
 653 |             // Update the token input field to show error
 654 |             const tokenInput = document.getElementById('auth-token');
 655 |             if (tokenInput) {
 656 |                 tokenInput.placeholder = "❌ Failed to fetch token automatically";
 657 |                 tokenInput.style.backgroundColor = "#fff5f5";
 658 |                 tokenInput.style.borderColor = "#ef4444";
 659 |                 tokenInput.readOnly = false;
 660 |                 tokenInput.style.cursor = "text";
 661 |             }
 662 |             
 663 |             throw error;
 664 |         }
 665 |     }
 666 | 
 667 |     async function searchProduct(query, token) {
 668 |         const requestBody = {
 669 |             "conversation_id": "",
 670 |             "is_client_thread": true,
 671 |             "message_id": "",
 672 |             "product_query": query,
 673 |             "supported_encodings": ["v1"],
 674 |             "product_lookup_key": {
 675 |                 "data": JSON.stringify({
 676 |                     "request_query": query,
 677 |                     "all_ids": {"p2": [""]},
 678 |                     "known_ids": {},
 679 |                     "metadata_sources": ["p1", "p3"],
 680 |                     "variant_sources": null,
 681 |                     "last_variant_group_types": null,
 682 |                     "merchant_hints": [],
 683 |                     "provider_title": query
 684 |                 }),
 685 |                 "version": "1",
 686 |                 "variant_options_query": null
 687 |             }
 688 |         };
 689 | 
 690 |         const response = await fetch("https://chatgpt.com/backend-api/search/product_info", {
 691 |             "headers": {
 692 |                 "accept": "text/event-stream",
 693 |                 "accept-language": "en-GB,en-US;q=0.9,en;q=0.8,es-AR;q=0.7,es;q=0.6,de;q=0.5,it;q=0.4,zh-CN;q=0.3,zh;q=0.2,id;q=0.1,pt-BR;q=0.1,pt;q=0.1,fr;q=0.1,tr;q=0.1,pl;q=0.1,sv;q=0.1,ru;q=0.1,ar;q=0.1,el;q=0.1",
 694 |                 "authorization": "Bearer " + token,
 695 |                 "content-type": "application/json",
 696 |                 "oai-client-version": "prod-43c98f917bf2c3e3a36183e9548cd048e4e40615",
 697 |                 "oai-device-id": generateDeviceId(),
 698 |                 "oai-language": "en-US",
 699 |                 "priority": "u=1, i",
 700 |                 "sec-ch-ua": '"Opera";v="120", "Not-A.Brand";v="8", "Chromium";v="135"',
 701 |                 "sec-ch-ua-arch": '"arm"',
 702 |                 "sec-ch-ua-bitness": '"64"',
 703 |                 "sec-ch-ua-full-version": '"120.0.5543.161"',
 704 |                 "sec-ch-ua-full-version-list": '"Opera";v="120.0.5543.161", "Not-A.Brand";v="8.0.0.0", "Chromium";v="135.0.7049.115"',
 705 |                 "sec-ch-ua-mobile": "?0",
 706 |                 "sec-ch-ua-model": '""',
 707 |                 "sec-ch-ua-platform": '"macOS"',
 708 |                 "sec-ch-ua-platform-version": '"15.5.0"',
 709 |                 "sec-fetch-dest": "empty",
 710 |                 "sec-fetch-mode": "cors",
 711 |                 "sec-fetch-site": "same-origin"
 712 |             },
 713 |             "referrer": window.location.href,
 714 |             "referrerPolicy": "strict-origin-when-cross-origin",
 715 |             "body": JSON.stringify(requestBody),
 716 |             "method": "POST",
 717 |             "mode": "cors",
 718 |             "credentials": "include"
 719 |         });
 720 | 
 721 |         if (!response.ok) {
 722 |             throw new Error(`HTTP error! status: ${response.status}`);
 723 |         }
 724 | 
 725 |         const responseText = await response.text();
 726 |         return parseProductInfo(responseText);
 727 |     }
 728 | 
 729 |     function parseProductInfo(content) {
 730 |         const products = [];
 731 |         const reviews = [];
 732 |         const productLinks = []; // Store product links from grouped_citation
 733 |         const rationaleObj = { text: '' }; // Track the current rationale being built (using object for reference)
 734 |         const citations = new Map(); // Store citations by cite key
 735 |         const summaryObj = { text: '' }; // Track the current summary being built (using object for reference)
 736 |         
 737 |         const lines = content.split('\n');
 738 |         let currentEvent = null;
 739 |         let currentData = [];
 740 |         let eventCount = 0;
 741 |         
 742 |         for (let i = 0; i < lines.length; i++) {
 743 |             const line = lines[i];
 744 |             
 745 |             if (line.startsWith('event: ')) {
 746 |                 if (currentEvent && currentData.length > 0) {
 747 |                     processEvent(currentEvent, currentData.join('\n'), products, reviews, productLinks, rationaleObj, eventCount, citations, summaryObj);
 748 |                     eventCount++;
 749 |                 }
 750 |                 
 751 |                 currentEvent = line.replace('event: ', '').trim();
 752 |                 currentData = [];
 753 |             } else if (line.startsWith('data: ')) {
 754 |                 currentData.push(line.replace('data: ', ''));
 755 |             } else if (line.trim() === '') {
 756 |                 continue;
 757 |             } else {
 758 |                 currentData.push(line);
 759 |             }
 760 |         }
 761 |         
 762 |         if (currentEvent && currentData.length > 0) {
 763 |             processEvent(currentEvent, currentData.join('\n'), products, reviews, productLinks, rationaleObj, eventCount, citations, summaryObj);
 764 |             eventCount++;
 765 |         }
 766 |         
 767 |         // Map citations to reviews
 768 |         for (const review of reviews) {
 769 |             if (review.cite && citations.has(review.cite)) {
 770 |                 review.url = citations.get(review.cite).url;
 771 |             }
 772 |         }
 773 |         
 774 |         const uniqueMerchants = new Set();
 775 |         for (const product of products) {
 776 |             for (const offer of product.offers || []) {
 777 |                 if (offer.merchant_name) {
 778 |                     uniqueMerchants.add(offer.merchant_name);
 779 |                 }
 780 |             }
 781 |         }
 782 |         
 783 |         const reviewThemes = [...new Set(reviews.map(review => review.theme))];
 784 |         
 785 |         return {
 786 |             products: products,
 787 |             productLinks: productLinks, // Add detected product links
 788 |             reviews: reviews,
 789 |             rationale: rationaleObj.text || null,
 790 |             reviewSummary: summaryObj.text || null, // Add the built summary
 791 |             summary: {
 792 |                 total_products: products.length,
 793 |                 total_product_links: productLinks.length,
 794 |                 total_reviews: reviews.length,
 795 |                 unique_merchants: uniqueMerchants.size,
 796 |                 review_themes: reviewThemes
 797 |             }
 798 |         };
 799 |     }
 800 | 
 801 |     function processEvent(eventType, dataStr, products, reviews, productLinks, rationaleObj, eventIndex, citations, summaryObj) {
 802 |         if (eventType !== 'delta' || !dataStr || dataStr === '""') {
 803 |             return;
 804 |         }
 805 |         
 806 |         try {
 807 |             const data = JSON.parse(dataStr);
 808 |             
 809 |             if (typeof data !== 'object' || data === null) {
 810 |                 return;
 811 |             }
 812 |             
 813 |             // Track processed patches to avoid duplicates
 814 |             const processedPatches = new Set();
 815 |             
 816 |             // Handle direct patch objects (like your examples)
 817 |             if (data.p === '/grouped_citation' && data.o === 'replace' && data.v && data.v.url) {
 818 |                 eventIndex++;
 819 |                 const citeKey = `turn0search${eventIndex}`;
 820 |                 citations.set(citeKey, {
 821 |                     url: data.v.url,
 822 |                     title: data.v.title || ''
 823 |                 });
 824 |                 
 825 |                 // Remove any existing product link with the same URL to avoid duplicates
 826 |                 const existingLinkIndex = productLinks.findIndex(link => link.url === data.v.url);
 827 |                 
 828 |                 // Capture ALL grouped_citation objects as product links
 829 |                 const productLink = {
 830 |                     title: data.v.title || '',
 831 |                     url: data.v.url,
 832 |                     snippet: data.v.snippet || '',
 833 |                     source: extractDomainFromUrl(data.v.url)
 834 |                 };
 835 |                 
 836 |                 if (existingLinkIndex >= 0) {
 837 |                     // Update existing link with potentially better title
 838 |                     productLinks[existingLinkIndex] = productLink;
 839 |                 } else {
 840 |                     // Add new link
 841 |                     productLinks.push(productLink);
 842 |                 }
 843 |             }
 844 |             
 845 |             // Handle direct rationale patches
 846 |             if (data.p === '/rationale' && data.o === 'append' && data.v) {
 847 |                 rationaleObj.text += data.v;
 848 |             }
 849 |             
 850 |             // Handle direct summary patches
 851 |             if (data.p === '/summary' && data.o === 'append' && data.v) {
 852 |                 summaryObj.text += data.v;
 853 |             }
 854 |             
 855 |             // Capture citations from cite_map and rationale patches
 856 |             if (data.v && Array.isArray(data.v)) {
 857 |                 for (const patch of data.v) {
 858 |                     if (patch.p && patch.p.startsWith('/cite_map/') && patch.o === 'add' && patch.v && patch.v.url) {
 859 |                         const citeKey = patch.p.replace('/cite_map/', '');
 860 |                         citations.set(citeKey, {
 861 |                             url: patch.v.url,
 862 |                             title: patch.v.title || ''
 863 |                         });
 864 |                     }
 865 |                     
 866 |                     // Capture grouped_citation from data.v array (like your example!)
 867 |                     if (patch.p === '/grouped_citation' && patch.o === 'replace' && patch.v && patch.v.url) {
 868 |                         eventIndex++;
 869 |                         const citeKey = `turn0search${eventIndex}`;
 870 |                         citations.set(citeKey, {
 871 |                             url: patch.v.url,
 872 |                             title: patch.v.title || ''
 873 |                         });
 874 |                         
 875 |                         // Remove any existing product link with the same URL to avoid duplicates
 876 |                         const existingLinkIndex = productLinks.findIndex(link => link.url === patch.v.url);
 877 |                         
 878 |                         // Capture ALL grouped_citation objects as product links
 879 |                         const productLink = {
 880 |                             title: patch.v.title || '',
 881 |                             url: patch.v.url,
 882 |                             snippet: patch.v.snippet || '',
 883 |                             source: extractDomainFromUrl(patch.v.url)
 884 |                         };
 885 |                         
 886 |                         if (existingLinkIndex >= 0) {
 887 |                             // Update existing link with potentially better title
 888 |                             productLinks[existingLinkIndex] = productLink;
 889 |                         } else {
 890 |                             // Add new link
 891 |                             productLinks.push(productLink);
 892 |                         }
 893 |                     }
 894 |                     
 895 |                     // Handle individual grouped_citation property updates (like URL and title changes)
 896 |                     if (patch.p && patch.p.startsWith('/grouped_citation/') && patch.o === 'replace' && patch.v) {
 897 |                         
 898 |                         // Find or create the latest citation entry
 899 |                         let latestCiteKey = null;
 900 |                         let maxCiteNum = -1;
 901 |                         
 902 |                         // Find the highest numbered citation
 903 |                         for (const [key, value] of citations.entries()) {
 904 |                             if (key.startsWith('turn0search')) {
 905 |                                 const num = parseInt(key.replace('turn0search', ''));
 906 |                                 if (num > maxCiteNum) {
 907 |                                     maxCiteNum = num;
 908 |                                     latestCiteKey = key;
 909 |                                 }
 910 |                             }
 911 |                         }
 912 |                         
 913 |                         // If no citation exists yet, create one
 914 |                         if (!latestCiteKey) {
 915 |                             latestCiteKey = `turn0search${eventIndex}`;
 916 |                             citations.set(latestCiteKey, {});
 917 |                         }
 918 |                         
 919 |                         // Update the citation with the new property
 920 |                         const existingCitation = citations.get(latestCiteKey) || {};
 921 |                         
 922 |                         if (patch.p === '/grouped_citation/url') {
 923 |                             citations.set(latestCiteKey, {
 924 |                                 ...existingCitation,
 925 |                                 url: patch.v
 926 |                             });
 927 |                         } else if (patch.p === '/grouped_citation/title') {
 928 |                             citations.set(latestCiteKey, {
 929 |                                 ...existingCitation,
 930 |                                 title: patch.v
 931 |                             });
 932 |                         }
 933 |                         
 934 |                         // Update or add product link if we have both URL and title
 935 |                         const updatedCitation = citations.get(latestCiteKey);
 936 |                         if (updatedCitation.url) {
 937 |                             // Remove any existing product link with the same URL to avoid duplicates
 938 |                             const existingLinkIndex = productLinks.findIndex(link => link.url === updatedCitation.url);
 939 |                             
 940 |                             const productLink = {
 941 |                                 title: updatedCitation.title || 'Product Link',
 942 |                                 url: updatedCitation.url,
 943 |                                 snippet: '',
 944 |                                 source: extractDomainFromUrl(updatedCitation.url)
 945 |                             };
 946 |                             
 947 |                             if (existingLinkIndex >= 0) {
 948 |                                 // Update existing link
 949 |                                 productLinks[existingLinkIndex] = productLink;
 950 |                             } else {
 951 |                                 // Add new link
 952 |                                 productLinks.push(productLink);
 953 |                             }
 954 |                         }
 955 |                     }
 956 |                     
 957 |                     // Capture rationale patches from data.v array
 958 |                     if (patch.p === '/rationale' && patch.o === 'append' && patch.v) {
 959 |                         const patchKey = `/rationale-${patch.v}`;
 960 |                         if (!processedPatches.has(patchKey)) {
 961 |                             processedPatches.add(patchKey);
 962 |                             rationaleObj.text += patch.v;
 963 |                         }
 964 |                     }
 965 |                     
 966 |                     // Capture summary patches from data.v array
 967 |                     if (patch.p === '/summary' && patch.o === 'append' && patch.v) {
 968 |                         summaryObj.text += patch.v;
 969 |                     }
 970 |                 }
 971 |             }
 972 |             
 973 |             // Also check patch operations for citations and rationale updates
 974 |             if (data.o === 'patch' && data.v && Array.isArray(data.v)) {
 975 |                 for (const patch of data.v) {
 976 |                     if (patch.p && patch.p.startsWith('/cite_map/') && patch.o === 'add' && patch.v && patch.v.url) {
 977 |                         const citeKey = patch.p.replace('/cite_map/', '');
 978 |                         citations.set(citeKey, {
 979 |                             url: patch.v.url,
 980 |                             title: patch.v.title || ''
 981 |                         });
 982 |                     }
 983 |                     
 984 |                     if (patch.p === '/grouped_citation' && patch.o === 'replace' && patch.v && patch.v.url) {
 985 |                         // This is a citation being added/updated
 986 |                         citations.set(`turn0search${eventIndex}`, {
 987 |                             url: patch.v.url,
 988 |                             title: patch.v.title || ''
 989 |                         });
 990 |                         
 991 |                         // Remove any existing product link with the same URL to avoid duplicates
 992 |                         const existingLinkIndex = productLinks.findIndex(link => link.url === patch.v.url);
 993 |                         
 994 |                         // Capture ALL grouped_citation objects as product links
 995 |                         const productLink = {
 996 |                             title: patch.v.title || '',
 997 |                             url: patch.v.url,
 998 |                             snippet: patch.v.snippet || '',
 999 |                             source: extractDomainFromUrl(patch.v.url)
1000 |                         };
1001 |                         
1002 |                         if (existingLinkIndex >= 0) {
1003 |                             // Update existing link with potentially better title
1004 |                             productLinks[existingLinkIndex] = productLink;
1005 |                         } else {
1006 |                             // Add new link
1007 |                             productLinks.push(productLink);
1008 |                         }
1009 |                     }
1010 |                     
1011 |                     // Capture rationale patches
1012 |                     if (patch.p === '/rationale' && patch.o === 'append' && patch.v) {
1013 |                         const patchKey = `/rationale-${patch.v}`;
1014 |                         if (!processedPatches.has(patchKey)) {
1015 |                             processedPatches.add(patchKey);
1016 |                             rationaleObj.text += patch.v;
1017 |                         }
1018 |                     }
1019 |                     
1020 |                     // Capture summary patches
1021 |                     if (patch.p === '/summary' && patch.o === 'append' && patch.v) {
1022 |                         summaryObj.text += patch.v;
1023 |                     }
1024 |                 }
1025 |             }
1026 |             
1027 |             if (data.v && typeof data.v === 'object' && !Array.isArray(data.v)) {
1028 |                 const vData = data.v;
1029 |                 
1030 |                 if (vData.type === 'product_entity' && vData.product) {
1031 |                     const product = vData.product;
1032 |                     
1033 |                     const productInfo = {
1034 |                         title: product.title || '',
1035 |                         price: product.price || '',
1036 |                         url: product.url || '',
1037 |                         merchants: product.merchants || '',
1038 |                         description: product.description || null,
1039 |                         rating: product.rating || null,
1040 |                         num_reviews: product.num_reviews || null,
1041 |                         featured_tag: product.featured_tag || null,
1042 |                         image_urls: product.image_urls || [],
1043 |                         offers: []
1044 |                     };
1045 |                     
1046 |                     if (product.offers) {
1047 |                         for (const offerData of product.offers) {
1048 |                             const offer = {
1049 |                                 merchant_name: offerData.merchant_name || '',
1050 |                                 product_name: offerData.product_name || '',
1051 |                                 url: offerData.url || '',
1052 |                                 price: offerData.price || '',
1053 |                                 details: offerData.details || null,
1054 |                                 available: offerData.available !== false,
1055 |                                 tag: offerData.tag?.text || null
1056 |                             };
1057 |                             productInfo.offers.push(offer);
1058 |                         }
1059 |                     }
1060 |                     
1061 |                     productInfo.variants = [];
1062 |                     if (product.variants) {
1063 |                         for (const variant of product.variants) {
1064 |                             const selectedOption = variant.options?.find(opt => opt.selected)?.label || null;
1065 |                             productInfo.variants.push({
1066 |                                 type: variant.label || '',
1067 |                                 selected: selectedOption
1068 |                             });
1069 |                         }
1070 |                     }
1071 |                     
1072 |                     products.push(productInfo);
1073 |                 }
1074 |                 
1075 |                 else if (vData.type === 'product_reviews') {
1076 |                     // Initialize the current summary from the product_reviews object
1077 |                     if (vData.summary) {
1078 |                         summaryObj.text = vData.summary;
1079 |                     }
1080 |                     
1081 |                     const reviewList = vData.reviews || [];
1082 |                     for (const reviewData of reviewList) {
1083 |                         const review = {
1084 |                             source: reviewData.source || '',
1085 |                             theme: reviewData.theme || '',
1086 |                             summary: reviewData.summary || '',
1087 |                             sentiment: reviewData.sentiment || '',
1088 |                             rating: reviewData.rating || null,
1089 |                             num_reviews: reviewData.num_reviews || null,
1090 |                             cite: reviewData.cite || `turn0search${eventIndex}`,
1091 |                             url: null // Will be populated later from citations
1092 |                         };
1093 |                         reviews.push(review);
1094 |                     }
1095 |                 }
1096 |                 
1097 |                 else if (vData.type === 'product_rationale') {
1098 |                     const rationale = vData.rationale || '';
1099 |                     if (rationale) {
1100 |                         // Initialize rationale - set the initial text
1101 |                         rationaleObj.text = rationale;
1102 |                     }
1103 |                 }
1104 |             }
1105 |             
1106 |             else if (data.o === 'add' && data.v) {
1107 |                 const vData = data.v;
1108 |                 if (typeof vData === 'object' && vData.type === 'product_reviews') {
1109 |                     const reviewList = vData.reviews || [];
1110 |                     for (const reviewData of reviewList) {
1111 |                         if (typeof reviewData === 'object') {
1112 |                             const review = {
1113 |                                 source: reviewData.source || '',
1114 |                                 theme: reviewData.theme || '',
1115 |                                 summary: reviewData.summary || '',
1116 |                                 sentiment: reviewData.sentiment || '',
1117 |                                 rating: reviewData.rating || null,
1118 |                                 num_reviews: reviewData.num_reviews || null,
1119 |                                 cite: reviewData.cite || `turn0search${eventIndex}`,
1120 |                                 url: null // Will be populated later from citations
1121 |                             };
1122 |                             reviews.push(review);
1123 |                         }
1124 |                     }
1125 |                 }
1126 |             }
1127 |             
1128 |             else if (data.o === 'patch' && data.v) {
1129 |                 const vData = data.v;
1130 |                 if (Array.isArray(vData)) {
1131 |                     for (const item of vData) {
1132 |                         if (typeof item === 'object' && item.p === '/reviews' && item.o === 'append') {
1133 |                             const reviewList = item.v || [];
1134 |                             if (Array.isArray(reviewList)) {
1135 |                                 for (const reviewData of reviewList) {
1136 |                                     if (typeof reviewData === 'object') {
1137 |                                         const review = {
1138 |                                             source: reviewData.source || '',
1139 |                                             theme: reviewData.theme || '',
1140 |                                             summary: reviewData.summary || '',
1141 |                                             sentiment: reviewData.sentiment || '',
1142 |                                             rating: reviewData.rating || null,
1143 |                                             num_reviews: reviewData.num_reviews || null,
1144 |                                             cite: reviewData.cite || `turn0search${eventIndex}`,
1145 |                                             url: null // Will be populated later from citations
1146 |                                         };
1147 |                                         reviews.push(review);
1148 |                                     }
1149 |                                 }
1150 |                             }
1151 |                         }
1152 |                     }
1153 |                 }
1154 |             }
1155 |             
1156 |             if (data.v && Array.isArray(data.v)) {
1157 |                 for (const item of data.v) {
1158 |                     if (typeof item === 'object' && item.p === '/reviews' && item.o === 'append') {
1159 |                         const reviewList = item.v || [];
1160 |                         if (Array.isArray(reviewList)) {
1161 |                             for (const reviewData of reviewList) {
1162 |                                 if (typeof reviewData === 'object') {
1163 |                                 const review = {
1164 |                                     source: reviewData.source || '',
1165 |                                     theme: reviewData.theme || '',
1166 |                                     summary: reviewData.summary || '',
1167 |                                     sentiment: reviewData.sentiment || '',
1168 |                                     rating: reviewData.rating || null,
1169 |                                     num_reviews: reviewData.num_reviews || null,
1170 |                                     cite: reviewData.cite || `turn0search${eventIndex}`,
1171 |                                     url: null // Will be populated later from citations
1172 |                                 };
1173 |                                 reviews.push(review);
1174 |                                 }
1175 |                             }
1176 |                         }
1177 |                     }
1178 |                 }
1179 |             }
1180 |             
1181 |         } catch (jsonError) {
1182 |             return;
1183 |         }
1184 |     }
1185 | 
1186 |     function generateDeviceId() {
1187 |         return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
1188 |             const r = Math.random() * 16 | 0;
1189 |             const v = c == 'x' ? r : (r & 0x3 | 0x8);
1190 |             return v.toString(16);
1191 |         });
1192 |     }
1193 | 
1194 |     function extractDomainFromUrl(url) {
1195 |         try {
1196 |             const urlObj = new URL(url);
1197 |             return urlObj.hostname.replace('www.', '');
1198 |         } catch (e) {
1199 |             return url.split('/')[2] || url;
1200 |         }
1201 |     }
1202 | 
1203 |     function displayResults(data, query) {
1204 |         const resultsContainer = document.getElementById('results-container');
1205 |         if (!resultsContainer) {
1206 |             console.error('Results container not found');
1207 |             return;
1208 |         }
1209 |         
1210 |         if (!data || (!data.reviews.length && !data.products.length && !data.productLinks.length)) {
1211 |             resultsContainer.innerHTML = `
1212 |                 <div style="
1213 |                     background: #fef2f2;
1214 |                     color: #991b1b;
1215 |                     padding: 15px;
1216 |                     border-radius: 8px;
1217 |                     border-left: 4px solid #ef4444;
1218 |                     margin: 20px 0;
1219 |                 ">
1220 |                     <h3>No results found</h3>
1221 |                     <p>No products or reviews were found for "${query}". Try a different search term.</p>
1222 |                 </div>
1223 |             `;
1224 |             return;
1225 |         }
1226 |         
1227 |         let html = `
1228 |             <div style="
1229 |                 padding: 12px;
1230 |                 margin-bottom: 16px;
1231 |                 border-bottom: 1px solid #e9ecef;
1232 |             ">
1233 |                 <div style="
1234 |                     font-size: 16px;
1235 |                     font-weight: 600;
1236 |                     color: #495057;
1237 |                     margin-bottom: 8px;
1238 |                 ">Results for "${query}"</div>
1239 |                 <div style="display: flex; gap: 16px; font-size: 14px; color: #6c757d;">
1240 |                     <span>${data.summary.total_reviews} reviews</span>
1241 |                     <span>${data.summary.total_products} products</span>
1242 |                     <span>${data.summary.total_product_links} citation links</span>
1243 |                     <span>${data.summary.review_themes.length} themes</span>
1244 |                 </div>
1245 |             </div>
1246 |         `;
1247 | 
1248 |         // Show message when we have links but no reviews or products
1249 |         if (data.productLinks.length > 0 && data.reviews.length === 0 && data.products.length === 0) {
1250 |             html += `
1251 |                 <div style="margin-bottom: 20px;">
1252 |                     <div style="
1253 |                         background: #fff3cd;
1254 |                         border: 1px solid #ffeaa7;
1255 |                         border-radius: 6px;
1256 |                         padding: 12px;
1257 |                         margin: 0 12px;
1258 |                         color: #856404;
1259 |                         font-size: 13px;
1260 |                     ">
1261 |                         <div style="font-weight: 600; margin-bottom: 4px;">⚠️ Limited Information Available</div>
1262 |                         <div>ChatGPT found ${data.productLinks.length} relevant link(s) for this product, but no detailed reviews or product information were retrieved. You can check the citation links below for more information.</div>
1263 |                     </div>
1264 |                 </div>
1265 |             `;
1266 |         }
1267 |         
1268 |         if (data.rationale && data.rationale.trim()) {
1269 |             html += `
1270 |                 <div style="margin-bottom: 20px;">
1271 |                     <div style="
1272 |                         font-size: 14px;
1273 |                         font-weight: 600;
1274 |                         color: #495057;
1275 |                         margin-bottom: 8px;
1276 |                         padding: 0 12px;
1277 |                     ">Product Overview</div>
1278 |                     <div style="
1279 |                         background: #e2f1ff;
1280 |                         padding: 12px;
1281 |                         margin: 0 12px;
1282 |                         color: #000;
1283 |                         line-height: 1.4;
1284 |                         font-size: 13px;
1285 |                         border-left: 3px solid #3a6797;
1286 |                     ">${data.rationale}</div>
1287 |                 </div>
1288 |             `;
1289 |         }
1290 | 
1291 |         if (data.reviewSummary && data.reviewSummary.trim()) {
1292 |             html += `
1293 |                 <div style="margin-bottom: 20px;">
1294 |                     <div style="
1295 |                         font-size: 14px;
1296 |                         font-weight: 600;
1297 |                         color: #495057;
1298 |                         margin-bottom: 8px;
1299 |                         padding: 0 12px;
1300 |                     ">Review Summary</div>
1301 |                     <div style="
1302 |                         background: #f5fee8;
1303 |                         padding: 12px;
1304 |                         margin: 0 12px;
1305 |                         color: #000;
1306 |                         line-height: 1.4;
1307 |                         font-size: 13px;
1308 |                         border-left: 3px solid #93ac71;
1309 |                     ">${data.reviewSummary}</div>
1310 |                 </div>
1311 |             `;
1312 |         }
1313 | 
1314 |         if (data.productLinks && data.productLinks.length > 0) {
1315 |             html += `
1316 |                 <div style="margin-bottom: 20px;">
1317 |                     <div style="
1318 |                         font-size: 14px;
1319 |                         font-weight: 600;
1320 |                         color: #495057;
1321 |                         margin-bottom: 8px;
1322 |                         padding: 0 12px;
1323 |                     ">Citation Links</div>
1324 |                     ${data.productLinks.map(link => `
1325 |                         <div style="
1326 |                             border: 1px solid #e9ecef;
1327 |                             border-radius: 6px;
1328 |                             padding: 12px;
1329 |                             margin: 0 12px 8px 12px;
1330 |                             background: #f8f9fa;
1331 |                         ">
1332 |                             <div style="
1333 |                                 display: flex;
1334 |                                 align-items: center;
1335 |                                 margin-bottom: 6px;
1336 |                                 gap: 8px;
1337 |                             ">
1338 |                                 <img src="https://t1.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodeURIComponent(link.url)}&size=128" alt="${link.source}" style="
1339 |                                     width: 16px;
1340 |                                     height: 16px;
1341 |                                     border-radius: 2px;
1342 |                                     flex-shrink: 0;
1343 |                                 " onerror="this.style.display='none'">
1344 |                                 <span style="
1345 |                                     font-weight: 600;
1346 |                                     color: #007bff;
1347 |                                     font-size: 14px;
1348 |                                 ">${link.title}</span>
1349 |                                 <a href="${link.url}" target="_blank" style="
1350 |                                     color: #28a745;
1351 |                                     text-decoration: none;
1352 |                                     font-size: 14px;
1353 |                                     margin-left: auto;
1354 |                                     background: #d4edda;
1355 |                                     padding: 4px 8px;
1356 |                                     border-radius: 4px;
1357 |                                     border: 1px solid #c3e6cb;
1358 |                                 " title="Visit citation page">↗</a>
1359 |                             </div>
1360 |                             ${link.snippet ? `<div style="
1361 |                                 color: #6c757d;
1362 |                                 font-size: 12px;
1363 |                                 line-height: 1.3;
1364 |                                 margin-top: 4px;
1365 |                             ">${link.snippet}</div>` : ''}
1366 |                         </div>
1367 |                     `).join('')}
1368 |                 </div>
1369 |             `;
1370 |         }
1371 | 
1372 |         if (data.reviews.length > 0) {
1373 |             // Calculate sentiment distribution
1374 |             const sentimentCounts = data.reviews.reduce((acc, review) => {
1375 |                 acc[review.sentiment] = (acc[review.sentiment] || 0) + 1;
1376 |                 return acc;
1377 |             }, {});
1378 |             
1379 |             const totalReviews = data.reviews.length;
1380 |             const positiveCount = sentimentCounts.positive || 0;
1381 |             const neutralCount = sentimentCounts.neutral || 0;
1382 |             const negativeCount = sentimentCounts.negative || 0;
1383 |             
1384 |             const positivePercent = Math.round((positiveCount / totalReviews) * 100);
1385 |             const neutralPercent = Math.round((neutralCount / totalReviews) * 100);
1386 |             const negativePercent = Math.round((negativeCount / totalReviews) * 100);
1387 |             
1388 |             html += `
1389 |                 <div>
1390 |                     <div style="
1391 |                         display: flex;
1392 |                         align-items: center;
1393 |                         justify-content: space-between;
1394 |                         margin-bottom: 12px;
1395 |                         padding: 0 12px;
1396 |                     ">
1397 |                         <div style="
1398 |                             font-size: 14px;
1399 |                             font-weight: 600;
1400 |                             color: #495057;
1401 |                         ">Reviews</div>
1402 |                         <div style="display: flex; align-items: center; gap: 12px;">
1403 |                             <div style="
1404 |                                 display: flex;
1405 |                                 background: #f8f9fa;
1406 |                                 border-radius: 8px;
1407 |                                 overflow: hidden;
1408 |                                 width: 100px;
1409 |                                 height: 6px;
1410 |                                 border: 1px solid #e9ecef;
1411 |                             ">
1412 |                                 ${positiveCount > 0 ? `<div style="
1413 |                                     background: #28a745;
1414 |                                     width: ${positivePercent}%;
1415 |                                     height: 100%;
1416 |                                 " title="${positiveCount} positive (${positivePercent}%)"></div>` : ''}
1417 |                                 ${neutralCount > 0 ? `<div style="
1418 |                                     background: #ffc107;
1419 |                                     width: ${neutralPercent}%;
1420 |                                     height: 100%;
1421 |                                 " title="${neutralCount} neutral (${neutralPercent}%)"></div>` : ''}
1422 |                                 ${negativeCount > 0 ? `<div style="
1423 |                                     background: #dc3545;
1424 |                                     width: ${negativePercent}%;
1425 |                                     height: 100%;
1426 |                                 " title="${negativeCount} negative (${negativePercent}%)"></div>` : ''}
1427 |                             </div>
1428 |                             <div style="
1429 |                                 font-size: 12px;
1430 |                                 color: #6c757d;
1431 |                                 white-space: nowrap;
1432 |                             ">
1433 |                                 <span style="color: #28a745;">●</span> ${positivePercent}%
1434 |                                 ${neutralCount > 0 ? `<span style="color: #ffc107; margin-left: 6px;">●</span> ${neutralPercent}%` : ''}
1435 |                                 ${negativeCount > 0 ? `<span style="color: #dc3545; margin-left: 6px;">●</span> ${negativePercent}%` : ''}
1436 |                             </div>
1437 |                         </div>
1438 |                     </div>
1439 |                     ${data.reviews.map(review => {
1440 |                         const sentimentColor = review.sentiment === 'positive' ? '#28a745' : 
1441 |                                              review.sentiment === 'negative' ? '#dc3545' : '#ffc107';
1442 |                         
1443 |                         
1444 |                         return `
1445 |                             <div style="
1446 |                                 border-bottom: 1px solid #f8f9fa;
1447 |                                 padding: 12px;
1448 |                                 margin-bottom: 1px;
1449 |                             ">
1450 |                                 <div style="
1451 |                                     display: flex;
1452 |                                     align-items: center;
1453 |                                     margin-bottom: 8px;
1454 |                                     gap: 8px;
1455 |                                 ">
1456 |                                     <img src="https://t1.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodeURIComponent(review.url || `https://${review.source.toLowerCase().replace(/\s+/g, '')}.com`)}&size=128" alt="${review.source}" style="
1457 |                                         width: 16px;
1458 |                                         height: 16px;
1459 |                                         border-radius: 2px;
1460 |                                         flex-shrink: 0;
1461 |                                     " onerror="this.style.display='none'">
1462 |                                     <span style="font-weight: 500; color: #495057; font-size: 14px;">${review.source}</span>
1463 |                                     ${review.url ? `<a href="${review.url}" target="_blank" style="
1464 |                                         color: #6c757d;
1465 |                                         text-decoration: none;
1466 |                                         font-size: 12px;
1467 |                                         margin-left: auto;
1468 |                                     " title="Open source">↗</a>` : ''}
1469 |                                     <span style="
1470 |                                         width: 8px;
1471 |                                         height: 8px;
1472 |                                         border-radius: 50%;
1473 |                                         background: ${sentimentColor};
1474 |                                         display: inline-block;
1475 |                                         margin-left: ${review.url ? '4px' : 'auto'};
1476 |                                     "></span>
1477 |                                 </div>
1478 |                                 <div style="
1479 |                                     font-size: 13px;
1480 |                                     font-weight: 600;
1481 |                                     color: #007bff;
1482 |                                     margin-bottom: 6px;
1483 |                                 ">${review.theme}</div>
1484 |                                 <div style="color: #6c757d; line-height: 1.4; font-size: 13px;">${review.summary}</div>
1485 |                             </div>
1486 |                         `;
1487 |                     }).join('')}
1488 |                 </div>
1489 |             `;
1490 |         }
1491 | 
1492 |         if (data.summary.review_themes.length > 0) {
1493 |             html += `
1494 |                 <div style="margin-bottom: 20px;">
1495 |                     <div style="
1496 |                         font-size: 14px;
1497 |                         font-weight: 600;
1498 |                         color: #495057;
1499 |                         margin-bottom: 8px;
1500 |                         padding: 0 12px;
1501 |                     ">Themes</div>
1502 |                     <div style="display: flex; flex-wrap: wrap; gap: 6px; padding: 0 12px;">
1503 |                         ${data.summary.review_themes.map(theme => 
1504 |                             `<span style="
1505 |                                 background: #e9ecef;
1506 |                                 color: #495057;
1507 |                                 padding: 4px 8px;
1508 |                                 border-radius: 12px;
1509 |                                 font-size: 12px;
1510 |                             ">${theme}</span>`
1511 |                         ).join('')}
1512 |                     </div>
1513 |                 </div>
1514 |             `;
1515 |         }
1516 |         
1517 |         resultsContainer.innerHTML = html;
1518 |     }
1519 | 
1520 |     function displayMultiResults(results) {
1521 |         const resultsContainer = document.getElementById('results-container');
1522 |         if (!resultsContainer) {
1523 |             console.error('Results container not found');
1524 |             return;
1525 |         }
1526 |         
1527 |         const successfulResults = results.filter(r => r.success);
1528 |         const failedResults = results.filter(r => !r.success);
1529 |         
1530 |         let html = `
1531 |             <div style="
1532 |                 padding: 12px;
1533 |                 margin-bottom: 16px;
1534 |                 border-bottom: 1px solid #e9ecef;
1535 |             ">
1536 |                 <div style="
1537 |                     font-size: 16px;
1538 |                     font-weight: 600;
1539 |                     color: #495057;
1540 |                     margin-bottom: 8px;
1541 |                 ">Multi-Product Search Results</div>
1542 |                 <div style="display: flex; gap: 16px; font-size: 14px; color: #6c757d;">
1543 |                     <span>${successfulResults.length}/${results.length} products found</span>
1544 |                     ${failedResults.length > 0 ? `<span style="color: #dc3545;">${failedResults.length} failed</span>` : ''}
1545 |                 </div>
1546 |             </div>
1547 |         `;
1548 | 
1549 |         if (failedResults.length > 0) {
1550 |             html += `
1551 |                 <div style="margin-bottom: 20px;">
1552 |                     <div style="
1553 |                         font-size: 14px;
1554 |                         font-weight: 600;
1555 |                         color: #dc3545;
1556 |                         margin-bottom: 8px;
1557 |                         padding: 0 12px;
1558 |                     ">Search Errors</div>
1559 |                     <div style="
1560 |                         background: #f8d7da;
1561 |                         border: 1px solid #f5c6cb;
1562 |                         border-radius: 4px;
1563 |                         padding: 12px;
1564 |                         margin: 0 12px;
1565 |                         color: #721c24;
1566 |                         font-size: 13px;
1567 |                     ">
1568 |                         ${failedResults.map(r => `<div><strong>${r.query}:</strong> ${r.error}</div>`).join('<br>')}
1569 |                     </div>
1570 |                 </div>
1571 |             `;
1572 |         }
1573 | 
1574 |         if (successfulResults.length === 0) {
1575 |             html += `
1576 |                 <div style="
1577 |                     text-align: center;
1578 |                     padding: 60px 40px;
1579 |                     color: #6c757d;
1580 |                 ">
1581 |                     <div style="font-size: 48px; margin-bottom: 20px; opacity: 0.7;">❌</div>
1582 |                     <h3 style="margin: 0 0 12px 0; font-size: 20px; font-weight: 600; color: #495057;">No Results Found</h3>
1583 |                     <p style="margin: 0; font-size: 16px; line-height: 1.5;">None of the products could be found. Please try different search terms.</p>
1584 |                 </div>
1585 |             `;
1586 |             resultsContainer.innerHTML = html;
1587 |             return;
1588 |         }
1589 | 
1590 |         // Create comparison table
1591 |         html += `
1592 |             <div style="margin-bottom: 20px;">
1593 |                 <div style="
1594 |                     font-size: 14px;
1595 |                     font-weight: 600;
1596 |                     color: #495057;
1597 |                     margin-bottom: 12px;
1598 |                     padding: 0 12px;
1599 |                 ">Product Comparison Table</div>
1600 |                 
1601 |                 <div style="overflow-x: auto; margin: 0 12px;">
1602 |                     <table style="
1603 |                         width: 100%;
1604 |                         border-collapse: collapse;
1605 |                         background: white;
1606 |                         border: 1px solid #e9ecef;
1607 |                         border-radius: 6px;
1608 |                         overflow: hidden;
1609 |                         font-size: 13px;
1610 |                     ">
1611 |                         <thead>
1612 |                             <tr style="background: #f8f9fa; border-bottom: 2px solid #e9ecef;">
1613 |                                 <th style="
1614 |                                     padding: 12px 8px;
1615 |                                     text-align: left;
1616 |                                     font-weight: 600;
1617 |                                     color: #495057;
1618 |                                     border-right: 1px solid #e9ecef;
1619 |                                     min-width: 150px;
1620 |                                 ">Product</th>
1621 |                                 <th style="
1622 |                                     padding: 12px 8px;
1623 |                                     text-align: center;
1624 |                                     font-weight: 600;
1625 |                                     color: #495057;
1626 |                                     border-right: 1px solid #e9ecef;
1627 |                                     min-width: 80px;
1628 |                                 ">Reviews</th>
1629 |                                 <th style="
1630 |                                     padding: 12px 8px;
1631 |                                     text-align: center;
1632 |                                     font-weight: 600;
1633 |                                     color: #495057;
1634 |                                     border-right: 1px solid #e9ecef;
1635 |                                     min-width: 120px;
1636 |                                 ">Sentiment</th>
1637 |                                 <th style="
1638 |                                     padding: 12px 8px;
1639 |                                     text-align: left;
1640 |                                     font-weight: 600;
1641 |                                     color: #495057;
1642 |                                     border-right: 1px solid #e9ecef;
1643 |                                     min-width: 200px;
1644 |                                 ">Top Themes</th>
1645 |                                 <th style="
1646 |                                     padding: 12px 8px;
1647 |                                     text-align: center;
1648 |                                     font-weight: 600;
1649 |                                     color: #495057;
1650 |                                     min-width: 80px;
1651 |                                 ">Links</th>
1652 |                             </tr>
1653 |                         </thead>
1654 |                         <tbody>
1655 |         `;
1656 | 
1657 |         successfulResults.forEach((result, index) => {
1658 |             const data = result.data;
1659 |             
1660 |             // Calculate sentiment distribution
1661 |             const sentimentCounts = data.reviews.reduce((acc, review) => {
1662 |                 acc[review.sentiment] = (acc[review.sentiment] || 0) + 1;
1663 |                 return acc;
1664 |             }, {});
1665 |             
1666 |             const totalReviews = data.reviews.length;
1667 |             const positiveCount = sentimentCounts.positive || 0;
1668 |             const neutralCount = sentimentCounts.neutral || 0;
1669 |             const negativeCount = sentimentCounts.negative || 0;
1670 |             
1671 |             const positivePercent = totalReviews > 0 ? Math.round((positiveCount / totalReviews) * 100) : 0;
1672 |             const neutralPercent = totalReviews > 0 ? Math.round((neutralCount / totalReviews) * 100) : 0;
1673 |             const negativePercent = totalReviews > 0 ? Math.round((negativeCount / totalReviews) * 100) : 0;
1674 |             
1675 |             // Get top 3 themes
1676 |             const topThemes = data.summary.review_themes.slice(0, 3);
1677 |             
1678 |             html += `
1679 |                 <tr style="
1680 |                     border-bottom: 1px solid #f8f9fa;
1681 |                     ${index % 2 === 0 ? 'background: #fdfdfd;' : 'background: white;'}
1682 |                 ">
1683 |                     <td style="
1684 |                         padding: 12px 8px;
1685 |                         border-right: 1px solid #e9ecef;
1686 |                         vertical-align: top;
1687 |                     ">
1688 |                         <div style="
1689 |                             font-weight: 600;
1690 |                             color: #007bff;
1691 |                             margin-bottom: 4px;
1692 |                             cursor: pointer;
1693 |                         " data-product-index="${index}" class="product-name-link">${result.query}</div>
1694 |                         ${data.rationale ? `<div style="
1695 |                             color: #6c757d;
1696 |                             font-size: 11px;
1697 |                             line-height: 1.3;
1698 |                             max-height: 60px;
1699 |                             overflow: hidden;
1700 |                         ">${data.rationale.substring(0, 120)}${data.rationale.length > 120 ? '...' : ''}</div>` : ''}
1701 |                     </td>
1702 |                     <td style="
1703 |                         padding: 12px 8px;
1704 |                         text-align: center;
1705 |                         border-right: 1px solid #e9ecef;
1706 |                         vertical-align: top;
1707 |                     ">
1708 |                         <div style="font-weight: 600; color: #495057;">${totalReviews}</div>
1709 |                         <div style="font-size: 11px; color: #6c757d;">reviews</div>
1710 |                     </td>
1711 |                     <td style="
1712 |                         padding: 12px 8px;
1713 |                         text-align: center;
1714 |                         border-right: 1px solid #e9ecef;
1715 |                         vertical-align: top;
1716 |                     ">
1717 |                         ${totalReviews > 0 ? `
1718 |                             <div style="
1719 |                                 display: flex;
1720 |                                 background: #f8f9fa;
1721 |                                 border-radius: 4px;
1722 |                                 overflow: hidden;
1723 |                                 width: 60px;
1724 |                                 height: 6px;
1725 |                                 margin: 0 auto 4px auto;
1726 |                                 border: 1px solid #e9ecef;
1727 |                             ">
1728 |                                 ${positiveCount > 0 ? `<div style="
1729 |                                     background: #28a745;
1730 |                                     width: ${positivePercent}%;
1731 |                                     height: 100%;
1732 |                                 " title="${positiveCount} positive"></div>` : ''}
1733 |                                 ${neutralCount > 0 ? `<div style="
1734 |                                     background: #ffc107;
1735 |                                     width: ${neutralPercent}%;
1736 |                                     height: 100%;
1737 |                                 " title="${neutralCount} neutral"></div>` : ''}
1738 |                                 ${negativeCount > 0 ? `<div style="
1739 |                                     background: #dc3545;
1740 |                                     width: ${negativePercent}%;
1741 |                                     height: 100%;
1742 |                                 " title="${negativeCount} negative"></div>` : ''}
1743 |                             </div>
1744 |                             <div style="
1745 |                                 font-size: 10px;
1746 |                                 color: #6c757d;
1747 |                                 line-height: 1.2;
1748 |                             ">
1749 |                                 <span style="color: #28a745;">●</span>${positivePercent}%
1750 |                                 ${neutralCount > 0 ? `<br><span style="color: #ffc107;">●</span>${neutralPercent}%` : ''}
1751 |                                 ${negativeCount > 0 ? `<br><span style="color: #dc3545;">●</span>${negativePercent}%` : ''}
1752 |                             </div>
1753 |                         ` : '<span style="color: #6c757d; font-size: 11px;">No reviews</span>'}
1754 |                     </td>
1755 |                     <td style="
1756 |                         padding: 12px 8px;
1757 |                         border-right: 1px solid #e9ecef;
1758 |                         vertical-align: top;
1759 |                     ">
1760 |                         ${topThemes.length > 0 ? topThemes.map(theme => `
1761 |                             <span style="
1762 |                                 display: inline-block;
1763 |                                 background: #e9ecef;
1764 |                                 color: #495057;
1765 |                                 padding: 2px 6px;
1766 |                                 border-radius: 8px;
1767 |                                 font-size: 10px;
1768 |                                 margin: 1px 2px 1px 0;
1769 |                             ">${theme}</span>
1770 |                         `).join('') : '<span style="color: #6c757d; font-size: 11px;">No themes</span>'}
1771 |                         ${data.summary.review_themes.length > 3 ? `<span style="color: #6c757d; font-size: 10px;">+${data.summary.review_themes.length - 3} more</span>` : ''}
1772 |                     </td>
1773 |                     <td style="
1774 |                         padding: 12px 8px;
1775 |                         text-align: center;
1776 |                         vertical-align: top;
1777 |                     ">
1778 |                         <div style="font-weight: 600; color: #495057;">${data.productLinks.length}</div>
1779 |                         <div style="font-size: 11px; color: #6c757d;">links</div>
1780 |                         ${(data.reviews.length > 0 || data.products.length > 0 || data.rationale || data.reviewSummary) ? `
1781 |                             <button data-product-index="${index}" class="view-details-btn" style="
1782 |                                 background: #007bff;
1783 |                                 color: white;
1784 |                                 border: none;
1785 |                                 padding: 2px 6px;
1786 |                                 border-radius: 3px;
1787 |                                 font-size: 10px;
1788 |                                 cursor: pointer;
1789 |                                 margin-top: 4px;
1790 |                             ">View</button>
1791 |                         ` : data.productLinks.length > 0 ? `
1792 |                             <button data-product-index="${index}" class="view-details-btn" style="
1793 |                                 background: #ffc107;
1794 |                                 color: #212529;
1795 |                                 border: none;
1796 |                                 padding: 2px 6px;
1797 |                                 border-radius: 3px;
1798 |                                 font-size: 10px;
1799 |                                 cursor: pointer;
1800 |                                 margin-top: 4px;
1801 |                             ">Links</button>
1802 |                         ` : ''}
1803 |                     </td>
1804 |                 </tr>
1805 |             `;
1806 |         });
1807 | 
1808 |         html += `
1809 |                         </tbody>
1810 |                     </table>
1811 |                 </div>
1812 |             </div>
1813 |         `;
1814 | 
1815 |         // Add detailed results section (initially hidden)
1816 |         html += `
1817 |             <div id="detailed-results" style="display: none;">
1818 |                 <div style="
1819 |                     font-size: 14px;
1820 |                     font-weight: 600;
1821 |                     color: #495057;
1822 |                     margin-bottom: 12px;
1823 |                     padding: 0 12px;
1824 |                 ">Detailed Product Information</div>
1825 |                 <div id="detailed-content"></div>
1826 |             </div>
1827 |         `;
1828 | 
1829 |         resultsContainer.innerHTML = html;
1830 | 
1831 |         // Store results for detailed view
1832 |         window.multiSearchResults = successfulResults;
1833 | 
1834 |         // Add event listeners for product details links
1835 |         const productNameLinks = document.querySelectorAll('.product-name-link');
1836 |         productNameLinks.forEach(link => {
1837 |             link.addEventListener('click', function() {
1838 |                 const index = parseInt(this.getAttribute('data-product-index'));
1839 |                 showProductDetails(index);
1840 |             });
1841 |         });
1842 | 
1843 |         const viewDetailsBtns = document.querySelectorAll('.view-details-btn');
1844 |         viewDetailsBtns.forEach(btn => {
1845 |             btn.addEventListener('click', function() {
1846 |                 const index = parseInt(this.getAttribute('data-product-index'));
1847 |                 showProductDetails(index);
1848 |             });
1849 |         });
1850 |     }
1851 | 
1852 |     // Function to show detailed product information
1853 |     window.showProductDetails = function(index) {
1854 |         const detailedResults = document.getElementById('detailed-results');
1855 |         const detailedContent = document.getElementById('detailed-content');
1856 |         
1857 |         if (!window.multiSearchResults || !detailedResults || !detailedContent) {
1858 |             return;
1859 |         }
1860 | 
1861 |         const result = window.multiSearchResults[index];
1862 |         if (!result) {
1863 |             return;
1864 |         }
1865 | 
1866 |         const productName = result.query;
1867 | 
1868 |         // Display the single product result using existing function
1869 |         const tempContainer = document.createElement('div');
1870 |         tempContainer.innerHTML = '';
1871 |         
1872 |         // Use the existing displayResults function but capture its output
1873 |         const originalContainer = document.getElementById('results-container');
1874 |         const tempId = 'temp-results-container';
1875 |         tempContainer.id = tempId;
1876 |         document.body.appendChild(tempContainer);
1877 |         
1878 |         // Temporarily replace the results container
1879 |         const originalGetElementById = document.getElementById;
1880 |         document.getElementById = function(id) {
1881 |             if (id === 'results-container') {
1882 |                 return tempContainer;
1883 |             }
1884 |             return originalGetElementById.call(document, id);
1885 |         };
1886 |         
1887 |         displayResults(result.data, result.query);
1888 |         
1889 |         // Restore original function
1890 |         document.getElementById = originalGetElementById;
1891 |         
1892 |         // Move the content to detailed view
1893 |         detailedContent.innerHTML = `
1894 |             <div style="
1895 |                 background: #f8f9fa;
1896 |                 padding: 12px;
1897 |                 margin-bottom: 16px;
1898 |                 border-radius: 6px;
1899 |                 border-left: 4px solid #007bff;
1900 |             ">
1901 |                 <div style="
1902 |                     display: flex;
1903 |                     justify-content: space-between;
1904 |                     align-items: center;
1905 |                 ">
1906 |                     <h3 style="margin: 0; color: #495057;">Detailed view: ${productName}</h3>
1907 |                     <button class="close-details-btn" style="
1908 |                         background: #6c757d;
1909 |                         color: white;
1910 |                         border: none;
1911 |                         padding: 4px 8px;
1912 |                         border-radius: 3px;
1913 |                         font-size: 12px;
1914 |                         cursor: pointer;
1915 |                     ">Close</button>
1916 |                 </div>
1917 |             </div>
1918 |             ${tempContainer.innerHTML}
1919 |         `;
1920 |         
1921 |         // Clean up
1922 |         document.body.removeChild(tempContainer);
1923 |         
1924 |         // Add event listener for close button
1925 |         const closeBtn = detailedContent.querySelector('.close-details-btn');
1926 |         if (closeBtn) {
1927 |             closeBtn.addEventListener('click', function() {
1928 |                 detailedResults.style.display = 'none';
1929 |             });
1930 |         }
1931 | 
1932 |         // Show detailed results
1933 |         detailedResults.style.display = 'block';
1934 |         detailedResults.scrollIntoView({ behavior: 'smooth' });
1935 |     };
1936 | 
1937 |     function displayError(message) {
1938 |         const resultsContainer = document.getElementById('results-container');
1939 |         if (!resultsContainer) {
1940 |             console.error('Results container not found');
1941 |             alert(`Search Error: ${message}`);
1942 |             return;
1943 |         }
1944 |         
1945 |         resultsContainer.innerHTML = `
1946 |             <div style="
1947 |                 background: #fef2f2;
1948 |                 color: #991b1b;
1949 |                 padding: 15px;
1950 |                 border-radius: 8px;
1951 |                 border-left: 4px solid #ef4444;
1952 |                 margin: 20px 0;
1953 |             ">
1954 |                 <h3>Search Error</h3>
1955 |                 <p>${message}</p>
1956 |                 <p>Please check your token and try again.</p>
1957 |             </div>
1958 |         `;
1959 |     }
1960 | 
1961 |     // Initialize token status check
1962 |     async function initializeTokenStatus() {
1963 |         const tokenInput = document.getElementById('auth-token');
1964 |         const authStatus = document.getElementById('auth-status');
1965 |         
1966 |         if (!tokenInput || !authStatus) {
1967 |             console.error('Token status elements not found');
1968 |             return;
1969 |         }
1970 |         
1971 |         try {
1972 |             const response = await fetch("/api/auth/session");
1973 |             if (response.ok) {
1974 |                 const sessionData = await response.json();
1975 |                 if (sessionData.accessToken) {
1976 |                     // Update hidden token field
1977 |                     if (tokenInput) {
1978 |                         tokenInput.placeholder = "✅ Token ready - session active";
1979 |                         tokenInput.style.backgroundColor = "#f0f8ff";
1980 |                         tokenInput.style.borderColor = "#007bff";
1981 |                     }
1982 |                     
1983 |                     // Update visible auth status
1984 |                     if (authStatus) {
1985 |                         authStatus.innerHTML = "✅ Ready to search";
1986 |                         authStatus.style.background = "#d4edda";
1987 |                         authStatus.style.color = "#155724";
1988 |                         authStatus.style.borderColor = "#c3e6cb";
1989 |                     }
1990 |                     
1991 |                 } else {
1992 |                     throw new Error("No access token in session");
1993 |                 }
1994 |             } else {
1995 |                 throw new Error(`Session check failed: ${response.status}`);
1996 |             }
1997 |         } catch (error) {
1998 |             // Update hidden token field
1999 |             if (tokenInput) {
2000 |                 tokenInput.placeholder = "❌ Please log in to ChatGPT first";
2001 |                 tokenInput.style.backgroundColor = "#fff5f5";
2002 |                 tokenInput.style.borderColor = "#ef4444";
2003 |             }
2004 |             
2005 |             // Update visible auth status
2006 |             if (authStatus) {
2007 |                 authStatus.innerHTML = "❌ Please log in to ChatGPT first";
2008 |                 authStatus.style.background = "#f8d7da";
2009 |                 authStatus.style.color = "#721c24";
2010 |                 authStatus.style.borderColor = "#f5c6cb";
2011 |             }
2012 |             
2013 |         }
2014 |     }
2015 | 
2016 |     // Function to create floating button
2017 |     function createFloatingButton() {
2018 |         const button = document.createElement('button');
2019 |         button.id = 'openProductSearchModalBtn';
2020 |         button.innerHTML = '🛍️';
2021 |         button.title = 'Open ChatGPT Product Info Search';
2022 |         document.body.appendChild(button);
2023 |         return button;
2024 |     }
2025 | 
2026 |     // Add styles to the page
2027 |     const styleSheet = document.createElement("style");
2028 |     styleSheet.type = "text/css";
2029 |     styleSheet.innerText = floatingButtonCSS;
2030 |     document.head.appendChild(styleSheet);
2031 | 
2032 |     // Create floating button and add click handler
2033 |     const floatingButton = createFloatingButton();
2034 |     floatingButton.addEventListener('click', createModal);
2035 | 
2036 | })();
```
Page 4/5FirstPrevNextLast