This is page 5 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 -------------------------------------------------------------------------------- /chrome-extension/content-script.js: -------------------------------------------------------------------------------- ```javascript 1 | // ChatGPT E-commerce Product Research - Chrome Extension Content Script 2 | // Automatically injects the product search functionality into ChatGPT 3 | 4 | (function() { 5 | 'use strict'; 6 | 7 | // Only run on ChatGPT pages 8 | if (!window.location.hostname.includes('chatgpt.com')) { 9 | return; 10 | } 11 | 12 | // Wait for page to be ready 13 | if (document.readyState === 'loading') { 14 | document.addEventListener('DOMContentLoaded', initializeExtension); 15 | } else { 16 | initializeExtension(); 17 | } 18 | 19 | function initializeExtension() { 20 | // Remove existing modal and button if present 21 | const existingModal = document.getElementById('chatgpt-product-search-modal'); 22 | const existingButton = document.getElementById('openProductSearchModalBtn'); 23 | if (existingModal) { 24 | existingModal.remove(); 25 | } 26 | if (existingButton) { 27 | existingButton.remove(); 28 | } 29 | 30 | // Migrate existing search history data to new format (Phase 1) 31 | migrateSearchHistoryData(); 32 | 33 | // Resolve extension assets 34 | const settingsIconUrl = (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function') 35 | ? chrome.runtime.getURL('assets/icons-ui/settings.svg') 36 | : 'assets/icons-ui/settings.svg'; 37 | const searchIconUrl = (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function') 38 | ? chrome.runtime.getURL('assets/icons-ui/search.svg') 39 | : 'assets/icons-ui/search.svg'; 40 | const historyIconUrl = (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function') 41 | ? chrome.runtime.getURL('assets/icons-ui/history.svg') 42 | : 'assets/icons-ui/history.svg'; 43 | const analysisIconUrl = (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function') 44 | ? chrome.runtime.getURL('assets/icons-ui/analysis.svg') 45 | : 'assets/icons-ui/analysis.svg'; 46 | const projectIconUrl = (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function') 47 | ? chrome.runtime.getURL('assets/icons-ui/project.svg') 48 | : 'assets/icons-ui/project.svg'; 49 | const tagIconUrl = (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function') 50 | ? chrome.runtime.getURL('assets/icons-ui/tag.svg') 51 | : 'assets/icons-ui/tag.svg'; 52 | const databaseIconUrl = (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function') 53 | ? chrome.runtime.getURL('assets/icons-ui/database.svg') 54 | : 'assets/icons-ui/database.svg'; 55 | const editIconUrl = (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function') 56 | ? chrome.runtime.getURL('assets/icons-ui/edit.svg') 57 | : 'assets/icons-ui/edit.svg'; 58 | const positiveIconUrl = (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function') 59 | ? chrome.runtime.getURL('assets/icons-ui/positive.svg') 60 | : 'assets/icons-ui/positive.svg'; 61 | const neutralIconUrl = (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function') 62 | ? chrome.runtime.getURL('assets/icons-ui/neutral.svg') 63 | : 'assets/icons-ui/neutral.svg'; 64 | const negativeIconUrl = (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function') 65 | ? chrome.runtime.getURL('assets/icons-ui/negative.svg') 66 | : 'assets/icons-ui/negative.svg'; 67 | const checkIconUrl = (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function') 68 | ? chrome.runtime.getURL('assets/icons-ui/check.svg') 69 | : 'assets/icons-ui/check.svg'; 70 | const warningIconUrl = (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function') 71 | ? chrome.runtime.getURL('assets/icons-ui/warning.svg') 72 | : 'assets/icons-ui/warning.svg'; 73 | const errorIconUrl = (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function') 74 | ? chrome.runtime.getURL('assets/icons-ui/error.svg') 75 | : 'assets/icons-ui/error.svg'; 76 | const xIconUrl = (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function') 77 | ? chrome.runtime.getURL('assets/icons-ui/x.svg') 78 | : 'assets/icons-ui/x.svg'; 79 | const clockIconSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" aria-hidden="true"><circle cx="128" cy="128" r="96" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><polyline points="128 72 128 128 184 128" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>'; 80 | const linkedinIconUrl = (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function') 81 | ? chrome.runtime.getURL('assets/icons-ui/linkedin.svg') 82 | : 'assets/icons-ui/linkedin.svg'; 83 | const githubIconUrl = (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function') 84 | ? chrome.runtime.getURL('assets/icons-ui/github.svg') 85 | : 'assets/icons-ui/github.svg'; 86 | 87 | function formatStatusMessage(iconType, message, size = 'medium') { 88 | const sizeClass = size === 'large' ? 'status-icon--large' : 'status-icon--medium'; 89 | return `<span class="status-icon ${sizeClass} status-icon--${iconType}" aria-hidden="true"></span><span>${message}</span>`; 90 | } 91 | 92 | function applyInputStatusStyles(input, { text, iconUrl, color, backgroundColor, borderColor }) { 93 | if (!input) { 94 | return; 95 | } 96 | 97 | input.placeholder = text; 98 | input.style.backgroundColor = backgroundColor; 99 | input.style.borderColor = borderColor; 100 | input.style.color = color; 101 | input.style.backgroundImage = iconUrl ? `url('${iconUrl}')` : ''; 102 | input.style.backgroundRepeat = 'no-repeat'; 103 | input.style.backgroundPosition = '12px center'; 104 | input.style.backgroundSize = '16px'; 105 | input.style.paddingLeft = iconUrl ? '36px' : '12px'; 106 | } 107 | 108 | function applyStatusBanner(element, { iconType, text, color, backgroundColor, borderColor }) { 109 | if (!element) { 110 | return; 111 | } 112 | 113 | element.innerHTML = formatStatusMessage(iconType, text); 114 | element.style.display = 'inline-flex'; 115 | element.style.alignItems = 'center'; 116 | element.style.gap = '8px'; 117 | element.style.background = backgroundColor; 118 | element.style.color = color; 119 | element.style.borderColor = borderColor; 120 | } 121 | 122 | // Create modal HTML 123 | const modalHTML = ` 124 | <div id="chatgpt-product-search-modal" style=" 125 | position: fixed; 126 | top: 0; 127 | left: 0; 128 | width: 100%; 129 | height: 100%; 130 | background: rgba(255, 244, 214, 0.55); 131 | backdrop-filter: blur(1.5px); 132 | -webkit-backdrop-filter: blur(1.5px); 133 | display: flex; 134 | align-items: center; 135 | justify-content: center; 136 | z-index: 10000; 137 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 138 | padding: 24px; 139 | box-sizing: border-box; 140 | "> 141 | <style> 142 | .table-row-hover:hover { 143 | background-color: #edf2ff !important; 144 | } 145 | .sidebar-project { 146 | padding: 6px 8px; 147 | margin: 1px 0; 148 | border-radius: 4px; 149 | cursor: pointer; 150 | transition: background-color 0.2s; 151 | font-size: 13px; 152 | color: #35426b; 153 | border: 1px solid transparent; 154 | } 155 | .sidebar-project:hover { 156 | background-color: rgba(91, 141, 239, 0.16) !important; 157 | } 158 | .sidebar-tag { 159 | padding: 4px 8px; 160 | margin: 1px 0; 161 | border-radius: 12px; 162 | cursor: pointer; 163 | transition: all 0.2s; 164 | font-size: 12px; 165 | color: #1f2a52; 166 | background-color: rgba(91, 141, 239, 0.22); 167 | border: 1px solid rgba(91, 141, 239, 0.35); 168 | display: flex; 169 | justify-content: space-between; 170 | align-items: center; 171 | } 172 | .sidebar-tag:hover { 173 | filter: brightness(1); 174 | transform: translateY(-1px); 175 | background-color: rgba(91, 141, 239, 0.32); 176 | border-color: rgba(91, 141, 239, 0.55); 177 | } 178 | #toggle-filters:hover { 179 | color: #3f6fe0 !important; 180 | text-decoration: underline; 181 | } 182 | .analysis-tag-label:hover { 183 | background-color: rgba(91, 141, 239, 0.12) !important; 184 | } 185 | #help-btn:hover { 186 | background: rgba(91, 141, 239, 0.22) !important; 187 | color: #1f2a52 !important; 188 | transform: translateY(-1px); 189 | } 190 | #help-btn:focus-visible { 191 | outline: none; 192 | box-shadow: 0 0 0 3px rgba(91, 141, 239, 0.35); 193 | } 194 | #settings-btn:hover { 195 | background: rgba(91, 141, 239, 0.22) !important; 196 | color: #1f2a52 !important; 197 | transform: translateY(-1px); 198 | } 199 | #settings-btn:focus-visible { 200 | outline: none; 201 | box-shadow: 0 0 0 3px rgba(91, 141, 239, 0.35); 202 | } 203 | #close-modal-btn:hover { 204 | background: rgba(91, 141, 239, 0.28) !important; 205 | color: #1f2a52 !important; 206 | } 207 | #close-modal-btn:focus-visible { 208 | outline: none; 209 | box-shadow: 0 0 0 3px rgba(91, 141, 239, 0.35); 210 | } 211 | .status-icon { 212 | display: inline-flex; 213 | background-color: currentColor; 214 | -webkit-mask-size: contain; 215 | -webkit-mask-repeat: no-repeat; 216 | -webkit-mask-position: center; 217 | mask-size: contain; 218 | mask-repeat: no-repeat; 219 | mask-position: center; 220 | flex-shrink: 0; 221 | } 222 | .status-icon--medium { 223 | width: 18px; 224 | height: 18px; 225 | } 226 | .status-icon--large { 227 | width: 48px; 228 | height: 48px; 229 | } 230 | .status-icon--success { 231 | -webkit-mask-image: url('${checkIconUrl}'); 232 | mask-image: url('${checkIconUrl}'); 233 | } 234 | .status-icon--warning { 235 | -webkit-mask-image: url('${warningIconUrl}'); 236 | mask-image: url('${warningIconUrl}'); 237 | } 238 | .status-icon--error { 239 | -webkit-mask-image: url('${errorIconUrl}'); 240 | mask-image: url('${errorIconUrl}'); 241 | } 242 | .history-list { 243 | display: flex; 244 | flex-direction: column; 245 | gap: 18px; 246 | } 247 | .history-day-group { 248 | display: flex; 249 | flex-direction: column; 250 | gap: 8px; 251 | } 252 | .history-day-header { 253 | position: sticky; 254 | top: 0; 255 | z-index: 2; 256 | align-self: flex-start; 257 | padding: 6px 10px; 258 | font-size: 11px; 259 | font-weight: 600; 260 | letter-spacing: 0.08em; 261 | text-transform: uppercase; 262 | color: #ffffff; 263 | background: rgb(164 155 189); 264 | border-radius: 8px; 265 | box-shadow: 0 1px 2px rgba(79, 130, 223, 0.08); 266 | } 267 | .history-day-body { 268 | border: 1px solid #e4e9f6; 269 | border-radius: 12px; 270 | overflow: hidden; 271 | background: rgba(255, 255, 255, 0.95); 272 | backdrop-filter: blur(2px); 273 | } 274 | .history-row { 275 | display: grid; 276 | grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr) auto; 277 | gap: 14px; 278 | align-items: center; 279 | cursor: pointer; 280 | transition: background 0.18s ease, box-shadow 0.18s ease; 281 | } 282 | .history-row { 283 | padding: 10px 14px; 284 | } 285 | .history-row + .history-row { 286 | border-top: 1px solid #e4e8f6; 287 | } 288 | .history-row:hover { 289 | background: rgba(91, 141, 239, 0.04); 290 | } 291 | .history-row:focus-visible { 292 | outline: 2px solid rgba(91, 141, 239, 0.4); 293 | outline-offset: -2px; 294 | background: rgba(91, 141, 239, 0.08); 295 | } 296 | .history-row-left { 297 | display: flex; 298 | flex-direction: column; 299 | gap: 8px; 300 | min-width: 0; 301 | } 302 | .history-query-group { 303 | display: flex; 304 | flex-wrap: wrap; 305 | gap: 6px; 306 | margin-top: 0; 307 | } 308 | .history-query-chip { 309 | display: inline-flex; 310 | align-items: center; 311 | gap: 4px; 312 | padding: 5px 12px; 313 | border-radius: 999px; 314 | background: #eef2ff; 315 | border: 1px solid #d5ddf5; 316 | color: #30426d; 317 | font-size: 13px; 318 | line-height: 1.25; 319 | white-space: nowrap; 320 | } 321 | .history-query-chip[data-extra="true"] { 322 | display: none; 323 | } 324 | .history-query-group[data-expanded="true"] .history-query-chip[data-extra="true"] { 325 | display: inline-flex; 326 | } 327 | .history-query-toggle { 328 | border: 1px dashed #c2cef3; 329 | background: #f7f9ff; 330 | color: #2f4db5; 331 | padding: 5px 12px; 332 | border-radius: 999px; 333 | font-size: 12px; 334 | font-weight: 600; 335 | cursor: pointer; 336 | display: inline-flex; 337 | align-items: center; 338 | gap: 4px; 339 | } 340 | .history-query-toggle:hover { 341 | background: #e8edff; 342 | } 343 | .history-row-info { 344 | display: flex; 345 | flex-direction: column; 346 | gap: 6px; 347 | align-items: flex-start; 348 | } 349 | .history-row-actions { 350 | display: flex; 351 | align-items: center; 352 | gap: 6px; 353 | } 354 | .history-chip { 355 | display: inline-flex; 356 | align-items: center; 357 | gap: 4px; 358 | padding: 3px 10px; 359 | border-radius: 999px; 360 | font-size: 12px; 361 | line-height: 1.2; 362 | white-space: nowrap; 363 | } 364 | .history-chip img { 365 | width: 12px; 366 | height: 12px; 367 | } 368 | .history-chip--project { 369 | background: #f6f7fb; 370 | border: 1px solid #e3e7f3; 371 | color: #46536d; 372 | font-size: 11px; 373 | } 374 | .history-chip--tag { 375 | font-size: 11px; 376 | border: 1px solid #e1e6f5; 377 | background: #f7f9fd; 378 | color: var(--chip-tag-color, #4c63a6); 379 | } 380 | .history-chip-dot { 381 | display: inline-block; 382 | width: 6px; 383 | height: 6px; 384 | border-radius: 50%; 385 | background: var(--chip-dot-color, currentColor); 386 | } 387 | .history-chip--tag .history-chip-dot { 388 | background: var(--chip-tag-color, #5b8def); 389 | } 390 | .history-row-labels { 391 | display: flex; 392 | flex-wrap: wrap; 393 | gap: 6px; 394 | margin-top: 6px; 395 | } 396 | .history-meta-group { 397 | display: flex; 398 | flex-wrap: wrap; 399 | gap: 12px; 400 | align-items: center; 401 | font-size: 12px; 402 | color: #4a5874; 403 | } 404 | .history-meta { 405 | font-weight: 600; 406 | color: #243356; 407 | } 408 | .history-time { 409 | display: inline-flex; 410 | align-items: center; 411 | gap: 4px; 412 | color: #97a1b8; 413 | font-weight: 500; 414 | letter-spacing: 0.01em; 415 | } 416 | .history-time svg { 417 | width: 14px; 418 | height: 14px; 419 | } 420 | .history-market { 421 | display: inline-flex; 422 | align-items: center; 423 | gap: 8px; 424 | font-size: 12px; 425 | color: #4a5874; 426 | } 427 | .history-market img { 428 | width: 16px; 429 | height: 12px; 430 | border-radius: 2px; 431 | box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.08); 432 | object-fit: cover; 433 | } 434 | .history-market-text { 435 | display: flex; 436 | flex-direction: column; 437 | gap: 2px; 438 | line-height: 1.1; 439 | } 440 | .history-market-label { 441 | font-weight: 600; 442 | color: #243356; 443 | } 444 | .history-market-language { 445 | font-size: 11px; 446 | color: #6c7893; 447 | } 448 | .history-stats { 449 | display: flex; 450 | flex-wrap: wrap; 451 | gap: 12px; 452 | font-size: 12px; 453 | color: #4b5976; 454 | } 455 | .history-stats span { 456 | display: inline-flex; 457 | align-items: baseline; 458 | gap: 3px; 459 | } 460 | .history-stats span span { 461 | color: #5a6886; 462 | font-weight: 500; 463 | text-transform: lowercase; 464 | } 465 | .history-stats strong { 466 | color: #1f2d4a; 467 | font-weight: 600; 468 | } 469 | .history-empty-row { 470 | padding: 32px 16px; 471 | text-align: center; 472 | color: #6c7a9b; 473 | border: 1px dashed #d4dcf6; 474 | border-radius: 12px; 475 | background: rgba(255, 255, 255, 0.6); 476 | } 477 | .history-icon-btn { 478 | border: 1px solid transparent; 479 | border-radius: 999px; 480 | padding: 4px 12px; 481 | font-size: 12px; 482 | font-weight: 500; 483 | background: transparent; 484 | color: #4b5972; 485 | cursor: pointer; 486 | transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease; 487 | } 488 | .history-icon-btn:hover { 489 | background: rgba(91, 141, 239, 0.1); 490 | color: #2f4db5; 491 | } 492 | .history-icon-btn--primary { 493 | color: #2f4db5; 494 | } 495 | .history-icon-btn--danger { 496 | color: #c94b59; 497 | } 498 | .history-icon-btn--danger:hover { 499 | background: rgba(201, 75, 89, 0.12); 500 | } 501 | .history-icon-btn:focus-visible { 502 | outline: none; 503 | box-shadow: 0 0 0 2px rgba(91, 141, 239, 0.35); 504 | } 505 | .search-input-group { 506 | display: flex; 507 | align-items: center; 508 | gap: 10px; 509 | padding: 10px 16px; 510 | background: rgba(238, 243, 255, 0.9); 511 | border: 1px solid rgba(91, 141, 239, 0.35); 512 | border-radius: 20px; 513 | flex: 1; 514 | transition: border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease; 515 | } 516 | .search-input-group:focus-within { 517 | border-color: #5b8def; 518 | box-shadow: 0 0 0 3px rgba(91, 141, 239, 0.18); 519 | background: rgba(255, 255, 255, 0.98); 520 | } 521 | .search-input-divider { 522 | width: 1px; 523 | height: 24px; 524 | background: rgba(91, 141, 239, 0.3); 525 | } 526 | .search-input-field { 527 | flex: 1; 528 | background: transparent; 529 | border: none; 530 | font-size: 14px; 531 | color: #111827; 532 | height: 32px; 533 | padding: 0; 534 | min-width: 0; 535 | } 536 | .search-input-field:focus { 537 | outline: none; 538 | } 539 | .search-btn { 540 | width: 44px; 541 | height: 44px; 542 | border-radius: 50%; 543 | border: none; 544 | background: #000000; 545 | color: white; 546 | display: flex; 547 | align-items: center; 548 | justify-content: center; 549 | cursor: pointer; 550 | transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease; 551 | box-shadow: 0 10px 24px rgba(87, 125, 238, 0.25); 552 | } 553 | .search-btn:hover { 554 | transform: translateY(-2px); 555 | box-shadow: 0 14px 30px rgba(87, 125, 238, 0.35); 556 | background: #000000; 557 | } 558 | .search-btn:focus-visible { 559 | outline: none; 560 | box-shadow: 0 0 0 3px rgba(121, 161, 255, 0.45); 561 | } 562 | .search-btn:disabled, 563 | .search-btn[data-state="loading"] { 564 | cursor: not-allowed; 565 | opacity: 0.6; 566 | transform: none; 567 | box-shadow: none; 568 | background: #000000; 569 | } 570 | .search-btn-icon { 571 | width: 20px; 572 | height: 20px; 573 | display: inline-flex; 574 | align-items: center; 575 | justify-content: center; 576 | } 577 | .search-btn-spinner { 578 | display: none; 579 | width: 18px; 580 | height: 18px; 581 | border: 2px solid rgba(255, 255, 255, 0.35); 582 | border-top-color: #ffffff; 583 | border-radius: 50%; 584 | animation: spin 1s linear infinite; 585 | } 586 | .search-btn[data-state="loading"] .search-btn-icon { 587 | display: none; 588 | } 589 | .search-btn[data-state="loading"] .search-btn-spinner { 590 | display: inline-flex; 591 | } 592 | .visually-hidden { 593 | position: absolute; 594 | width: 1px; 595 | height: 1px; 596 | padding: 0; 597 | margin: -1px; 598 | overflow: hidden; 599 | clip: rect(0, 0, 0, 0); 600 | white-space: nowrap; 601 | border: 0; 602 | } 603 | @keyframes spin { 604 | to { 605 | transform: rotate(360deg); 606 | } 607 | } 608 | .market-select-trigger { 609 | display: flex; 610 | align-items: center; 611 | gap: 8px; 612 | border: none; 613 | background: white; 614 | padding: 6px 12px; 615 | border-radius: 14px; 616 | cursor: pointer; 617 | text-align: left; 618 | box-shadow: inset 0 0 0 1px #e5e7eb; 619 | transition: box-shadow 0.2s ease, transform 0.2s ease; 620 | min-width: 180px; 621 | min-height: 32px; 622 | white-space: nowrap; 623 | } 624 | .market-select-trigger:focus-visible { 625 | outline: none; 626 | box-shadow: inset 0 0 0 2px #10a37f; 627 | } 628 | .market-select-trigger:hover { 629 | box-shadow: inset 0 0 0 1px #10a37f, 0 2px 8px rgba(16, 163, 127, 0.12); 630 | } 631 | .market-select-text { 632 | display: flex; 633 | flex-direction: column; 634 | align-items: flex-start; 635 | min-width: 0; 636 | flex: 0 1 auto; 637 | } 638 | .market-select-country { 639 | font-size: 13px; 640 | font-weight: 600; 641 | color: #343a40; 642 | line-height: 1.1; 643 | } 644 | .market-select-language { 645 | font-size: 12px; 646 | color: #6c757d; 647 | line-height: 1.1; 648 | } 649 | .market-select-caret { 650 | margin-left: auto; 651 | font-size: 12px; 652 | color: #6c757d; 653 | } 654 | .market-select-dropdown { 655 | position: absolute; 656 | top: calc(100% + 4px); 657 | left: 0; 658 | width: 100%; 659 | background: white; 660 | border: 1px solid #dee2e6; 661 | border-radius: 8px; 662 | box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12); 663 | padding: 4px 0; 664 | max-height: 260px; 665 | overflow-y: auto; 666 | z-index: 25; 667 | display: none; 668 | } 669 | .market-select-dropdown.open { 670 | display: block; 671 | } 672 | .market-select-option { 673 | display: flex; 674 | align-items: center; 675 | gap: 10px; 676 | width: 100%; 677 | border: none; 678 | background: transparent; 679 | padding: 8px 12px; 680 | font-size: 13px; 681 | text-align: left; 682 | cursor: pointer; 683 | transition: background 0.15s ease; 684 | } 685 | .market-select-option:focus-visible { 686 | outline: none; 687 | background: #e7f1ff; 688 | } 689 | .market-select-option:hover { 690 | background: #f1f3f5; 691 | } 692 | .market-select-option--selected { 693 | background: #e9f5ff; 694 | } 695 | .market-select-option-flag { 696 | width: 20px; 697 | height: 14px; 698 | object-fit: cover; 699 | border-radius: 2px; 700 | flex-shrink: 0; 701 | } 702 | .market-select-option-text { 703 | display: flex; 704 | flex-direction: column; 705 | align-items: flex-start; 706 | min-width: 0; 707 | } 708 | .market-select-option-country { 709 | font-weight: 600; 710 | color: #343a40; 711 | line-height: 1.1; 712 | } 713 | .market-select-option-language { 714 | font-size: 12px; 715 | color: #6c757d; 716 | line-height: 1.1; 717 | } 718 | </style> 719 | <div style=" 720 | background: linear-gradient(140deg, #f5f8ff 0%, #ffffff 60%); 721 | width: min(1600px, 90vw); 722 | height: min(1000px, 90vh); 723 | border-radius: 20px; 724 | display: flex; 725 | flex-direction: column; 726 | overflow: hidden; 727 | box-shadow: 0 28px 68px rgba(32, 42, 92, 0.28); 728 | border: 1px solid rgba(74, 105, 183, 0.32); 729 | position: relative; 730 | "> 731 | <div style=" 732 | background: linear-gradient(90deg, rgba(91, 141, 239, 0.16), rgba(232, 238, 255, 0.9)); 733 | padding: 16px 24px; 734 | display: flex; 735 | justify-content: space-between; 736 | align-items: center; 737 | border-bottom: 1px solid rgba(91, 141, 239, 0.18); 738 | "> 739 | <h1 style=" 740 | font-size: 19px; 741 | font-weight: 600; 742 | margin: 0; 743 | color: #27325f; 744 | display: flex; 745 | align-items: center; 746 | gap: 10px; 747 | letter-spacing: 0.15px; 748 | "><img src="${searchIconUrl}" alt="Search" style="width: 20px; height: 20px; filter: hue-rotate(18deg) saturate(1.1);" />ChatGPT E-commerce Product Research</h1> 749 | <button id="close-modal-btn" style=" 750 | border: none; 751 | color: #3c4b7c; 752 | font-size: 20px; 753 | width: 34px; 754 | height: 34px; 755 | border-radius: 50%; 756 | cursor: pointer; 757 | display: flex; 758 | align-items: center; 759 | justify-content: center; 760 | transition: all 0.2s ease; 761 | ">×</button> 762 | </div> 763 | 764 | <div style=" 765 | flex: 1; 766 | display: flex; 767 | flex-direction: row; 768 | overflow: hidden; 769 | "> 770 | <!-- Sidebar Navigation --> 771 | <div id="sidebar" style=" 772 | width: 200px; 773 | min-width: 200px; 774 | background: linear-gradient(180deg, rgba(91, 141, 239, 0.14), rgba(232, 238, 255, 0.35)); 775 | border-right: 1px solid rgba(91, 141, 239, 0.18); 776 | display: flex; 777 | flex-direction: column; 778 | overflow: hidden; 779 | "> 780 | <div style=" 781 | padding: 14px 18px; 782 | background: rgba(255, 255, 255, 0.96); 783 | border-bottom: 1px solid rgba(91, 141, 239, 0.18); 784 | display: flex; 785 | justify-content: space-between; 786 | align-items: center; 787 | "> 788 | <h3 style=" 789 | margin: 0; 790 | font-size: 14px; 791 | font-weight: 600; 792 | color: #27325f; 793 | ">Organization</h3> 794 | <div style="display: flex; gap: 8px;"> 795 | <button id="help-btn" style=" 796 | background: rgba(91, 141, 239, 0.12); 797 | border: none; 798 | color: #3c4b7c; 799 | font-size: 18px; 800 | width: 24px; 801 | height: 24px; 802 | border-radius: 6px; 803 | cursor: pointer; 804 | display: flex; 805 | align-items: center; 806 | justify-content: center; 807 | transition: all 0.2s ease; 808 | " title="Help & Tutorial">?</button> 809 | <button id="settings-btn" style=" 810 | background: rgba(91, 141, 239, 0.12); 811 | border: none; 812 | color: #3c4b7c; 813 | font-size: 16px; 814 | width: 24px; 815 | height: 24px; 816 | border-radius: 6px; 817 | cursor: pointer; 818 | display: flex; 819 | align-items: center; 820 | justify-content: center; 821 | transition: all 0.2s ease; 822 | " title="Settings"><img src="${settingsIconUrl}" alt="Settings" style="width: 18px; height: 18px;" /></button> 823 | </div> 824 | </div> 825 | 826 | <div style=" 827 | flex: 1; 828 | overflow-y: auto; 829 | padding: 8px; 830 | "> 831 | <!-- Projects Section --> 832 | <div style="margin-bottom: 16px;"> 833 | <div style=" 834 | display: flex; 835 | justify-content: space-between; 836 | align-items: center; 837 | padding: 4px 8px; 838 | margin-bottom: 4px; 839 | "> 840 | <span style=" 841 | font-size: 12px; 842 | font-weight: 600; 843 | color: #6c757d; 844 | text-transform: uppercase; 845 | letter-spacing: 0.5px; 846 | ">Projects</span> 847 | <button id="add-project-btn" style=" 848 | background: none; 849 | border: none; 850 | color: #5b8def; 851 | font-size: 12px; 852 | cursor: pointer; 853 | padding: 2px 4px; 854 | border-radius: 2px; 855 | " title="Add Project">+</button> 856 | </div> 857 | <div id="projects-list" style=" 858 | display: flex; 859 | flex-direction: column; 860 | gap: 2px; 861 | "> 862 | <!-- Projects will be dynamically loaded here --> 863 | </div> 864 | </div> 865 | 866 | <!-- Tags Section --> 867 | <div> 868 | <div style=" 869 | display: flex; 870 | justify-content: space-between; 871 | align-items: center; 872 | padding: 4px 8px; 873 | margin-bottom: 4px; 874 | "> 875 | <span style=" 876 | font-size: 12px; 877 | font-weight: 600; 878 | color: #6c757d; 879 | text-transform: uppercase; 880 | letter-spacing: 0.5px; 881 | ">Tags</span> 882 | <button id="add-tag-btn" style=" 883 | background: none; 884 | border: none; 885 | color: #5b8def; 886 | font-size: 12px; 887 | cursor: pointer; 888 | padding: 2px 4px; 889 | border-radius: 2px; 890 | " title="Add Tag">+</button> 891 | </div> 892 | <div id="tags-list" style=" 893 | display: flex; 894 | flex-direction: column; 895 | gap: 2px; 896 | "> 897 | <!-- Tags will be dynamically loaded here --> 898 | </div> 899 | </div> 900 | </div> 901 | </div> 902 | 903 | <!-- Main Content Area --> 904 | <div style=" 905 | flex: 1; 906 | display: flex; 907 | flex-direction: column; 908 | overflow: hidden; 909 | "> 910 | <!-- Tab Navigation --> 911 | <div id="tab-navigation" style=" 912 | display: flex; 913 | background: rgba(91, 141, 239, 0.12); 914 | border-bottom: 1px solid rgba(91, 141, 239, 0.18); 915 | backdrop-filter: blur(6px); 916 | "> 917 | <button id="search-tab" class="tab-button active-tab" style=" 918 | flex: 1; 919 | padding: 14px 24px; 920 | border: none; 921 | background: rgba(255, 255, 255, 0.96); 922 | color: #27325f; 923 | font-size: 14px; 924 | font-weight: 600; 925 | cursor: pointer; 926 | border-bottom: 3px solid #5b8def; 927 | transition: all 0.2s ease; 928 | display: flex; 929 | align-items: center; 930 | justify-content: center; 931 | gap: 8px; 932 | "><img src="${searchIconUrl}" alt="Search" style="width: 20px; height: 20px;" />Search</button> 933 | <button id="history-tab" class="tab-button" style=" 934 | flex: 1; 935 | padding: 14px 24px; 936 | border: none; 937 | background: transparent; 938 | color: #5e6f9b; 939 | font-size: 14px; 940 | font-weight: 600; 941 | cursor: pointer; 942 | border-bottom: 2px solid transparent; 943 | transition: all 0.2s ease; 944 | display: flex; 945 | align-items: center; 946 | justify-content: center; 947 | gap: 8px; 948 | "><img src="${historyIconUrl}" alt="History" style="width: 20px; height: 20px;" />History</button> 949 | <button id="reports-tab" class="tab-button" style=" 950 | flex: 1; 951 | padding: 14px 24px; 952 | border: none; 953 | background: transparent; 954 | color: #5e6f9b; 955 | font-size: 14px; 956 | font-weight: 600; 957 | cursor: pointer; 958 | border-bottom: 2px solid transparent; 959 | transition: all 0.2s ease; 960 | display: flex; 961 | align-items: center; 962 | justify-content: center; 963 | gap: 8px; 964 | "><img src="${analysisIconUrl}" alt="Analysis" style="width: 20px; height: 20px;" />Analysis</button> 965 | </div> 966 | 967 | <div id="search-area" style=" 968 | position: relative; 969 | padding: 24px; 970 | border-bottom: 1px solid rgba(91, 141, 239, 0.18); 971 | background: rgba(255, 255, 255, 0.9); 972 | transition: all 0.3s ease; 973 | "> 974 | <!-- Collapse/Expand Button - positioned absolutely --> 975 | <div id="collapse-toggle" style=" 976 | display: none; 977 | position: absolute; 978 | top: 8px; 979 | right: 20px; 980 | cursor: pointer; 981 | color: #5b8def; 982 | font-size: 12px; 983 | font-weight: 500; 984 | transition: all 0.2s ease; 985 | border-radius: 4px; 986 | padding: 4px 8px; 987 | background: rgba(91, 141, 239, 0.1); 988 | border: 1px solid rgba(91, 141, 239, 0.18); 989 | z-index: 10; 990 | "> 991 | <span id="collapse-text">▲ Hide</span> 992 | </div> 993 | 994 | <div id="search-controls"> 995 | <!-- Multi-product toggle --> 996 | <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px; padding: 8px 0;"> 997 | <label style=" 998 | display: flex; 999 | align-items: center; 1000 | gap: 8px; 1001 | font-size: 14px; 1002 | color: #495057; 1003 | font-weight: 500; 1004 | cursor: pointer; 1005 | "> 1006 | <div style=" 1007 | position: relative; 1008 | width: 44px; 1009 | height: 24px; 1010 | background: #dee2e6; 1011 | border-radius: 12px; 1012 | transition: background 0.3s ease; 1013 | cursor: pointer; 1014 | " id="toggle-background"> 1015 | <input type="checkbox" id="multi-product-toggle" style=" 1016 | position: absolute; 1017 | opacity: 0; 1018 | width: 100%; 1019 | height: 100%; 1020 | margin: 0; 1021 | cursor: pointer; 1022 | " /> 1023 | <div style=" 1024 | position: absolute; 1025 | top: 2px; 1026 | left: 2px; 1027 | width: 20px; 1028 | height: 20px; 1029 | background: white; 1030 | border-radius: 50%; 1031 | transition: transform 0.3s ease; 1032 | box-shadow: 0 2px 4px rgba(0,0,0,0.2); 1033 | " id="toggle-slider"></div> 1034 | </div> 1035 | Multi-product search 1036 | </label> 1037 | <div style=" 1038 | font-size: 12px; 1039 | color: #6c757d; 1040 | font-style: italic; 1041 | ">Search multiple products at once</div> 1042 | </div> 1043 | 1044 | <!-- Single product input --> 1045 | <div id="single-product-input" style="display: flex; gap: 12px; margin-bottom: 12px; align-items: center;"> 1046 | <div id="single-input-group" class="search-input-group" style="flex: 1;"> 1047 | <input type="text" id="search-query" class="search-input-field" placeholder="Search query (e.g., iPhone 17, Nike shoes, Pets Deli Hundefutter)" autocomplete="off" /> 1048 | <div id="market-input-divider" class="search-input-divider" aria-hidden="true"></div> 1049 | <div id="market-select-container" style="position: relative; flex-shrink: 0;"> 1050 | <button id="market-select-trigger" type="button" class="market-select-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="market-select-dropdown"> 1051 | <img id="market-select-flag" src="" alt="Selected market" style=" 1052 | width: 20px; 1053 | height: 14px; 1054 | object-fit: cover; 1055 | border-radius: 2px; 1056 | flex-shrink: 0; 1057 | " /> 1058 | <div class="market-select-text"> 1059 | <span id="market-select-country" class="market-select-country">Deutschland</span> 1060 | <span id="market-select-language" class="market-select-language">Deutsch</span> 1061 | </div> 1062 | <span class="market-select-caret" aria-hidden="true"><svg aria-hidden="true" tabindex="-1" disabled="" data-ui-name="Globe" width="16" height="16" viewBox="0 0 16 16" data-name="Globe" data-group="m" color="#A9ABB6" data-at="db-flag" aria-label="" style="--color_yxgog: #A9ABB6;"><path fill-rule="evenodd" clip-rule="evenodd" d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0ZM3.201 4.398A5.99 5.99 0 0 1 8 2c1.801 0 3.417.794 4.517 2.05a4.578 4.578 0 0 0-.967.429v.02a2 2 0 0 0-1.27.16 3.87 3.87 0 0 0-1.55 1.63 1.51 1.51 0 0 0-.17 1.13c.108.308.364.465.619.623.286.176.572.352.651.738.03.157.034.317.039.478.006.217.012.435.081.642a.67.67 0 0 0 .65.56c.35 0 .57-.39.69-.69a6.38 6.38 0 0 1 .82-1.63c.176-.227 1.076-.705 1.59-.979.088-.046.164-.087.224-.12a6 6 0 1 1-11.893.35c.288.09.62.178.999.259a2.88 2.88 0 0 1 2 1.16 6.75 6.75 0 0 1 .89 2c.17.52.41 1.25 1.11 1.16.7-.09 1-1 1-1.63a1.64 1.64 0 0 0-.74-1.63 11.524 11.524 0 0 0-.26-.154C6.305 8.13 5.17 7.47 5.78 6.55a3.63 3.63 0 0 1 1.17-1l.093-.065c.288-.2.6-.417.637-.805a.62.62 0 0 0-.55-.67 4.46 4.46 0 0 0-1.21 0c-.323.04-.64.107-.955.173l-.225.047a7.6 7.6 0 0 1-1.539.168Z" shape-rendering="geometricPrecision"></path></svg></span> 1063 | </button> 1064 | <div id="market-select-dropdown" class="market-select-dropdown" role="listbox" aria-labelledby="market-select-trigger"></div> 1065 | <select id="market-select" aria-label="Select language and market" style=" 1066 | position: absolute; 1067 | width: 1px; 1068 | height: 1px; 1069 | opacity: 0; 1070 | pointer-events: none; 1071 | border: none; 1072 | background: transparent; 1073 | "></select> 1074 | </div> 1075 | </div> 1076 | <button id="search-btn" class="search-btn" type="button" aria-label="Run product search" data-state="ready" data-ready-aria-label="Run product search" data-loading-aria-label="Searching" data-ready-status="Search" data-loading-status="Searching"> 1077 | <span class="search-btn-icon" aria-hidden="true"> 1078 | <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="icon"> 1079 | <path d="M8.99992 16V6.41407L5.70696 9.70704C5.31643 10.0976 4.68342 10.0976 4.29289 9.70704C3.90237 9.31652 3.90237 8.6835 4.29289 8.29298L9.29289 3.29298L9.36907 3.22462C9.76184 2.90427 10.3408 2.92686 10.707 3.29298L15.707 8.29298L15.7753 8.36915C16.0957 8.76192 16.0731 9.34092 15.707 9.70704C15.3408 10.0732 14.7618 10.0958 14.3691 9.7754L14.2929 9.70704L10.9999 6.41407V16C10.9999 16.5523 10.5522 17 9.99992 17C9.44764 17 8.99992 16.5523 8.99992 16Z"></path> 1080 | </svg> 1081 | </span> 1082 | <span class="search-btn-spinner" aria-hidden="true"></span> 1083 | <span class="visually-hidden search-btn-status">Search</span> 1084 | </button> 1085 | </div> 1086 | 1087 | <!-- Multi product input --> 1088 | <div id="multi-product-input" style="display: none; margin-bottom: 12px;"> 1089 | <textarea id="multi-search-query" placeholder="Enter product names, one per line: iPhone 17 Pro Samsung Galaxy S25 Google Pixel 9" style=" 1090 | width: 100%; 1091 | min-height: 100px; 1092 | padding: 8px 12px; 1093 | border: 1px solid #dee2e6; 1094 | border-radius: 4px; 1095 | font-size: 14px; 1096 | box-sizing: border-box; 1097 | resize: vertical; 1098 | font-family: inherit; 1099 | "></textarea> 1100 | <div id="multi-product-actions" style=" 1101 | display: flex; 1102 | justify-content: space-between; 1103 | align-items: center; 1104 | gap: 12px; 1105 | margin-top: 12px; 1106 | "> 1107 | <div style=" 1108 | font-size: 12px; 1109 | color: #6c757d; 1110 | font-style: italic; 1111 | ">Results will be shown in a table format</div> 1112 | <div id="multi-product-controls" style="display: flex; gap: 12px; align-items: center;"> 1113 | <div id="multi-market-select-mount" style="display: none; align-items: center; gap: 8px;"></div> 1114 | <button id="multi-search-btn" class="search-btn" type="button" aria-label="Run multi-product search" data-state="ready" data-ready-aria-label="Run multi-product search" data-loading-aria-label="Searching all products" data-ready-status="Search All Products" data-loading-status="Searching all products"> 1115 | <span class="search-btn-icon" aria-hidden="true"> 1116 | <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="icon"> 1117 | <path d="M8.99992 16V6.41407L5.70696 9.70704C5.31643 10.0976 4.68342 10.0976 4.29289 9.70704C3.90237 9.31652 3.90237 8.6835 4.29289 8.29298L9.29289 3.29298L9.36907 3.22462C9.76184 2.90427 10.3408 2.92686 10.707 3.29298L15.707 8.29298L15.7753 8.36915C16.0957 8.76192 16.0731 9.34092 15.707 9.70704C15.3408 10.0732 14.7618 10.0958 14.3691 9.7754L14.2929 9.70704L10.9999 6.41407V16C10.9999 16.5523 10.5522 17 9.99992 17C9.44764 17 8.99992 16.5523 8.99992 16Z"></path> 1118 | </svg> 1119 | </span> 1120 | <span class="search-btn-spinner" aria-hidden="true"></span> 1121 | <span class="visually-hidden search-btn-status">Search All Products</span> 1122 | </button> 1123 | </div> 1124 | </div> 1125 | </div> 1126 | </div> <!-- End search-controls --> 1127 | 1128 | <!-- Hidden token field for status display --> 1129 | <input type="text" id="auth-token" placeholder="Token will be fetched automatically" readonly style=" 1130 | display: none; 1131 | padding: 8px 12px; 1132 | border: 1px solid #dee2e6; 1133 | border-radius: 4px; 1134 | font-size: 14px; 1135 | box-sizing: border-box; 1136 | background-color: #f9f9f9; 1137 | cursor: not-allowed; 1138 | " /> 1139 | </div> 1140 | 1141 | <div id="results-container" style=" 1142 | flex: 1; 1143 | overflow-y: auto; 1144 | padding: 24px; 1145 | "> 1146 | ${createWelcomeState()} 1147 | </div> 1148 | 1149 | <!-- History Container --> 1150 | <div id="history-container" style=" 1151 | flex: 1; 1152 | overflow-y: auto; 1153 | padding: 24px; 1154 | display: none; 1155 | "> 1156 | <div id="history-welcome-state" style=" 1157 | text-align: center; 1158 | padding: 60px 40px; 1159 | color: #5e6f9b; 1160 | display: flex; 1161 | flex-direction: column; 1162 | align-items: center; 1163 | justify-content: center; 1164 | height: 100%; 1165 | min-height: 300px; 1166 | "> 1167 | <img src="${historyIconUrl}" alt="History" style="width: 52px; height: 52px; margin-bottom: 22px; opacity: 0.9; filter: hue-rotate(18deg) saturate(1.05);" /> 1168 | <h3 style=" 1169 | margin: 0 0 12px 0; 1170 | font-size: 20px; 1171 | font-weight: 600; 1172 | color: #27325f; 1173 | ">Search History</h3> 1174 | <p style=" 1175 | margin: 0 0 24px 0; 1176 | font-size: 16px; 1177 | line-height: 1.5; 1178 | max-width: 400px; 1179 | color: #465584; 1180 | ">Your search history will appear here. Start searching to build your history!</p> 1181 | <button id="clear-history-btn" style=" 1182 | background: #dc3545; 1183 | color: white; 1184 | border: none; 1185 | padding: 8px 16px; 1186 | border-radius: 4px; 1187 | font-size: 14px; 1188 | font-weight: 500; 1189 | cursor: pointer; 1190 | display: none; 1191 | ">Clear All History</button> 1192 | </div> 1193 | <div id="history-content" style="display: none;"> 1194 | <div style=" 1195 | margin-bottom: 20px; 1196 | border-bottom: 1px solid #e9ecef; 1197 | "> 1198 | <div style=" 1199 | display: flex; 1200 | justify-content: space-between; 1201 | align-items: center; 1202 | padding-bottom: 10px; 1203 | "> 1204 | <h3 style="margin: 0; font-size: 18px; color: #495057;">Search History</h3> 1205 | <div style="display: flex; gap: 10px; align-items: center;"> 1206 | <button id="toggle-filters" style=" 1207 | background: none; 1208 | color: #5b8def; 1209 | border: none; 1210 | padding: 6px 8px; 1211 | font-size: 13px; 1212 | font-weight: 500; 1213 | cursor: pointer; 1214 | display: flex; 1215 | align-items: center; 1216 | gap: 6px; 1217 | text-decoration: none; 1218 | "> 1219 | <svg width="16" height="16" viewBox="0 0 90 90" style="fill: currentColor;"> 1220 | <path d="M 85.813 59.576 H 55.575 c -1.657 0 -3 -1.343 -3 -3 s 1.343 -3 3 -3 h 30.237 c 1.657 0 3 1.343 3 3 S 87.47 59.576 85.813 59.576 z"/> 1221 | <path d="M 48.302 66.849 c -5.664 0 -10.272 -4.608 -10.272 -10.272 c 0 -5.665 4.608 -10.273 10.272 -10.273 c 5.665 0 10.273 4.608 10.273 10.273 C 58.575 62.24 53.967 66.849 48.302 66.849 z M 48.302 52.303 c -2.356 0 -4.272 1.917 -4.272 4.273 c 0 2.355 1.917 4.272 4.272 4.272 c 2.356 0 4.273 -1.917 4.273 -4.272 C 52.575 54.22 50.658 52.303 48.302 52.303 z"/> 1222 | <path d="M 41.029 59.576 H 4.188 c -1.657 0 -3 -1.343 -3 -3 s 1.343 -3 3 -3 h 36.842 c 1.657 0 3 1.343 3 3 S 42.686 59.576 41.029 59.576 z"/> 1223 | <path d="M 85.813 36.424 h -57.79 c -1.657 0 -3 -1.343 -3 -3 s 1.343 -3 3 -3 h 57.79 c 1.657 0 3 1.343 3 3 S 87.47 36.424 85.813 36.424 z"/> 1224 | <path d="M 20.75 43.697 c -5.665 0 -10.273 -4.608 -10.273 -10.273 s 4.608 -10.273 10.273 -10.273 s 10.273 4.608 10.273 10.273 S 26.414 43.697 20.75 43.697 z M 20.75 29.151 c -2.356 0 -4.273 1.917 -4.273 4.273 s 1.917 4.273 4.273 4.273 s 4.273 -1.917 4.273 -4.273 S 23.105 29.151 20.75 29.151 z"/> 1225 | <path d="M 13.477 36.424 H 4.188 c -1.657 0 -3 -1.343 -3 -3 s 1.343 -3 3 -3 h 9.289 c 1.657 0 3 1.343 3 3 S 15.133 36.424 13.477 36.424 z"/> 1226 | <path d="M 57.637 13.273 H 4.188 c -1.657 0 -3 -1.343 -3 -3 s 1.343 -3 3 -3 h 53.449 c 1.657 0 3 1.343 3 3 S 59.294 13.273 57.637 13.273 z"/> 1227 | <path d="M 64.909 20.546 c -5.664 0 -10.272 -4.608 -10.272 -10.273 S 59.245 0 64.909 0 c 5.665 0 10.273 4.608 10.273 10.273 S 70.574 20.546 64.909 20.546 z M 64.909 6 c -2.355 0 -4.272 1.917 -4.272 4.273 s 1.917 4.273 4.272 4.273 c 2.356 0 4.273 -1.917 4.273 -4.273 S 67.266 6 64.909 6 z"/> 1228 | <path d="M 85.813 13.273 h -13.63 c -1.657 0 -3 -1.343 -3 -3 s 1.343 -3 3 -3 h 13.63 c 1.657 0 3 1.343 3 3 S 87.47 13.273 85.813 13.273 z"/> 1229 | <path d="M 85.813 82.728 h -57.79 c -1.657 0 -3 -1.343 -3 -3 s 1.343 -3 3 -3 h 57.79 c 1.657 0 3 1.343 3 3 S 87.47 82.728 85.813 82.728 z"/> 1230 | <path d="M 20.75 90 c -5.665 0 -10.273 -4.608 -10.273 -10.272 c 0 -5.665 4.608 -10.273 10.273 -10.273 s 10.273 4.608 10.273 10.273 C 31.022 85.392 26.414 90 20.75 90 z M 20.75 75.454 c -2.356 0 -4.273 1.917 -4.273 4.273 c 0 2.355 1.917 4.272 4.273 4.272 s 4.273 -1.917 4.273 -4.272 C 25.022 77.371 23.105 75.454 20.75 75.454 z"/> 1231 | <path d="M 13.477 82.728 H 4.188 c -1.657 0 -3 -1.343 -3 -3 s 1.343 -3 3 -3 h 9.289 c 1.657 0 3 1.343 3 3 S 15.133 82.728 13.477 82.728 z"/> 1232 | </svg> 1233 | <span id="filter-toggle-text">Filters</span> 1234 | </button> 1235 | <button id="clear-history-btn-header" style=" 1236 | background: #dc3545; 1237 | color: white; 1238 | border: none; 1239 | padding: 6px 12px; 1240 | border-radius: 4px; 1241 | font-size: 13px; 1242 | font-weight: 500; 1243 | cursor: pointer; 1244 | ">Clear History</button> 1245 | </div> 1246 | </div> 1247 | 1248 | <!-- Advanced Filter Panel --> 1249 | <div id="filter-panel" style=" 1250 | display: none; 1251 | background: rgba(238, 243, 255, 0.85); 1252 | border: 1px solid rgba(91, 141, 239, 0.18); 1253 | border-radius: 12px; 1254 | padding: 18px; 1255 | margin-bottom: 16px; 1256 | box-shadow: 0 12px 28px rgba(79, 130, 223, 0.08); 1257 | "> 1258 | <div style=" 1259 | display: grid; 1260 | grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); 1261 | gap: 16px; 1262 | margin-bottom: 12px; 1263 | "> 1264 | <div> 1265 | <label style=" 1266 | display: block; 1267 | font-size: 12px; 1268 | font-weight: 600; 1269 | color: #4f6091; 1270 | margin-bottom: 6px; 1271 | ">Search Text</label> 1272 | <input type="text" id="filter-text" placeholder="Search in queries and results..." style=" 1273 | width: 100%; 1274 | padding: 8px 12px; 1275 | border: 1px solid rgba(91, 141, 239, 0.3); 1276 | border-radius: 8px; 1277 | font-size: 13px; 1278 | box-sizing: border-box; 1279 | " /> 1280 | </div> 1281 | 1282 | <div> 1283 | <label style=" 1284 | display: block; 1285 | font-size: 12px; 1286 | font-weight: 600; 1287 | color: #6c757d; 1288 | margin-bottom: 6px; 1289 | ">Project</label> 1290 | <select id="filter-project" style=" 1291 | width: 100%; 1292 | padding: 8px 12px; 1293 | border: 1px solid #dee2e6; 1294 | border-radius: 4px; 1295 | font-size: 13px; 1296 | background: white; 1297 | box-sizing: border-box; 1298 | "> 1299 | <option value="">All Projects</option> 1300 | </select> 1301 | </div> 1302 | 1303 | <div> 1304 | <label style=" 1305 | display: block; 1306 | font-size: 12px; 1307 | font-weight: 600; 1308 | color: #6c757d; 1309 | margin-bottom: 6px; 1310 | ">Market</label> 1311 | <select id="filter-market" style=" 1312 | width: 100%; 1313 | padding: 8px 12px; 1314 | border: 1px solid #dee2e6; 1315 | border-radius: 4px; 1316 | font-size: 13px; 1317 | background: white; 1318 | box-sizing: border-box; 1319 | "> 1320 | <option value="all">All Markets</option> 1321 | </select> 1322 | </div> 1323 | </div> 1324 | 1325 | <div style="margin-bottom: 12px;"> 1326 | <label style=" 1327 | display: block; 1328 | font-size: 12px; 1329 | font-weight: 600; 1330 | color: #6c757d; 1331 | margin-bottom: 6px; 1332 | ">Tags</label> 1333 | <div id="filter-tags" style=" 1334 | min-height: 32px; 1335 | max-height: 80px; 1336 | overflow-y: auto; 1337 | border: 1px solid #dee2e6; 1338 | border-radius: 4px; 1339 | padding: 8px; 1340 | background: white; 1341 | display: flex; 1342 | flex-wrap: wrap; 1343 | gap: 6px; 1344 | align-items: flex-start; 1345 | "> 1346 | <!-- Tag checkboxes will be populated here --> 1347 | </div> 1348 | </div> 1349 | 1350 | <div style=" 1351 | display: flex; 1352 | justify-content: space-between; 1353 | align-items: center; 1354 | padding-top: 8px; 1355 | border-top: 1px solid #e9ecef; 1356 | "> 1357 | <div id="filter-summary" style=" 1358 | font-size: 12px; 1359 | color: #6c757d; 1360 | "> 1361 | No filters applied 1362 | </div> 1363 | <div style="display: flex; gap: 8px;"> 1364 | <button id="clear-filters" style=" 1365 | background: #6c757d; 1366 | color: white; 1367 | border: none; 1368 | padding: 6px 12px; 1369 | border-radius: 4px; 1370 | font-size: 12px; 1371 | cursor: pointer; 1372 | ">Clear Filters</button> 1373 | <button id="apply-filters" style=" 1374 | background: #28a745; 1375 | color: white; 1376 | border: none; 1377 | padding: 6px 12px; 1378 | border-radius: 4px; 1379 | font-size: 12px; 1380 | cursor: pointer; 1381 | ">Apply</button> 1382 | </div> 1383 | </div> 1384 | </div> 1385 | 1386 | <!-- Active Filters Display --> 1387 | <div id="active-filters" style=" 1388 | display: none; 1389 | margin-bottom: 12px; 1390 | padding: 8px 0; 1391 | "> 1392 | <div style=" 1393 | font-size: 12px; 1394 | font-weight: 600; 1395 | color: #6c757d; 1396 | margin-bottom: 6px; 1397 | ">Active Filters:</div> 1398 | <div id="filter-chips" style=" 1399 | display: flex; 1400 | flex-wrap: wrap; 1401 | gap: 6px; 1402 | "> 1403 | <!-- Filter chips will be populated here --> 1404 | </div> 1405 | </div> 1406 | </div> 1407 | <div id="history-list" class="history-list"></div> 1408 | </div> 1409 | <div id="history-detail-container" style=" 1410 | display: none; 1411 | height: 100%; 1412 | flex-direction: column; 1413 | gap: 16px; 1414 | "> 1415 | <div style=" 1416 | display: flex; 1417 | justify-content: space-between; 1418 | align-items: center; 1419 | gap: 12px; 1420 | flex-wrap: wrap; 1421 | margin-bottom: 16px; 1422 | "> 1423 | <div style=" 1424 | display: flex; 1425 | align-items: center; 1426 | gap: 12px; 1427 | flex-wrap: wrap; 1428 | "> 1429 | <button id="history-detail-back" style=" 1430 | background: none; 1431 | border: 1px solid #e9ecef; 1432 | color: #495057; 1433 | padding: 6px 12px; 1434 | border-radius: 6px; 1435 | font-size: 13px; 1436 | font-weight: 500; 1437 | display: inline-flex; 1438 | align-items: center; 1439 | gap: 6px; 1440 | cursor: pointer; 1441 | ">← Back to history</button> 1442 | <div id="history-detail-title" style=" 1443 | display: flex; 1444 | flex-wrap: wrap; 1445 | gap: 8px; 1446 | align-items: center; 1447 | "></div> 1448 | </div> 1449 | <div style="display: flex; gap: 10px; align-items: center;"> 1450 | <span id="history-detail-meta" style=" 1451 | font-size: 13px; 1452 | color: #6c757d; 1453 | "></span> 1454 | <button id="history-detail-open-search" style=" 1455 | background: #5b8def; 1456 | color: white; 1457 | border: none; 1458 | padding: 6px 12px; 1459 | border-radius: 6px; 1460 | font-size: 12px; 1461 | cursor: not-allowed; 1462 | opacity: 0.6; 1463 | " disabled>Open in Search</button> 1464 | </div> 1465 | </div> 1466 | <div id="history-detail-results" style=" 1467 | flex: 1; 1468 | overflow-y: auto; 1469 | border: 1px solid #e9ecef; 1470 | border-radius: 10px; 1471 | padding: 16px; 1472 | background: white; 1473 | box-shadow: inset 0 1px 3px rgba(0,0,0,0.04); 1474 | "></div> 1475 | </div> 1476 | </div> 1477 | 1478 | <!-- Reports Container --> 1479 | <div id="reports-container" style=" 1480 | flex: 1; 1481 | overflow-y: auto; 1482 | padding: 20px; 1483 | display: none; 1484 | "> 1485 | <div id="reports-welcome-state" style=" 1486 | text-align: center; 1487 | padding: 60px 40px; 1488 | color: #6c757d; 1489 | display: flex; 1490 | flex-direction: column; 1491 | align-items: center; 1492 | justify-content: center; 1493 | height: 100%; 1494 | min-height: 300px; 1495 | "> 1496 | <img src="${analysisIconUrl}" alt="Analysis" style="width: 20px; height: 20px; margin-bottom: 20px; opacity: 0.7;" /> 1497 | <h3 style=" 1498 | margin: 0 0 12px 0; 1499 | font-size: 20px; 1500 | font-weight: 600; 1501 | color: #495057; 1502 | ">Cross-Search Analysis</h3> 1503 | <p style=" 1504 | margin: 0 0 24px 0; 1505 | font-size: 16px; 1506 | line-height: 1.5; 1507 | max-width: 400px; 1508 | ">No search history available. Start searching to see analysis results here.</p> 1509 | </div> 1510 | <div id="analysis-content" style="display: none;"> 1511 | <div style=" 1512 | margin-bottom: 20px; 1513 | border-bottom: 1px solid #e9ecef; 1514 | "> 1515 | <div style=" 1516 | display: flex; 1517 | justify-content: space-between; 1518 | align-items: center; 1519 | padding-bottom: 10px; 1520 | "> 1521 | <h4 style="margin: 0; font-size: 16px; color: #495057;">Analysis Results</h4> 1522 | <div style="display: flex; gap: 8px;"> 1523 | <button id="toggle-analysis-filters" style=" 1524 | background: none; 1525 | color: #5b8def; 1526 | border: none; 1527 | padding: 6px 12px; 1528 | border-radius: 4px; 1529 | font-size: 13px; 1530 | font-weight: 500; 1531 | cursor: pointer; 1532 | transition: background-color 0.2s; 1533 | display: flex; 1534 | align-items: center; 1535 | gap: 6px; 1536 | "> 1537 | <svg width="16" height="16" viewBox="0 0 90 90" style="fill: currentColor;"> 1538 | <path d="M 85.813 59.576 H 55.575 c -1.657 0 -3 -1.343 -3 -3 s 1.343 -3 3 -3 h 30.237 c 1.657 0 3 1.343 3 3 S 87.47 59.576 85.813 59.576 z"/> 1539 | <path d="M 48.302 66.849 c -5.664 0 -10.272 -4.608 -10.272 -10.272 c 0 -5.665 4.608 -10.273 10.272 -10.273 c 5.665 0 10.273 4.608 10.273 10.273 C 58.575 62.24 53.967 66.849 48.302 66.849 z M 48.302 52.303 c -2.356 0 -4.272 1.917 -4.272 4.273 c 0 2.355 1.917 4.272 4.272 4.272 c 2.356 0 4.273 -1.917 4.273 -4.272 C 52.575 54.22 50.658 52.303 48.302 52.303 z"/> 1540 | <path d="M 41.029 59.576 H 4.188 c -1.657 0 -3 -1.343 -3 -3 s 1.343 -3 3 -3 h 36.842 c 1.657 0 3 1.343 3 3 S 42.686 59.576 41.029 59.576 z"/> 1541 | <path d="M 85.813 36.424 h -57.79 c -1.657 0 -3 -1.343 -3 -3 s 1.343 -3 3 -3 h 57.79 c 1.657 0 3 1.343 3 3 S 87.47 36.424 85.813 36.424 z"/> 1542 | <path d="M 20.75 43.697 c -5.665 0 -10.273 -4.608 -10.273 -10.273 s 4.608 -10.273 10.273 -10.273 s 10.273 4.608 10.273 10.273 S 26.414 43.697 20.75 43.697 z M 20.75 29.151 c -2.356 0 -4.273 1.917 -4.273 4.273 s 1.917 4.273 4.273 4.273 s 4.273 -1.917 4.273 -4.273 S 23.105 29.151 20.75 29.151 z"/> 1543 | <path d="M 13.477 36.424 H 4.188 c -1.657 0 -3 -1.343 -3 -3 s 1.343 -3 3 -3 h 9.289 c 1.657 0 3 1.343 3 3 S 15.133 36.424 13.477 36.424 z"/> 1544 | <path d="M 57.637 13.273 H 4.188 c -1.657 0 -3 -1.343 -3 -3 s 1.343 -3 3 -3 h 53.449 c 1.657 0 3 1.343 3 3 S 59.294 13.273 57.637 13.273 z"/> 1545 | <path d="M 64.909 20.546 c -5.664 0 -10.272 -4.608 -10.272 -10.273 S 59.245 0 64.909 0 c 5.665 0 10.273 4.608 10.273 10.273 S 70.574 20.546 64.909 20.546 z M 64.909 6 c -2.355 0 -4.272 1.917 -4.272 4.273 s 1.917 4.273 4.272 4.273 c 2.356 0 4.273 -1.917 4.273 -4.273 S 67.266 6 64.909 6 z"/> 1546 | <path d="M 85.813 13.273 h -13.63 c -1.657 0 -3 -1.343 -3 -3 s 1.343 -3 3 -3 h 13.63 c 1.657 0 3 1.343 3 3 S 87.47 13.273 85.813 13.273 z"/> 1547 | <path d="M 85.813 82.728 h -57.79 c -1.657 0 -3 -1.343 -3 -3 s 1.343 -3 3 -3 h 57.79 c 1.657 0 3 1.343 3 3 S 87.47 82.728 85.813 82.728 z"/> 1548 | <path d="M 20.75 90 c -5.665 0 -10.273 -4.608 -10.273 -10.272 c 0 -5.665 4.608 -10.273 10.273 -10.273 s 10.273 4.608 10.273 10.273 C 31.022 85.392 26.414 90 20.75 90 z M 20.75 75.454 c -2.356 0 -4.273 1.917 -4.273 4.273 c 0 2.355 1.917 4.272 4.273 4.272 s 4.273 -1.917 4.273 -4.272 C 25.022 77.371 23.105 75.454 20.75 75.454 z"/> 1549 | <path d="M 13.477 82.728 H 4.188 c -1.657 0 -3 -1.343 -3 -3 s 1.343 -3 3 -3 h 9.289 c 1.657 0 3 1.343 3 3 S 15.133 82.728 13.477 82.728 z"/> 1550 | </svg> 1551 | <span id="analysis-filter-toggle-text">Filters</span> 1552 | </button> 1553 | </div> 1554 | </div> 1555 | 1556 | <!-- Advanced Filter Panel --> 1557 | <div id="analysis-filter-panel" style=" 1558 | display: none; 1559 | background: #f8f9fa; 1560 | border: 1px solid #e9ecef; 1561 | border-radius: 8px; 1562 | padding: 16px; 1563 | margin-bottom: 16px; 1564 | "> 1565 | <div style=" 1566 | display: grid; 1567 | grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); 1568 | gap: 16px; 1569 | margin-bottom: 12px; 1570 | "> 1571 | <div> 1572 | <label style=" 1573 | display: block; 1574 | font-size: 12px; 1575 | font-weight: 600; 1576 | color: #6c757d; 1577 | margin-bottom: 4px; 1578 | ">Filter by Project</label> 1579 | <select id="analysis-project-filter" style=" 1580 | width: 100%; 1581 | padding: 8px 12px; 1582 | border: 1px solid #dee2e6; 1583 | border-radius: 4px; 1584 | font-size: 14px; 1585 | background: white; 1586 | "> 1587 | <option value="">All Projects</option> 1588 | </select> 1589 | </div> 1590 | <div> 1591 | <label style=" 1592 | display: block; 1593 | font-size: 12px; 1594 | font-weight: 600; 1595 | color: #6c757d; 1596 | margin-bottom: 4px; 1597 | ">Filter by Market</label> 1598 | <select id="analysis-market-filter" style=" 1599 | width: 100%; 1600 | padding: 8px 12px; 1601 | border: 1px solid #dee2e6; 1602 | border-radius: 4px; 1603 | font-size: 14px; 1604 | background: white; 1605 | "> 1606 | <option value="all">All Markets</option> 1607 | </select> 1608 | </div> 1609 | <div> 1610 | <label style=" 1611 | display: block; 1612 | font-size: 12px; 1613 | font-weight: 600; 1614 | color: #6c757d; 1615 | margin-bottom: 4px; 1616 | ">Filter by Tags</label> 1617 | <div id="analysis-tags-filter" style=" 1618 | min-height: 32px; 1619 | max-height: 80px; 1620 | overflow-y: auto; 1621 | border: 1px solid #dee2e6; 1622 | border-radius: 4px; 1623 | padding: 8px; 1624 | background: white; 1625 | display: flex; 1626 | flex-wrap: wrap; 1627 | gap: 6px; 1628 | align-items: center; 1629 | "> 1630 | <!-- Tags populated dynamically --> 1631 | </div> 1632 | </div> 1633 | </div> 1634 | <div style=" 1635 | display: flex; 1636 | justify-content: space-between; 1637 | align-items: center; 1638 | "> 1639 | <div id="analysis-filter-summary" style=" 1640 | font-size: 13px; 1641 | color: #6c757d; 1642 | ">No filters applied</div> 1643 | <div style="display: flex; gap: 8px;"> 1644 | <button id="clear-analysis-filters" style=" 1645 | background: #6c757d; 1646 | color: white; 1647 | border: none; 1648 | border-radius: 4px; 1649 | padding: 6px 12px; 1650 | font-size: 12px; 1651 | cursor: pointer; 1652 | ">Clear</button> 1653 | <button id="apply-analysis-filters" style=" 1654 | background: #28a745; 1655 | color: white; 1656 | border: none; 1657 | border-radius: 4px; 1658 | padding: 6px 12px; 1659 | font-size: 12px; 1660 | cursor: pointer; 1661 | ">Apply</button> 1662 | </div> 1663 | </div> 1664 | </div> 1665 | 1666 | <!-- Active Filters Display --> 1667 | <div id="analysis-active-filters" style=" 1668 | display: none; 1669 | margin-bottom: 12px; 1670 | padding: 8px 0; 1671 | "> 1672 | <div style=" 1673 | font-size: 12px; 1674 | font-weight: 600; 1675 | color: #6c757d; 1676 | margin-bottom: 6px; 1677 | ">Active Filters:</div> 1678 | <div id="analysis-filter-chips" style=" 1679 | display: flex; 1680 | flex-wrap: wrap; 1681 | gap: 6px; 1682 | "> 1683 | <!-- Filter chips will be populated here --> 1684 | </div> 1685 | </div> 1686 | </div> 1687 | 1688 | <!-- Analysis Results --> 1689 | <div id="analysis-results"> 1690 | <div style=" 1691 | display: grid; 1692 | grid-template-columns: 1fr 1fr; 1693 | gap: 32px; 1694 | margin-bottom: 32px; 1695 | max-width: 1200px; 1696 | margin: 0 auto 32px auto; 1697 | justify-content: center; 1698 | "> 1699 | <div id="citation-sources-table"></div> 1700 | <div id="review-sources-table"></div> 1701 | </div> 1702 | 1703 | <!-- Sentiment Analysis Section --> 1704 | <div id="sentiment-analysis-section" style=" 1705 | max-width: 600px; 1706 | margin: 0 auto; 1707 | background: white; 1708 | border: 1px solid #e9ecef; 1709 | border-radius: 8px; 1710 | padding: 20px; 1711 | "> 1712 | <h3 style=" 1713 | margin: 0 0 16px 0; 1714 | font-size: 16px; 1715 | font-weight: 600; 1716 | color: #495057; 1717 | ">Sentiment Analysis</h3> 1718 | <div id="sentiment-content"> 1719 | <!-- Populated by analysis --> 1720 | </div> 1721 | </div> 1722 | </div> 1723 | </div> 1724 | </div> 1725 | </div> 1726 | 1727 | <!-- Fixed Footer --> 1728 | <div style=" 1729 | position: fixed; 1730 | bottom: 0; 1731 | left: 0; 1732 | right: 0; 1733 | background: #f8f9fa; 1734 | border-top: 1px solid #e9ecef; 1735 | padding: 8px 0; 1736 | font-size: 14px; 1737 | z-index: 10001; 1738 | display: flex; 1739 | justify-content: center; 1740 | "> 1741 | <div style="display: inline-flex; align-items: center; justify-content: center; gap: 12px; flex-wrap: wrap;"> 1742 | <span>Created by <a href="https://www.martinaberastegue.com/" target="_blank" rel="noopener noreferrer"><strong>Martin Aberastegue</strong></a></span> 1743 | <span style="display: inline-flex; align-items: center; gap: 10px;"> 1744 | <a href="https://www.linkedin.com/in/aberastegue/" target="_blank" rel="noopener noreferrer" aria-label="Martin Aberastegue on LinkedIn"> 1745 | <img src="${linkedinIconUrl}" alt="LinkedIn logo" style="width: 18px; height: 18px; display: block;" /> 1746 | </a> 1747 | <a href="https://github.com/Xyborg" target="_blank" rel="noopener noreferrer" aria-label="Martin Aberastegue on GitHub"> 1748 | <img src="${githubIconUrl}" alt="GitHub logo" style="width: 18px; height: 18px; display: block;" /> 1749 | </a> 1750 | <a href="https://x.com/Xyborg" target="_blank" rel="noopener noreferrer" aria-label="Martin Aberastegue on X"> 1751 | <img src="${xIconUrl}" alt="X logo" style="width: 18px; height: 18px; display: block;" /> 1752 | </a> 1753 | </span> 1754 | </div> 1755 | </div> 1756 | </div> <!-- End Main Content Area --> 1757 | </div> <!-- End Sidebar + Content Container --> 1758 | </div> 1759 | </div> 1760 | `; 1761 | 1762 | // Tutorial Overlay HTML 1763 | const tutorialHTML = ` 1764 | <div id="tutorial-overlay" style=" 1765 | position: absolute; 1766 | top: 0; 1767 | left: 0; 1768 | width: 100%; 1769 | height: 100%; 1770 | background: rgba(0, 0, 0, 0.85); 1771 | z-index: 10001; 1772 | display: flex; 1773 | align-items: center; 1774 | justify-content: center; 1775 | backdrop-filter: blur(3px); 1776 | -webkit-backdrop-filter: blur(3px); 1777 | "> 1778 | <div style=" 1779 | background: white; 1780 | max-width: 600px; 1781 | width: 90%; 1782 | border-radius: 12px; 1783 | box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5); 1784 | overflow: hidden; 1785 | "> 1786 | <!-- Tutorial content screens --> 1787 | <div id="tutorial-screens"> 1788 | <!-- Screen 1: Welcome --> 1789 | <div class="tutorial-screen" data-screen="1" style="display: block;"> 1790 | <div style=" 1791 | background: linear-gradient(135deg, #5b8def 0%, #4a7de8 100%); 1792 | padding: 48px 32px; 1793 | text-align: center; 1794 | color: white; 1795 | "> 1796 | <div style=" 1797 | width: 80px; 1798 | height: 80px; 1799 | margin: 0 auto 20px; 1800 | border-radius: 50%; 1801 | display: flex; 1802 | align-items: center; 1803 | justify-content: center; 1804 | font-size: 40px; 1805 | "><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><path d="M40.49,205.52,93,61.14a7.79,7.79,0,0,1,12.84-2.85l91.88,91.88A7.79,7.79,0,0,1,194.86,163L50.48,215.51A7.79,7.79,0,0,1,40.49,205.52Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M168,72s0-24,24-24,24-24,24-24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="144" y1="16" x2="144" y2="40" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="216" y1="112" x2="232" y2="128" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="216" y1="80" x2="240" y2="72" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="78.09" y1="102.09" x2="153.91" y2="177.91" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="101.11" y1="197.11" x2="58.89" y2="154.89" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg></div> 1806 | <h2 style="margin: 0 0 12px 0; font-size: 28px; font-weight: 700;">Welcome!</h2> 1807 | <p style="margin: 0; font-size: 16px; opacity: 0.95; line-height: 1.5;"> 1808 | Let's take a quick tour of <strong>ChatGPT E-commerce Product Research</strong> 1809 | </p> 1810 | </div> 1811 | <div style="padding: 32px;"> 1812 | <p style=" 1813 | font-size: 15px; 1814 | line-height: 1.6; 1815 | color: #495057; 1816 | margin: 0 0 24px 0; 1817 | "> 1818 | This extension lets you search for product reviews, comparisons, and detailed information 1819 | directly within ChatGPT. It's powerful yet simple to use! 1820 | </p> 1821 | <div style=" 1822 | background: #f8f9fa; 1823 | padding: 16px; 1824 | border-radius: 4px; 1825 | "> 1826 | <p style="margin: 0; font-size: 14px; color: #6c757d;"> 1827 | This tutorial takes about 30 seconds. 1828 | You can skip it, but I recommend going through it once. 1829 | </p> 1830 | </div> 1831 | </div> 1832 | </div> 1833 | 1834 | <!-- Screen 2: The Bubble --> 1835 | <div class="tutorial-screen" data-screen="2" style="display: none;"> 1836 | <div style=" 1837 | background: linear-gradient(135deg, #5b8def 0%, #4a7de8 100%); 1838 | padding: 40px 32px; 1839 | text-align: center; 1840 | color: white; 1841 | "> 1842 | <h2 style="margin: 0 0 12px 0; font-size: 24px; font-weight: 700;">The Yellow Bubble</h2> 1843 | <p style="margin: 0; font-size: 15px; opacity: 0.95;">Your quick access button</p> 1844 | </div> 1845 | <div style="padding: 32px;"> 1846 | <div style=" 1847 | background: #fffaed; 1848 | border: 2px solid #ffd43b; 1849 | border-radius: 8px; 1850 | padding: 24px; 1851 | margin-bottom: 20px; 1852 | text-align: center; 1853 | "> 1854 | <div style=" 1855 | width: 56px; 1856 | height: 56px; 1857 | background: #ffd43b; 1858 | border-radius: 50%; 1859 | margin: 0 auto 12px; 1860 | display: flex; 1861 | align-items: center; 1862 | justify-content: center; 1863 | font-size: 28px; 1864 | box-shadow: 0 4px 12px rgba(255, 212, 59, 0.4); 1865 | "><svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="#000000" viewBox="0 0 256 256"><path d="M232,48V88a8,8,0,0,1-16,0V56H184a8,8,0,0,1,0-16h40A8,8,0,0,1,232,48ZM72,200H40V168a8,8,0,0,0-16,0v40a8,8,0,0,0,8,8H72a8,8,0,0,0,0-16Zm152-40a8,8,0,0,0-8,8v32H184a8,8,0,0,0,0,16h40a8,8,0,0,0,8-8V168A8,8,0,0,0,224,160ZM32,96a8,8,0,0,0,8-8V56H72a8,8,0,0,0,0-16H32a8,8,0,0,0-8,8V88A8,8,0,0,0,32,96ZM80,80a8,8,0,0,0-8,8v80a8,8,0,0,0,16,0V88A8,8,0,0,0,80,80Zm104,88V88a8,8,0,0,0-16,0v80a8,8,0,0,0,16,0ZM144,80a8,8,0,0,0-8,8v80a8,8,0,0,0,16,0V88A8,8,0,0,0,144,80Zm-32,0a8,8,0,0,0-8,8v80a8,8,0,0,0,16,0V88A8,8,0,0,0,112,80Z"></path></svg></div> 1866 | <p style="margin: 0; font-size: 14px; color: #856404; font-weight: 500;"> 1867 | Look for this button in the bottom-right corner! 1868 | </p> 1869 | </div> 1870 | <p style=" 1871 | font-size: 15px; 1872 | line-height: 1.6; 1873 | color: #495057; 1874 | margin: 0; 1875 | "> 1876 | Click the <strong>yellow floating bubble</strong> anytime on ChatGPT to open this search modal. 1877 | You can also click the extension icon in your browser toolbar. 1878 | </p> 1879 | </div> 1880 | </div> 1881 | 1882 | <!-- Screen 3: Key Features --> 1883 | <div class="tutorial-screen" data-screen="3" style="display: none;"> 1884 | <div style=" 1885 | background: linear-gradient(135deg, #5b8def 0%, #4a7de8 100%); 1886 | padding: 40px 32px; 1887 | text-align: center; 1888 | color: white; 1889 | "> 1890 | <h2 style="margin: 0 0 12px 0; font-size: 24px; font-weight: 700;">Key Features</h2> 1891 | <p style="margin: 0; font-size: 15px; opacity: 0.95;">Everything you need to know</p> 1892 | </div> 1893 | <div style="padding: 32px;"> 1894 | <div style="display: flex; flex-direction: column; gap: 16px;"> 1895 | <div style="display: flex; gap: 12px; align-items: start;"> 1896 | <div style=" 1897 | width: 32px; 1898 | height: 32px; 1899 | background: #5b8def; 1900 | border-radius: 50%; 1901 | flex-shrink: 0; 1902 | display: flex; 1903 | align-items: center; 1904 | justify-content: center; 1905 | color: white; 1906 | font-weight: 700; 1907 | ">1</div> 1908 | <div style="flex: 1;"> 1909 | <h4 style="margin: 0 0 4px 0; color: #495057; font-size: 15px;">Search Tab</h4> 1910 | <p style="margin: 0; color: #6c757d; font-size: 14px; line-height: 1.4;"> 1911 | Search single products or compare multiple products side-by-side 1912 | </p> 1913 | </div> 1914 | </div> 1915 | <div style="display: flex; gap: 12px; align-items: start;"> 1916 | <div style=" 1917 | width: 32px; 1918 | height: 32px; 1919 | background: #5b8def; 1920 | border-radius: 50%; 1921 | flex-shrink: 0; 1922 | display: flex; 1923 | align-items: center; 1924 | justify-content: center; 1925 | color: white; 1926 | font-weight: 700; 1927 | ">2</div> 1928 | <div style="flex: 1;"> 1929 | <h4 style="margin: 0 0 4px 0; color: #495057; font-size: 15px;">History Tab</h4> 1930 | <p style="margin: 0; color: #6c757d; font-size: 14px; line-height: 1.4;"> 1931 | All searches are auto-saved. Filter by projects & tags to organize your work 1932 | </p> 1933 | </div> 1934 | </div> 1935 | <div style="display: flex; gap: 12px; align-items: start;"> 1936 | <div style=" 1937 | width: 32px; 1938 | height: 32px; 1939 | background: #5b8def; 1940 | border-radius: 50%; 1941 | flex-shrink: 0; 1942 | display: flex; 1943 | align-items: center; 1944 | justify-content: center; 1945 | color: white; 1946 | font-weight: 700; 1947 | ">3</div> 1948 | <div style="flex: 1;"> 1949 | <h4 style="margin: 0 0 4px 0; color: #495057; font-size: 15px;">Analysis Tab</h4> 1950 | <p style="margin: 0; color: #6c757d; font-size: 14px; line-height: 1.4;"> 1951 | See which sources provide reviews and citations across your searches 1952 | </p> 1953 | </div> 1954 | </div> 1955 | <div style="display: flex; gap: 12px; align-items: start;"> 1956 | <div style=" 1957 | width: 32px; 1958 | height: 32px; 1959 | background: #5b8def; 1960 | border-radius: 50%; 1961 | flex-shrink: 0; 1962 | display: flex; 1963 | align-items: center; 1964 | justify-content: center; 1965 | color: white; 1966 | font-weight: 700; 1967 | ">4</div> 1968 | <div style="flex: 1;"> 1969 | <h4 style="margin: 0 0 4px 0; color: #495057; font-size: 15px;">Left Sidebar</h4> 1970 | <p style="margin: 0; color: #6c757d; font-size: 14px; line-height: 1.4;"> 1971 | Create projects & tags to organize searches. Use filters to narrow down results 1972 | </p> 1973 | </div> 1974 | </div> 1975 | </div> 1976 | </div> 1977 | </div> 1978 | 1979 | <!-- Screen 4: Ready to Start --> 1980 | <div class="tutorial-screen" data-screen="4" style="display: none;"> 1981 | <div style=" 1982 | background: linear-gradient(135deg, #28a745 0%, #20914a 100%); 1983 | padding: 48px 32px; 1984 | text-align: center; 1985 | color: white; 1986 | "> 1987 | <div style=" 1988 | width: 80px; 1989 | height: 80px; 1990 | margin: 0 auto 20px; 1991 | border-radius: 50%; 1992 | display: flex; 1993 | align-items: center; 1994 | justify-content: center; 1995 | font-size: 40px; 1996 | "><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><path d="M191.11,112.89c24-24,25.5-52.55,24.75-65.28a8,8,0,0,0-7.47-7.47c-12.73-.75-41.26.73-65.28,24.75L80,128l48,48Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M136,72H74.35a8,8,0,0,0-5.65,2.34L34.35,108.69a8,8,0,0,0,4.53,13.57L80,128" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M184,120v61.65a8,8,0,0,1-2.34,5.65l-34.35,34.35a8,8,0,0,1-13.57-4.53L128,176" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M94.56,187.82C90.69,196.31,77.65,216,40,216c0-37.65,19.69-50.69,28.18-54.56" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg></div> 1997 | <h2 style="margin: 0 0 12px 0; font-size: 28px; font-weight: 700;">You're All Set!</h2> 1998 | <p style="margin: 0; font-size: 16px; opacity: 0.95; line-height: 1.5;"> 1999 | Start searching for products right away 2000 | </p> 2001 | </div> 2002 | <div style="padding: 32px;"> 2003 | <div style=" 2004 | background: #f8f9fa; 2005 | border-radius: 8px; 2006 | padding: 20px; 2007 | margin-bottom: 20px; 2008 | "> 2009 | <h4 style="margin: 0 0 12px 0; color: #495057; font-size: 16px;">Try searching for:</h4> 2010 | <ul style="margin: 0; padding-left: 20px; color: #6c757d; font-size: 14px; line-height: 1.8;"> 2011 | <li>"iPhone 17 Pro camera quality"</li> 2012 | <li>"best wireless headphones 2025"</li> 2013 | <li>"Pets Deli Hundefutter"</li> 2014 | </ul> 2015 | </div> 2016 | <p style=" 2017 | font-size: 13px; 2018 | color: #6c757d; 2019 | text-align: center; 2020 | margin: 0; 2021 | "> 2022 | 💡 You can re-watch this tutorial anytime by clicking the <strong>?</strong> icon in the sidebar 2023 | </p> 2024 | </div> 2025 | </div> 2026 | </div> 2027 | 2028 | <!-- Tutorial Navigation --> 2029 | <div style=" 2030 | border-top: 1px solid #e9ecef; 2031 | padding: 20px 32px; 2032 | display: flex; 2033 | justify-content: space-between; 2034 | align-items: center; 2035 | background: #f8f9fa; 2036 | "> 2037 | <button id="tutorial-skip" style=" 2038 | background: none; 2039 | border: none; 2040 | color: #6c757d; 2041 | font-size: 14px; 2042 | cursor: pointer; 2043 | padding: 8px 12px; 2044 | border-radius: 4px; 2045 | transition: all 0.2s; 2046 | ">Skip Tutorial</button> 2047 | 2048 | <div style="display: flex; gap: 8px;"> 2049 | <div class="tutorial-dot" data-dot="1" style=" 2050 | width: 8px; 2051 | height: 8px; 2052 | border-radius: 50%; 2053 | background: #5b8def; 2054 | cursor: pointer; 2055 | transition: all 0.2s; 2056 | "></div> 2057 | <div class="tutorial-dot" data-dot="2" style=" 2058 | width: 8px; 2059 | height: 8px; 2060 | border-radius: 50%; 2061 | background: #dee2e6; 2062 | cursor: pointer; 2063 | transition: all 0.2s; 2064 | "></div> 2065 | <div class="tutorial-dot" data-dot="3" style=" 2066 | width: 8px; 2067 | height: 8px; 2068 | border-radius: 50%; 2069 | background: #dee2e6; 2070 | cursor: pointer; 2071 | transition: all 0.2s; 2072 | "></div> 2073 | <div class="tutorial-dot" data-dot="4" style=" 2074 | width: 8px; 2075 | height: 8px; 2076 | border-radius: 50%; 2077 | background: #dee2e6; 2078 | cursor: pointer; 2079 | transition: all 0.2s; 2080 | "></div> 2081 | </div> 2082 | 2083 | <div style="display: flex; gap: 12px;"> 2084 | <button id="tutorial-prev" style=" 2085 | background: white; 2086 | border: 1px solid #dee2e6; 2087 | color: #495057; 2088 | font-size: 14px; 2089 | font-weight: 500; 2090 | cursor: pointer; 2091 | padding: 8px 16px; 2092 | border-radius: 6px; 2093 | transition: all 0.2s; 2094 | display: none; 2095 | ">Previous</button> 2096 | <button id="tutorial-next" style=" 2097 | background: #5b8def; 2098 | border: none; 2099 | color: white; 2100 | font-size: 14px; 2101 | font-weight: 500; 2102 | cursor: pointer; 2103 | padding: 8px 20px; 2104 | border-radius: 6px; 2105 | transition: all 0.2s; 2106 | ">Next</button> 2107 | </div> 2108 | </div> 2109 | </div> 2110 | </div> 2111 | `; 2112 | 2113 | const ACCEPT_LANGUAGE_FALLBACK = 'en;q=0.8, es-AR;q=0.7, es;q=0.6, 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'; 2114 | const MARKET_OPTIONS = [ 2115 | { value: 'de-DE', label: 'Deutschland (Deutsch)', country: 'Deutschland', language: 'Deutsch', code: 'DE', acceptLanguagePrefix: 'de-DE, de;q=0.9', oaiLanguage: 'de-DE', icon: 'assets/flags/de.svg' }, 2116 | { value: 'en-DE', label: 'Germany (English)', country: 'Germany', language: 'English', code: 'DE', acceptLanguagePrefix: 'en-DE, en;q=0.9', oaiLanguage: 'en-DE', icon: 'assets/flags/de.svg' }, 2117 | { value: 'tr-DE', label: 'Almanya (Türkçe)', country: 'Almanya', language: 'Türkçe', code: 'DE', acceptLanguagePrefix: 'tr-DE, tr;q=0.9', oaiLanguage: 'tr-DE', icon: 'assets/flags/de.svg' }, 2118 | { value: 'de-AT', label: 'Österreich (Deutsch)', country: 'Österreich', language: 'Deutsch', code: 'AT', acceptLanguagePrefix: 'de-AT, de;q=0.9', oaiLanguage: 'de-AT', icon: 'assets/flags/at.svg' }, 2119 | { value: 'de-CH', label: 'Schweiz (Deutsch)', country: 'Schweiz', language: 'Deutsch', code: 'CH', acceptLanguagePrefix: 'de-CH, de;q=0.9', oaiLanguage: 'de-CH', icon: 'assets/flags/ch.svg' }, 2120 | { value: 'fr-CH', label: 'Suisse (Français)', country: 'Suisse', language: 'Français', code: 'CH', acceptLanguagePrefix: 'fr-CH, fr;q=0.9', oaiLanguage: 'fr-CH', icon: 'assets/flags/ch.svg' }, 2121 | { value: 'de-BE', label: 'Belgien (Deutsch)', country: 'Belgien', language: 'Deutsch', code: 'BE', acceptLanguagePrefix: 'de-BE, de;q=0.9', oaiLanguage: 'de-BE', icon: 'assets/flags/be.svg' }, 2122 | { value: 'fr-BE', label: 'Belgique (Français)', country: 'Belgique', language: 'Français', code: 'BE', acceptLanguagePrefix: 'fr-BE, fr;q=0.9', oaiLanguage: 'fr-BE', icon: 'assets/flags/be.svg' }, 2123 | { value: 'nl-BE', label: 'België (Nederlands)', country: 'België', language: 'Nederlands', code: 'BE', acceptLanguagePrefix: 'nl-BE, nl;q=0.9', oaiLanguage: 'nl-BE', icon: 'assets/flags/be.svg' }, 2124 | { value: 'fr-FR', label: 'France (Français)', country: 'France', language: 'Français', code: 'FR', acceptLanguagePrefix: 'fr-FR, fr;q=0.9', oaiLanguage: 'fr-FR', icon: 'assets/flags/fr.svg' }, 2125 | { value: 'es-ES', label: 'España (Español)', country: 'España', language: 'Español', code: 'ES', acceptLanguagePrefix: 'es-ES, es;q=0.9', oaiLanguage: 'es-ES', icon: 'assets/flags/es.svg' }, 2126 | { value: 'es-AR', label: 'Argentina (Español)', country: 'Argentina', language: 'Español', code: 'AR', acceptLanguagePrefix: 'es-AR, es;q=0.9', oaiLanguage: 'es-AR', icon: 'assets/flags/ar.svg' }, 2127 | { value: 'es-MX', label: 'México (Español)', country: 'México', language: 'Español', code: 'MX', acceptLanguagePrefix: 'es-MX, es;q=0.9', oaiLanguage: 'es-MX', icon: 'assets/flags/mx.svg' }, 2128 | { value: 'nl-NL', label: 'Nederland (Nederlands)', country: 'Nederland', language: 'Nederlands', code: 'NL', acceptLanguagePrefix: 'nl-NL, nl;q=0.9', oaiLanguage: 'nl-NL', icon: 'assets/flags/nl.svg' }, 2129 | { value: 'sv-SE', label: 'Sverige (Svenska)', country: 'Sverige', language: 'Svenska', code: 'SE', acceptLanguagePrefix: 'sv-SE, sv;q=0.9', oaiLanguage: 'sv-SE', icon: 'assets/flags/se.svg' }, 2130 | { value: 'it-IT', label: 'Italia (Italiano)', country: 'Italia', language: 'Italiano', code: 'IT', acceptLanguagePrefix: 'it-IT, it;q=0.9', oaiLanguage: 'it-IT', icon: 'assets/flags/it.svg' }, 2131 | { value: 'pt-PT', label: 'Portugal (Português)', country: 'Portugal', language: 'Português', code: 'PT', acceptLanguagePrefix: 'pt-PT, pt;q=0.9', oaiLanguage: 'pt-PT', icon: 'assets/flags/pt.svg' }, 2132 | { value: 'pt-BR', label: 'Brasil (Português)', country: 'Brasil', language: 'Português', code: 'BR', acceptLanguagePrefix: 'pt-BR, pt;q=0.9', oaiLanguage: 'pt-BR', icon: 'assets/flags/br.svg' }, 2133 | { value: 'en-GB', label: 'United Kingdom (English)', country: 'United Kingdom', language: 'English', code: 'GB', acceptLanguagePrefix: 'en-GB, en;q=0.9', oaiLanguage: 'en-GB', icon: 'assets/flags/gb.svg' }, 2134 | { value: 'en-US', label: 'United States (English)', country: 'United States', language: 'English', code: 'US', acceptLanguagePrefix: 'en-US, en;q=0.9', oaiLanguage: 'en-US', icon: 'assets/flags/us.svg' } 2135 | ]; 2136 | 2137 | function getMarketOption(value) { 2138 | const match = MARKET_OPTIONS.find(option => option.value === value); 2139 | return match || MARKET_OPTIONS[0]; 2140 | } 2141 | 2142 | function buildAcceptLanguage(option) { 2143 | const prefixParts = option.acceptLanguagePrefix.split(',').map(part => part.trim().toLowerCase()); 2144 | const fallbackParts = ACCEPT_LANGUAGE_FALLBACK.split(',').map(part => part.trim()); 2145 | const filteredFallback = fallbackParts.filter(part => !prefixParts.includes(part.toLowerCase())); 2146 | return [option.acceptLanguagePrefix, filteredFallback.join(', ')].filter(Boolean).join(', '); 2147 | } 2148 | 2149 | function updateMarketSelectorDisplay(value) { 2150 | const option = getMarketOption(value); 2151 | const flagEl = document.getElementById('market-select-flag'); 2152 | const countryEl = document.getElementById('market-select-country'); 2153 | const languageEl = document.getElementById('market-select-language'); 2154 | const container = document.getElementById('market-select-container'); 2155 | const trigger = document.getElementById('market-select-trigger'); 2156 | if (flagEl) { 2157 | flagEl.src = chrome.runtime.getURL(option.icon); 2158 | flagEl.alt = `${option.country} flag`; 2159 | } 2160 | if (countryEl) { 2161 | countryEl.textContent = option.country; 2162 | } 2163 | if (languageEl) { 2164 | languageEl.textContent = option.language; 2165 | } 2166 | if (container) { 2167 | container.title = `${option.country} • ${option.language}`; 2168 | } 2169 | if (trigger) { 2170 | trigger.setAttribute('aria-label', `${option.country}, ${option.language}`); 2171 | } 2172 | 2173 | updateMarketDropdownSelection(option.value); 2174 | updateAnalysisFilterSummary(); 2175 | updateAnalysisFilterChips(); 2176 | } 2177 | 2178 | function updateMarketDropdownSelection(value) { 2179 | const dropdown = document.getElementById('market-select-dropdown'); 2180 | if (!dropdown) { 2181 | return; 2182 | } 2183 | dropdown.querySelectorAll('.market-select-option').forEach(optionEl => { 2184 | const isSelected = optionEl.getAttribute('data-value') === value; 2185 | optionEl.setAttribute('aria-selected', String(isSelected)); 2186 | optionEl.classList.toggle('market-select-option--selected', isSelected); 2187 | }); 2188 | } 2189 | 2190 | function initializeMarketDropdownControls() { 2191 | const container = document.getElementById('market-select-container'); 2192 | const dropdown = document.getElementById('market-select-dropdown'); 2193 | const trigger = document.getElementById('market-select-trigger'); 2194 | const marketSelect = document.getElementById('market-select'); 2195 | if (!container || !dropdown || !trigger || !marketSelect) { 2196 | return; 2197 | } 2198 | 2199 | dropdown.innerHTML = ''; 2200 | 2201 | MARKET_OPTIONS.forEach((option, index) => { 2202 | const optionButton = document.createElement('button'); 2203 | optionButton.type = 'button'; 2204 | optionButton.className = 'market-select-option'; 2205 | optionButton.setAttribute('role', 'option'); 2206 | optionButton.setAttribute('aria-selected', 'false'); 2207 | optionButton.setAttribute('data-value', option.value); 2208 | optionButton.setAttribute('data-index', String(index)); 2209 | const iconUrl = chrome.runtime.getURL(option.icon); 2210 | optionButton.innerHTML = ` 2211 | <img src="${iconUrl}" alt="${option.country} flag" class="market-select-option-flag" /> 2212 | <span class="market-select-option-text"> 2213 | <span class="market-select-option-country">${option.country}</span> 2214 | <span class="market-select-option-language">${option.language}</span> 2215 | </span> 2216 | `; 2217 | optionButton.addEventListener('click', () => { 2218 | selectOption(option.value); 2219 | }); 2220 | dropdown.appendChild(optionButton); 2221 | }); 2222 | 2223 | const optionButtons = Array.from(dropdown.querySelectorAll('.market-select-option')); 2224 | let isOpen = false; 2225 | let activeIndex = -1; 2226 | const outsideClickOptions = { capture: true }; 2227 | 2228 | function setActiveIndex(index) { 2229 | if (index < 0 || index >= optionButtons.length) { 2230 | return; 2231 | } 2232 | activeIndex = index; 2233 | optionButtons[index].focus(); 2234 | } 2235 | 2236 | function openDropdown() { 2237 | if (isOpen) { 2238 | return; 2239 | } 2240 | dropdown.classList.add('open'); 2241 | trigger.setAttribute('aria-expanded', 'true'); 2242 | isOpen = true; 2243 | const currentValue = marketSelect.value || MARKET_OPTIONS[0].value; 2244 | const currentIndex = optionButtons.findIndex(btn => btn.getAttribute('data-value') === currentValue); 2245 | setActiveIndex(currentIndex >= 0 ? currentIndex : 0); 2246 | document.addEventListener('mousedown', handleOutsideClick, outsideClickOptions); 2247 | } 2248 | 2249 | function closeDropdown() { 2250 | if (!isOpen) { 2251 | return; 2252 | } 2253 | dropdown.classList.remove('open'); 2254 | trigger.setAttribute('aria-expanded', 'false'); 2255 | isOpen = false; 2256 | activeIndex = -1; 2257 | document.removeEventListener('mousedown', handleOutsideClick, outsideClickOptions); 2258 | } 2259 | 2260 | function selectOption(value) { 2261 | setMarketSelection(value); 2262 | closeDropdown(); 2263 | trigger.focus(); 2264 | } 2265 | 2266 | function handleOutsideClick(event) { 2267 | if (!container.contains(event.target)) { 2268 | closeDropdown(); 2269 | } 2270 | } 2271 | 2272 | function focusNext(offset) { 2273 | if (!isOpen) { 2274 | return; 2275 | } 2276 | if (activeIndex === -1) { 2277 | setActiveIndex(offset >= 0 ? 0 : optionButtons.length - 1); 2278 | return; 2279 | } 2280 | const nextIndex = activeIndex + offset; 2281 | if (nextIndex < 0) { 2282 | setActiveIndex(0); 2283 | } else if (nextIndex >= optionButtons.length) { 2284 | setActiveIndex(optionButtons.length - 1); 2285 | } else { 2286 | setActiveIndex(nextIndex); 2287 | } 2288 | } 2289 | 2290 | function handleTriggerKeydown(event) { 2291 | if (event.key === 'ArrowDown') { 2292 | event.preventDefault(); 2293 | if (!isOpen) { 2294 | openDropdown(); 2295 | } else { 2296 | focusNext(1); 2297 | } 2298 | } else if (event.key === 'ArrowUp') { 2299 | event.preventDefault(); 2300 | if (!isOpen) { 2301 | openDropdown(); 2302 | } else { 2303 | focusNext(-1); 2304 | } 2305 | } else if (event.key === 'Enter' || event.key === ' ') { 2306 | event.preventDefault(); 2307 | if (isOpen) { 2308 | closeDropdown(); 2309 | } else { 2310 | openDropdown(); 2311 | } 2312 | } else if (event.key === 'Escape' && isOpen) { 2313 | event.preventDefault(); 2314 | closeDropdown(); 2315 | } 2316 | } 2317 | 2318 | function handleOptionKeydown(event, index) { 2319 | switch (event.key) { 2320 | case 'ArrowDown': 2321 | event.preventDefault(); 2322 | focusNext(1); 2323 | break; 2324 | case 'ArrowUp': 2325 | event.preventDefault(); 2326 | focusNext(-1); 2327 | break; 2328 | case 'Home': 2329 | event.preventDefault(); 2330 | setActiveIndex(0); 2331 | break; 2332 | case 'End': 2333 | event.preventDefault(); 2334 | setActiveIndex(optionButtons.length - 1); 2335 | break; 2336 | case 'Enter': 2337 | case ' ': 2338 | event.preventDefault(); 2339 | selectOption(optionButtons[index].getAttribute('data-value')); 2340 | break; 2341 | case 'Escape': 2342 | event.preventDefault(); 2343 | closeDropdown(); 2344 | trigger.focus(); 2345 | break; 2346 | default: 2347 | break; 2348 | } 2349 | } 2350 | 2351 | trigger.addEventListener('click', (event) => { 2352 | event.preventDefault(); 2353 | if (isOpen) { 2354 | closeDropdown(); 2355 | } else { 2356 | openDropdown(); 2357 | } 2358 | }); 2359 | 2360 | trigger.addEventListener('keydown', handleTriggerKeydown); 2361 | 2362 | optionButtons.forEach((button, index) => { 2363 | button.addEventListener('keydown', (event) => handleOptionKeydown(event, index)); 2364 | }); 2365 | 2366 | container.addEventListener('focusout', (event) => { 2367 | if (!isOpen) { 2368 | return; 2369 | } 2370 | const nextFocus = event.relatedTarget; 2371 | if (!nextFocus || !container.contains(nextFocus)) { 2372 | closeDropdown(); 2373 | } 2374 | }); 2375 | 2376 | updateMarketDropdownSelection(marketSelect.value || MARKET_OPTIONS[0].value); 2377 | } 2378 | 2379 | function setMarketSelection(value) { 2380 | const option = getMarketOption(value); 2381 | const marketSelect = document.getElementById('market-select'); 2382 | if (marketSelect) { 2383 | marketSelect.value = option.value; 2384 | } 2385 | localStorage.setItem('chatgpt-product-search-market', option.value); 2386 | updateMarketSelectorDisplay(option.value); 2387 | return option; 2388 | } 2389 | 2390 | function getSelectedMarketSettings() { 2391 | const marketSelect = document.getElementById('market-select'); 2392 | const storedValue = localStorage.getItem('chatgpt-product-search-market'); 2393 | const selectedValue = marketSelect ? marketSelect.value : storedValue; 2394 | const option = getMarketOption(selectedValue); 2395 | return { 2396 | ...option, 2397 | acceptLanguage: buildAcceptLanguage(option) 2398 | }; 2399 | } 2400 | 2401 | function moveMarketSelector(isMultiMode) { 2402 | const marketSelectContainer = document.getElementById('market-select-container'); 2403 | const singleInputGroup = document.getElementById('single-input-group'); 2404 | const divider = document.getElementById('market-input-divider'); 2405 | const multiMarketMount = document.getElementById('multi-market-select-mount'); 2406 | if (!marketSelectContainer) { 2407 | return; 2408 | } 2409 | 2410 | if (isMultiMode) { 2411 | if (divider) { 2412 | divider.style.display = 'none'; 2413 | } 2414 | if (multiMarketMount) { 2415 | multiMarketMount.style.display = 'flex'; 2416 | multiMarketMount.appendChild(marketSelectContainer); 2417 | } 2418 | } else { 2419 | if (singleInputGroup) { 2420 | if (divider) { 2421 | divider.style.display = 'inline-flex'; 2422 | singleInputGroup.insertBefore(marketSelectContainer, divider.nextSibling); 2423 | } else { 2424 | singleInputGroup.appendChild(marketSelectContainer); 2425 | } 2426 | } 2427 | if (multiMarketMount) { 2428 | multiMarketMount.style.display = 'none'; 2429 | } 2430 | } 2431 | } 2432 | 2433 | function renderMarketBadge(marketValue, marketCode, marketLabel) { 2434 | if (!marketValue) { 2435 | return ''; 2436 | } 2437 | const option = getMarketOption(marketValue); 2438 | if (!option) { 2439 | return ''; 2440 | } 2441 | let countryText = option.country; 2442 | if (typeof marketLabel === 'string' && marketLabel.length) { 2443 | if (marketLabel.includes('(')) { 2444 | const parsed = marketLabel.split('(')[0].trim(); 2445 | if (parsed) { 2446 | countryText = parsed; 2447 | } 2448 | } else if (!marketLabel.includes('/')) { 2449 | countryText = marketLabel; 2450 | } 2451 | } 2452 | const codeText = marketCode || option.code; 2453 | const languageText = codeText ? `${option.language}` : option.language; 2454 | const flagUrl = chrome.runtime.getURL(option.icon); 2455 | return `<span style="display:inline-flex;align-items:center;gap:8px;"><img src="${flagUrl}" alt="${countryText} flag" style="width:16px;height:12px;object-fit:cover;border-radius:2px;flex-shrink:0;" /><span style="display:flex;flex-direction:column;gap:2px;min-width:0;"><span style="font-weight:600;color:#343a40;line-height:1.1;">${countryText}</span><span style="font-size:12px;color:#6c757d;line-height:1.1;">${languageText}</span></span></span>`; 2456 | } 2457 | 2458 | // Function to create and show modal 2459 | function createModal() { 2460 | // Remove existing modal if present 2461 | const existingModal = document.getElementById('chatgpt-product-search-modal'); 2462 | if (existingModal) { 2463 | existingModal.remove(); 2464 | } 2465 | 2466 | // Inject modal into page 2467 | document.body.insertAdjacentHTML('beforeend', modalHTML); 2468 | 2469 | // Get modal elements 2470 | const modal = document.getElementById('chatgpt-product-search-modal'); 2471 | const closeBtn = document.getElementById('close-modal-btn'); 2472 | const searchBtn = document.getElementById('search-btn'); 2473 | const multiSearchBtn = document.getElementById('multi-search-btn'); 2474 | const searchQuery = document.getElementById('search-query'); 2475 | const multiSearchQuery = document.getElementById('multi-search-query'); 2476 | const authToken = document.getElementById('auth-token'); 2477 | const resultsContainer = document.getElementById('results-container'); 2478 | const multiProductToggle = document.getElementById('multi-product-toggle'); 2479 | const toggleBackground = document.getElementById('toggle-background'); 2480 | const toggleSlider = document.getElementById('toggle-slider'); 2481 | const singleProductInput = document.getElementById('single-product-input'); 2482 | const multiProductInput = document.getElementById('multi-product-input'); 2483 | const marketSelect = document.getElementById('market-select'); 2484 | const marketSelectContainer = document.getElementById('market-select-container'); 2485 | const multiMarketMount = document.getElementById('multi-market-select-mount'); 2486 | const singleInputGroup = document.getElementById('single-input-group'); 2487 | const collapseToggle = document.getElementById('collapse-toggle'); 2488 | const collapseText = document.getElementById('collapse-text'); 2489 | const searchControls = document.getElementById('search-controls'); 2490 | const historyDetailBack = document.getElementById('history-detail-back'); 2491 | const historyDetailOpenSearch = document.getElementById('history-detail-open-search'); 2492 | 2493 | if (marketSelect) { 2494 | marketSelect.innerHTML = ''; 2495 | MARKET_OPTIONS.forEach(option => { 2496 | const optionEl = document.createElement('option'); 2497 | optionEl.value = option.value; 2498 | optionEl.textContent = `${option.label}`; 2499 | marketSelect.appendChild(optionEl); 2500 | }); 2501 | const savedValue = localStorage.getItem('chatgpt-product-search-market') || MARKET_OPTIONS[0].value; 2502 | marketSelect.value = savedValue; 2503 | initializeMarketDropdownControls(); 2504 | setMarketSelection(savedValue); 2505 | marketSelect.addEventListener('change', () => { 2506 | setMarketSelection(marketSelect.value); 2507 | }); 2508 | } else { 2509 | const savedValue = localStorage.getItem('chatgpt-product-search-market') || MARKET_OPTIONS[0].value; 2510 | setMarketSelection(savedValue); 2511 | } 2512 | 2513 | if (marketSelectContainer) { 2514 | marketSelectContainer.style.height = '100%'; 2515 | } 2516 | 2517 | moveMarketSelector(multiProductToggle ? multiProductToggle.checked : false); 2518 | 2519 | // Close modal functionality 2520 | closeBtn.addEventListener('click', () => { 2521 | modal.remove(); 2522 | }); 2523 | 2524 | // Close on outside click 2525 | modal.addEventListener('click', (e) => { 2526 | if (e.target === modal) { 2527 | modal.remove(); 2528 | } 2529 | }); 2530 | 2531 | // Close on Escape key 2532 | document.addEventListener('keydown', (e) => { 2533 | if (e.key === 'Escape') { 2534 | modal.remove(); 2535 | } 2536 | }); 2537 | 2538 | // Toggle functionality 2539 | multiProductToggle.addEventListener('change', () => { 2540 | const isMultiMode = multiProductToggle.checked; 2541 | 2542 | if (isMultiMode) { 2543 | // Switch to multi-product mode 2544 | singleProductInput.style.display = 'none'; 2545 | multiProductInput.style.display = 'block'; 2546 | toggleBackground.style.background = '#5b8def'; 2547 | toggleSlider.style.transform = 'translateX(20px)'; 2548 | } else { 2549 | // Switch to single-product mode 2550 | singleProductInput.style.display = 'flex'; 2551 | multiProductInput.style.display = 'none'; 2552 | toggleBackground.style.background = '#dee2e6'; 2553 | toggleSlider.style.transform = 'translateX(0px)'; 2554 | } 2555 | 2556 | moveMarketSelector(isMultiMode); 2557 | }); 2558 | 2559 | // Collapse/Expand functionality 2560 | collapseToggle.addEventListener('click', () => { 2561 | const isCollapsed = searchControls.style.display === 'none'; 2562 | 2563 | if (isCollapsed) { 2564 | // Expand 2565 | searchControls.style.display = 'block'; 2566 | collapseText.textContent = '▲ Hide'; 2567 | collapseToggle.style.background = 'rgba(0, 123, 255, 0.1)'; 2568 | collapseToggle.style.border = '1px solid rgba(0, 123, 255, 0.2)'; 2569 | collapseToggle.style.color = '#5b8def'; 2570 | } else { 2571 | // Collapse 2572 | searchControls.style.display = 'none'; 2573 | collapseText.textContent = '▼ Show'; 2574 | collapseToggle.style.background = 'rgba(40, 167, 69, 0.1)'; 2575 | collapseToggle.style.border = '1px solid rgba(40, 167, 69, 0.2)'; 2576 | collapseToggle.style.color = '#28a745'; 2577 | } 2578 | }); 2579 | 2580 | // Add hover effects to collapse toggle 2581 | collapseToggle.addEventListener('mouseenter', () => { 2582 | const isCollapsed = searchControls.style.display === 'none'; 2583 | if (isCollapsed) { 2584 | collapseToggle.style.background = 'rgba(40, 167, 69, 0.2)'; 2585 | collapseToggle.style.transform = 'scale(1.05)'; 2586 | } else { 2587 | collapseToggle.style.background = 'rgba(0, 123, 255, 0.2)'; 2588 | collapseToggle.style.transform = 'scale(1.05)'; 2589 | } 2590 | }); 2591 | 2592 | collapseToggle.addEventListener('mouseleave', () => { 2593 | const isCollapsed = searchControls.style.display === 'none'; 2594 | if (isCollapsed) { 2595 | collapseToggle.style.background = 'rgba(40, 167, 69, 0.1)'; 2596 | } else { 2597 | collapseToggle.style.background = 'rgba(0, 123, 255, 0.1)'; 2598 | } 2599 | collapseToggle.style.transform = 'scale(1)'; 2600 | }); 2601 | 2602 | if (historyDetailBack) { 2603 | historyDetailBack.addEventListener('click', () => { 2604 | showHistoryListView(); 2605 | }); 2606 | } 2607 | 2608 | if (historyDetailOpenSearch) { 2609 | historyDetailOpenSearch.addEventListener('click', () => { 2610 | const historyId = historyDetailOpenSearch.dataset?.historyId; 2611 | if (historyId) { 2612 | openHistoryItemInSearchById(historyId); 2613 | } 2614 | }); 2615 | } 2616 | 2617 | // Search functionality 2618 | searchBtn.addEventListener('click', performSearch); 2619 | multiSearchBtn.addEventListener('click', performMultiSearch); 2620 | 2621 | // Enter key support 2622 | searchQuery.addEventListener('keydown', (e) => { 2623 | if (e.key === 'Enter') { 2624 | performSearch(); 2625 | } 2626 | }); 2627 | 2628 | // Tab switching functionality 2629 | const searchTab = document.getElementById('search-tab'); 2630 | const historyTab = document.getElementById('history-tab'); 2631 | const reportsTab = document.getElementById('reports-tab'); 2632 | const searchArea = document.getElementById('search-area'); 2633 | const resultsContainerTab = document.getElementById('results-container'); 2634 | const historyContainer = document.getElementById('history-container'); 2635 | const reportsContainer = document.getElementById('reports-container'); 2636 | 2637 | searchTab.addEventListener('click', () => { 2638 | switchTab('search'); 2639 | // Hide reports container and reset reports tab 2640 | const reportsContainer = document.getElementById('reports-container'); 2641 | if (reportsContainer) reportsContainer.style.display = 'none'; 2642 | if (reportsTab) { 2643 | reportsTab.style.background = 'rgba(91, 141, 239, 0.08)'; 2644 | reportsTab.style.color = '#5e6f9b'; 2645 | reportsTab.style.borderBottom = '3px solid transparent'; 2646 | reportsTab.classList.remove('active-tab'); 2647 | } 2648 | }); 2649 | 2650 | historyTab.addEventListener('click', () => { 2651 | switchTab('history'); 2652 | syncHistoryFiltersWithAnalysisFilters(); 2653 | loadHistory(); 2654 | // Hide reports container and reset reports tab 2655 | const reportsContainer = document.getElementById('reports-container'); 2656 | if (reportsContainer) reportsContainer.style.display = 'none'; 2657 | if (reportsTab) { 2658 | reportsTab.style.background = 'rgba(91, 141, 239, 0.08)'; 2659 | reportsTab.style.color = '#5e6f9b'; 2660 | reportsTab.style.borderBottom = '3px solid transparent'; 2661 | reportsTab.classList.remove('active-tab'); 2662 | } 2663 | }); 2664 | 2665 | reportsTab.addEventListener('click', () => { 2666 | // Reset all tabs first 2667 | [searchTab, historyTab, reportsTab].forEach(t => { 2668 | if (t) { 2669 | t.style.background = 'rgba(91, 141, 239, 0.08)'; 2670 | t.style.color = '#5e6f9b'; 2671 | t.style.borderBottom = '3px solid transparent'; 2672 | t.classList.remove('active-tab'); 2673 | } 2674 | }); 2675 | 2676 | // Set reports tab as active 2677 | reportsTab.style.background = 'rgba(255, 255, 255, 0.96)'; 2678 | reportsTab.style.color = '#27325f'; 2679 | reportsTab.style.borderBottom = '3px solid #5b8def'; 2680 | reportsTab.classList.add('active-tab'); 2681 | 2682 | // Hide all containers 2683 | const searchArea = document.getElementById('search-area'); 2684 | const resultsContainer = document.getElementById('results-container'); 2685 | const historyContainer = document.getElementById('history-container'); 2686 | const reportsContainer = document.getElementById('reports-container'); 2687 | 2688 | if (searchArea) searchArea.style.display = 'none'; 2689 | if (resultsContainer) resultsContainer.style.display = 'none'; 2690 | if (historyContainer) historyContainer.style.display = 'none'; 2691 | 2692 | // Show reports container 2693 | if (reportsContainer) { 2694 | reportsContainer.style.display = 'block'; 2695 | } else { 2696 | } 2697 | 2698 | // Initialize the Analysis Dashboard 2699 | initializeAnalysisDashboard(); 2700 | }); 2701 | 2702 | // History functionality 2703 | const clearHistoryBtn = document.getElementById('clear-history-btn'); 2704 | const clearHistoryBtnHeader = document.getElementById('clear-history-btn-header'); 2705 | const historySearch = document.getElementById('history-search'); 2706 | 2707 | if (clearHistoryBtn) { 2708 | clearHistoryBtn.addEventListener('click', clearAllHistory); 2709 | } 2710 | if (clearHistoryBtnHeader) { 2711 | clearHistoryBtnHeader.addEventListener('click', clearAllHistory); 2712 | } 2713 | if (historySearch) { 2714 | historySearch.addEventListener('input', filterHistory); 2715 | } 2716 | 2717 | // Initialize token status 2718 | initializeTokenStatus(); 2719 | 2720 | // Initialize sidebar (Phase 2) 2721 | initializeSidebar(); 2722 | 2723 | // Recalculate counts to ensure consistency 2724 | recalculateAllCounts(); 2725 | 2726 | // Check and show first-time tutorial 2727 | checkAndShowTutorial(); 2728 | } 2729 | 2730 | // ===== TUTORIAL FUNCTIONS ===== 2731 | 2732 | function checkAndShowTutorial() { 2733 | const tutorialCompleted = localStorage.getItem('chatgpt-product-info-tutorial-completed'); 2734 | if (!tutorialCompleted) { 2735 | showTutorial(); 2736 | } 2737 | } 2738 | 2739 | function showTutorial() { 2740 | const modal = document.getElementById('chatgpt-product-search-modal'); 2741 | if (!modal) return; 2742 | 2743 | // Check if tutorial is already shown 2744 | if (document.getElementById('tutorial-overlay')) return; 2745 | 2746 | // Inject tutorial HTML into modal 2747 | const modalContent = modal.querySelector('div[style*="border-radius"]'); 2748 | if (modalContent) { 2749 | modalContent.style.position = 'relative'; 2750 | modalContent.insertAdjacentHTML('beforeend', tutorialHTML); 2751 | initializeTutorialControls(); 2752 | } 2753 | } 2754 | 2755 | function initializeTutorialControls() { 2756 | let currentScreen = 1; 2757 | const totalScreens = 4; 2758 | 2759 | const overlay = document.getElementById('tutorial-overlay'); 2760 | const prevBtn = document.getElementById('tutorial-prev'); 2761 | const nextBtn = document.getElementById('tutorial-next'); 2762 | const skipBtn = document.getElementById('tutorial-skip'); 2763 | const dots = document.querySelectorAll('.tutorial-dot'); 2764 | 2765 | if (!overlay || !prevBtn || !nextBtn || !skipBtn) return; 2766 | 2767 | function updateScreen(screenNum) { 2768 | // Hide all screens 2769 | document.querySelectorAll('.tutorial-screen').forEach(screen => { 2770 | screen.style.display = 'none'; 2771 | }); 2772 | 2773 | // Show current screen 2774 | const currentScreenEl = document.querySelector(`.tutorial-screen[data-screen="${screenNum}"]`); 2775 | if (currentScreenEl) { 2776 | currentScreenEl.style.display = 'block'; 2777 | } 2778 | 2779 | // Update dots 2780 | dots.forEach(dot => { 2781 | const dotNum = parseInt(dot.dataset.dot); 2782 | if (dotNum === screenNum) { 2783 | dot.style.background = '#5b8def'; 2784 | dot.style.transform = 'scale(1.2)'; 2785 | } else { 2786 | dot.style.background = '#dee2e6'; 2787 | dot.style.transform = 'scale(1)'; 2788 | } 2789 | }); 2790 | 2791 | // Update buttons 2792 | if (screenNum === 1) { 2793 | prevBtn.style.display = 'none'; 2794 | } else { 2795 | prevBtn.style.display = 'block'; 2796 | } 2797 | 2798 | if (screenNum === totalScreens) { 2799 | nextBtn.textContent = 'Get Started'; 2800 | nextBtn.style.background = '#28a745'; 2801 | } else { 2802 | nextBtn.textContent = 'Next'; 2803 | nextBtn.style.background = '#5b8def'; 2804 | } 2805 | 2806 | currentScreen = screenNum; 2807 | } 2808 | 2809 | // Next button 2810 | nextBtn.addEventListener('click', () => { 2811 | if (currentScreen < totalScreens) { 2812 | updateScreen(currentScreen + 1); 2813 | } else { 2814 | closeTutorial(); 2815 | } 2816 | }); 2817 | 2818 | // Previous button 2819 | prevBtn.addEventListener('click', () => { 2820 | if (currentScreen > 1) { 2821 | updateScreen(currentScreen - 1); 2822 | } 2823 | }); 2824 | 2825 | // Skip button 2826 | skipBtn.addEventListener('click', () => { 2827 | closeTutorial(); 2828 | }); 2829 | 2830 | // Dot navigation 2831 | dots.forEach(dot => { 2832 | dot.addEventListener('click', () => { 2833 | const targetScreen = parseInt(dot.dataset.dot); 2834 | updateScreen(targetScreen); 2835 | }); 2836 | }); 2837 | 2838 | // Add hover effects to buttons 2839 | skipBtn.addEventListener('mouseenter', () => { 2840 | skipBtn.style.background = '#e9ecef'; 2841 | }); 2842 | skipBtn.addEventListener('mouseleave', () => { 2843 | skipBtn.style.background = 'none'; 2844 | }); 2845 | 2846 | prevBtn.addEventListener('mouseenter', () => { 2847 | prevBtn.style.background = '#f8f9fa'; 2848 | }); 2849 | prevBtn.addEventListener('mouseleave', () => { 2850 | prevBtn.style.background = 'white'; 2851 | }); 2852 | 2853 | nextBtn.addEventListener('mouseenter', () => { 2854 | if (currentScreen === totalScreens) { 2855 | nextBtn.style.background = '#218838'; 2856 | } else { 2857 | nextBtn.style.background = '#4a7de8'; 2858 | } 2859 | }); 2860 | nextBtn.addEventListener('mouseleave', () => { 2861 | if (currentScreen === totalScreens) { 2862 | nextBtn.style.background = '#28a745'; 2863 | } else { 2864 | nextBtn.style.background = '#5b8def'; 2865 | } 2866 | }); 2867 | 2868 | // Initialize first screen 2869 | updateScreen(1); 2870 | } 2871 | 2872 | function closeTutorial() { 2873 | const overlay = document.getElementById('tutorial-overlay'); 2874 | if (overlay) { 2875 | overlay.remove(); 2876 | } 2877 | // Mark tutorial as completed 2878 | localStorage.setItem('chatgpt-product-info-tutorial-completed', 'true'); 2879 | } 2880 | 2881 | function resetTutorial() { 2882 | localStorage.removeItem('chatgpt-product-info-tutorial-completed'); 2883 | showTutorial(); 2884 | } 2885 | 2886 | // ===== END TUTORIAL FUNCTIONS ===== 2887 | 2888 | // Helper to read active tab id: 'search' | 'history' | 'reports' 2889 | function getActiveTab() { 2890 | const active = document.querySelector('#tab-navigation .active-tab'); 2891 | return active?.id?.replace('-tab', '') || 'search'; 2892 | } 2893 | 2894 | function switchTab(tab) { 2895 | const searchTab = document.getElementById('search-tab'); 2896 | const historyTab = document.getElementById('history-tab'); 2897 | const reportsTab = document.getElementById('reports-tab'); 2898 | const searchArea = document.getElementById('search-area'); 2899 | const resultsContainer = document.getElementById('results-container'); 2900 | const historyContainer = document.getElementById('history-container'); 2901 | const reportsContainer = document.getElementById('reports-container'); 2902 | 2903 | // Reset all tabs 2904 | [searchTab, historyTab, reportsTab].forEach(t => { 2905 | if (t) { 2906 | t.style.background = 'rgba(91, 141, 239, 0.08)'; 2907 | t.style.color = '#5e6f9b'; 2908 | t.style.borderBottom = '3px solid transparent'; 2909 | t.classList.remove('active-tab'); 2910 | } 2911 | }); 2912 | 2913 | // Hide all containers explicitly and aggressively 2914 | 2915 | // Hide all containers cleanly 2916 | if (searchArea) { 2917 | searchArea.style.display = 'none'; 2918 | } 2919 | if (resultsContainer) { 2920 | resultsContainer.style.display = 'none'; 2921 | } 2922 | if (historyContainer) { 2923 | historyContainer.style.display = 'none'; 2924 | } 2925 | if (reportsContainer) { 2926 | reportsContainer.style.display = 'none'; 2927 | } 2928 | 2929 | // Reset all internal content states 2930 | const analysisContent = document.getElementById('analysis-content'); 2931 | const reportsWelcomeState = document.getElementById('reports-welcome-state'); 2932 | const historyContent = document.getElementById('history-content'); 2933 | const historyWelcomeState = document.getElementById('history-welcome-state'); 2934 | 2935 | if (analysisContent) { 2936 | analysisContent.style.display = 'none'; 2937 | } 2938 | if (reportsWelcomeState) { 2939 | reportsWelcomeState.style.display = 'none'; 2940 | } 2941 | if (historyContent) { 2942 | historyContent.style.display = 'none'; 2943 | } 2944 | if (historyWelcomeState) { 2945 | historyWelcomeState.style.display = 'none'; 2946 | } 2947 | 2948 | // Clean up any organization interfaces when switching tabs 2949 | const postSearchInterface = document.getElementById('post-search-tagging'); 2950 | const editInterface = document.getElementById('edit-organization-interface'); 2951 | if (postSearchInterface) { 2952 | postSearchInterface.remove(); 2953 | } 2954 | if (editInterface) { 2955 | editInterface.remove(); 2956 | } 2957 | 2958 | if (tab === 'search') { 2959 | searchTab.style.background = 'rgba(255, 255, 255, 0.96)'; 2960 | searchTab.style.color = '#27325f'; 2961 | searchTab.style.borderBottom = '3px solid #5b8def'; 2962 | searchTab.classList.add('active-tab'); 2963 | 2964 | if (searchArea) searchArea.style.display = 'block'; 2965 | if (resultsContainer) { 2966 | resultsContainer.style.display = 'block'; 2967 | resultsContainer.innerHTML = ''; 2968 | } 2969 | if (typeof resetToCleanSearchState === 'function') { 2970 | resetToCleanSearchState(); 2971 | } 2972 | } else if (tab === 'history') { 2973 | historyTab.style.background = 'rgba(255, 255, 255, 0.96)'; 2974 | historyTab.style.color = '#27325f'; 2975 | historyTab.style.borderBottom = '3px solid #5b8def'; 2976 | historyTab.classList.add('active-tab'); 2977 | 2978 | showHistoryListView(); 2979 | 2980 | // CRITICAL: Completely hide and clear analysis content when switching to history 2981 | if (reportsContainer) { 2982 | reportsContainer.style.display = 'none'; 2983 | 2984 | // Clear all analysis content from the DOM 2985 | const analysisContent = reportsContainer.querySelector('#analysis-content'); 2986 | if (analysisContent) { 2987 | analysisContent.style.display = 'none'; 2988 | } 2989 | } 2990 | 2991 | // Also ensure no analysis content exists anywhere else 2992 | const globalAnalysisContent = document.getElementById('analysis-content'); 2993 | const globalAnalysisResults = document.getElementById('analysis-results'); 2994 | const globalCitationTable = document.getElementById('citation-sources-table'); 2995 | const globalReviewTable = document.getElementById('review-sources-table'); 2996 | 2997 | if (globalAnalysisContent) { 2998 | globalAnalysisContent.style.display = 'none'; 2999 | } 3000 | if (globalAnalysisResults) { 3001 | globalAnalysisResults.style.display = 'none'; 3002 | } 3003 | if (globalCitationTable) { 3004 | globalCitationTable.style.display = 'none'; 3005 | } 3006 | if (globalReviewTable) { 3007 | globalReviewTable.style.display = 'none'; 3008 | } 3009 | 3010 | if (historyContainer) { 3011 | historyContainer.style.display = 'block'; 3012 | historyContainer.style.visibility = 'visible'; 3013 | 3014 | // Restore visibility that reports tab may have hidden 3015 | ['history-content', 'history-welcome-state', 'history-list'].forEach(id => { 3016 | const el = document.getElementById(id); 3017 | if (el) { 3018 | el.style.visibility = 'visible'; 3019 | } 3020 | }); 3021 | } 3022 | } else if (tab === 'reports') { 3023 | reportsTab.style.background = 'rgba(255, 255, 255, 0.96)'; 3024 | reportsTab.style.color = '#27325f'; 3025 | reportsTab.style.borderBottom = '3px solid #5b8def'; 3026 | reportsTab.classList.add('active-tab'); 3027 | 3028 | // CRITICAL: Completely hide and clear history content when switching to reports 3029 | if (historyContainer) { 3030 | historyContainer.style.display = 'none'; 3031 | 3032 | // Clear all history content from the DOM 3033 | const historyContent = historyContainer.querySelector('#history-content'); 3034 | const historyList = historyContainer.querySelector('#history-list'); 3035 | if (historyContent) { 3036 | historyContent.style.display = 'none'; 3037 | } 3038 | if (historyList) { 3039 | historyList.style.display = 'none'; 3040 | } 3041 | } 3042 | 3043 | if (reportsContainer) { 3044 | reportsContainer.style.display = 'block'; 3045 | reportsContainer.style.visibility = 'visible'; 3046 | reportsContainer.style.removeProperty('visibility'); 3047 | 3048 | // Restore visibility for analysis sections that may have been hidden 3049 | ['analysis-content', 'analysis-results', 'citation-sources-table', 'review-sources-table'] 3050 | .forEach(id => { 3051 | const el = document.getElementById(id); 3052 | if (el) { 3053 | if (el.style.display === 'none') { 3054 | el.style.display = 'block'; 3055 | } 3056 | el.style.visibility = 'visible'; 3057 | el.style.removeProperty('visibility'); 3058 | } 3059 | }); 3060 | 3061 | // Ensure scrollable analysis containers remain scrollable after tab switches 3062 | const scrollTargets = [ 3063 | reportsContainer, 3064 | document.getElementById('analysis-results'), 3065 | document.getElementById('analysis-content'), 3066 | document.querySelector('#analysis-content .analysis-scroll-area') 3067 | ]; 3068 | scrollTargets.forEach(target => { 3069 | if (target) { 3070 | target.style.overflowY = 'auto'; 3071 | } 3072 | }); 3073 | 3074 | // Initialize analysis dashboard when switching to reports 3075 | initializeAnalysisDashboard(); 3076 | } else { 3077 | } 3078 | } 3079 | } 3080 | 3081 | // ===== ANALYSIS DASHBOARD FUNCTIONALITY - Phase 6 ===== 3082 | 3083 | function initializeAnalysisDashboard() { 3084 | 3085 | // Allow analysis dashboard to initialize when called 3086 | 3087 | const history = loadSearchHistory(); 3088 | const reportsWelcomeState = document.getElementById('reports-welcome-state'); 3089 | const analysisContent = document.getElementById('analysis-content'); 3090 | 3091 | if (history.length === 0) { 3092 | if (reportsWelcomeState) reportsWelcomeState.style.display = 'flex'; 3093 | if (analysisContent) analysisContent.style.display = 'none'; 3094 | return; 3095 | } 3096 | 3097 | // Show analysis content immediately and setup interface 3098 | if (reportsWelcomeState) { 3099 | reportsWelcomeState.style.display = 'none'; 3100 | reportsWelcomeState.style.visibility = 'hidden'; 3101 | } 3102 | if (analysisContent) { 3103 | analysisContent.style.display = 'block'; 3104 | analysisContent.style.visibility = 'visible'; 3105 | analysisContent.style.removeProperty('visibility'); 3106 | 3107 | // Also ensure analysis results are visible 3108 | const analysisResults = document.getElementById('analysis-results'); 3109 | const citationTable = document.getElementById('citation-sources-table'); 3110 | const reviewTable = document.getElementById('review-sources-table'); 3111 | 3112 | if (analysisResults) { 3113 | analysisResults.style.display = 'block'; 3114 | analysisResults.style.visibility = 'visible'; 3115 | analysisResults.style.removeProperty('visibility'); 3116 | } 3117 | if (citationTable) { 3118 | citationTable.style.display = 'block'; 3119 | citationTable.style.visibility = 'visible'; 3120 | citationTable.style.removeProperty('visibility'); 3121 | } 3122 | if (reviewTable) { 3123 | reviewTable.style.display = 'block'; 3124 | reviewTable.style.visibility = 'visible'; 3125 | reviewTable.style.removeProperty('visibility'); 3126 | } 3127 | } 3128 | 3129 | // Clean up any existing event listeners and reset state 3130 | cleanupAnalysisInterface(); 3131 | resetAnalysisFilterPanelState(); 3132 | 3133 | // Setup analysis interface and generate initial analysis 3134 | setupAnalysisInterface(); 3135 | initializeAnalysisFilters(); 3136 | syncAnalysisFiltersWithHistoryFilters(); 3137 | if (typeof applyAnalysisFilters === 'function') { 3138 | applyAnalysisFilters(); 3139 | } else { 3140 | generateAnalysisReports(); 3141 | } 3142 | } 3143 | 3144 | function cleanupAnalysisInterface() { 3145 | // Remove existing event listeners to prevent duplicates 3146 | const toggleFiltersBtn = document.getElementById('toggle-analysis-filters'); 3147 | const clearFiltersBtn = document.getElementById('clear-analysis-filters'); 3148 | const applyFiltersBtn = document.getElementById('apply-analysis-filters'); 3149 | 3150 | if (toggleFiltersBtn) { 3151 | // Clone the element to remove all event listeners 3152 | const newToggleBtn = toggleFiltersBtn.cloneNode(true); 3153 | toggleFiltersBtn.parentNode.replaceChild(newToggleBtn, toggleFiltersBtn); 3154 | } 3155 | 3156 | if (clearFiltersBtn) { 3157 | const newClearBtn = clearFiltersBtn.cloneNode(true); 3158 | clearFiltersBtn.parentNode.replaceChild(newClearBtn, clearFiltersBtn); 3159 | } 3160 | 3161 | if (applyFiltersBtn) { 3162 | const newApplyBtn = applyFiltersBtn.cloneNode(true); 3163 | applyFiltersBtn.parentNode.replaceChild(newApplyBtn, applyFiltersBtn); 3164 | } 3165 | 3166 | // Clear any dynamic content that might have old event listeners 3167 | const tagsFilter = document.getElementById('analysis-tags-filter'); 3168 | if (tagsFilter) { 3169 | tagsFilter.innerHTML = ''; 3170 | } 3171 | 3172 | } 3173 | 3174 | function resetAnalysisFilterPanelState() { 3175 | // Reset filter panel to hidden state 3176 | const filterPanel = document.getElementById('analysis-filter-panel'); 3177 | const toggleText = document.getElementById('analysis-filter-toggle-text'); 3178 | const activeFiltersDiv = document.getElementById('analysis-active-filters'); 3179 | 3180 | if (filterPanel) { 3181 | filterPanel.style.display = 'none'; 3182 | } 3183 | 3184 | if (toggleText) { 3185 | toggleText.textContent = 'Filters'; 3186 | } 3187 | 3188 | // Hide active filters section 3189 | if (activeFiltersDiv) { 3190 | activeFiltersDiv.style.display = 'none'; 3191 | } 3192 | 3193 | } 3194 | 3195 | function setupAnalysisInterface() { 3196 | const toggleFiltersBtn = document.getElementById('toggle-analysis-filters'); 3197 | const clearFiltersBtn = document.getElementById('clear-analysis-filters'); 3198 | const applyFiltersBtn = document.getElementById('apply-analysis-filters'); 3199 | 3200 | // Toggle filters panel with hover effects 3201 | if (toggleFiltersBtn) { 3202 | toggleFiltersBtn.addEventListener('click', () => { 3203 | const filterPanel = document.getElementById('analysis-filter-panel'); 3204 | const toggleText = document.getElementById('analysis-filter-toggle-text'); 3205 | if (filterPanel && toggleText) { 3206 | const isHidden = filterPanel.style.display === 'none'; 3207 | filterPanel.style.display = isHidden ? 'block' : 'none'; 3208 | toggleText.textContent = isHidden ? 'Hide Filters' : 'Filters'; 3209 | } 3210 | }); 3211 | 3212 | // Add hover effects 3213 | toggleFiltersBtn.addEventListener('mouseenter', () => { 3214 | toggleFiltersBtn.style.backgroundColor = '#f8f9fa'; 3215 | }); 3216 | toggleFiltersBtn.addEventListener('mouseleave', () => { 3217 | toggleFiltersBtn.style.backgroundColor = 'transparent'; 3218 | }); 3219 | } 3220 | 3221 | // Clear filters 3222 | if (clearFiltersBtn) { 3223 | clearFiltersBtn.addEventListener('click', clearAnalysisFilters); 3224 | } 3225 | 3226 | // Apply filters 3227 | if (applyFiltersBtn) { 3228 | applyFiltersBtn.addEventListener('click', applyAnalysisFilters); 3229 | } 3230 | 3231 | } 3232 | 3233 | function initializeAnalysisFilters() { 3234 | const projectFilter = document.getElementById('analysis-project-filter'); 3235 | const marketFilter = document.getElementById('analysis-market-filter'); 3236 | const tagsFilter = document.getElementById('analysis-tags-filter'); 3237 | const projects = loadProjects(); 3238 | const tags = loadTags(); 3239 | 3240 | // Populate project filter 3241 | if (projectFilter) { 3242 | projectFilter.innerHTML = '<option value="">All Projects</option>' + 3243 | projects.map(project => `<option value="${project.id}">${project.name}</option>`).join(''); 3244 | 3245 | projectFilter.addEventListener('change', updateAnalysisFilterSummary); 3246 | } 3247 | 3248 | if (marketFilter) { 3249 | marketFilter.innerHTML = '<option value="all">All Markets</option>' + 3250 | MARKET_OPTIONS.map(option => `<option value="${option.value}">${option.label}</option>`).join(''); 3251 | marketFilter.addEventListener('change', updateAnalysisFilterSummary); 3252 | } 3253 | 3254 | // Populate tags filter 3255 | if (tagsFilter) { 3256 | tagsFilter.innerHTML = ''; 3257 | 3258 | if (tags.length === 0) { 3259 | tagsFilter.innerHTML = ` 3260 | <div style=" 3261 | color: #6c757d; 3262 | font-size: 12px; 3263 | font-style: italic; 3264 | padding: 8px; 3265 | ">No tags available</div> 3266 | `; 3267 | } else { 3268 | tags.forEach(tag => { 3269 | const tagCheckbox = document.createElement('label'); 3270 | tagCheckbox.className = 'analysis-tag-label'; 3271 | tagCheckbox.style.cssText = ` 3272 | display: flex; 3273 | align-items: center; 3274 | gap: 6px; 3275 | padding: 4px 8px; 3276 | border-radius: 12px; 3277 | background: ${tag.color}15; 3278 | border: 1px solid ${tag.color}30; 3279 | cursor: pointer; 3280 | font-size: 12px; 3281 | color: ${tag.color}; 3282 | margin: 0; 3283 | `; 3284 | 3285 | tagCheckbox.innerHTML = ` 3286 | <input type="checkbox" 3287 | class="analysis-tag-checkbox" 3288 | value="${tag.id}" 3289 | style="margin: 0; width: 12px; height: 12px;"> 3290 | <img src="${tagIconUrl}" alt="Tag" style="width: 14px; height: 14px;" /> 3291 | <span>${tag.name}</span> 3292 | `; 3293 | 3294 | const checkbox = tagCheckbox.querySelector('input'); 3295 | checkbox.addEventListener('change', updateAnalysisFilterSummary); 3296 | 3297 | tagsFilter.appendChild(tagCheckbox); 3298 | }); 3299 | } 3300 | } 3301 | } 3302 | 3303 | function syncAnalysisFiltersWithHistoryFilters() { 3304 | if (!currentFilters) { 3305 | return; 3306 | } 3307 | 3308 | const projectFilter = document.getElementById('analysis-project-filter'); 3309 | const marketFilter = document.getElementById('analysis-market-filter'); 3310 | if (projectFilter) { 3311 | projectFilter.value = currentFilters.project || ''; 3312 | } 3313 | 3314 | if (marketFilter) { 3315 | const targetMarket = currentFilters.market || 'all'; 3316 | marketFilter.value = marketFilter.querySelector(`option[value="${targetMarket}"]`) ? targetMarket : 'all'; 3317 | } 3318 | 3319 | const tagCheckboxes = document.querySelectorAll('.analysis-tag-checkbox'); 3320 | if (tagCheckboxes.length > 0) { 3321 | const activeTags = new Set(currentFilters.tags || []); 3322 | tagCheckboxes.forEach(cb => { 3323 | cb.checked = activeTags.has(cb.value); 3324 | }); 3325 | } 3326 | 3327 | if (typeof updateAnalysisFilterSummary === 'function') { 3328 | updateAnalysisFilterSummary(); 3329 | } 3330 | if (typeof updateAnalysisFilterChips === 'function') { 3331 | updateAnalysisFilterChips(); 3332 | } 3333 | } 3334 | 3335 | function syncHistoryFiltersWithAnalysisFilters() { 3336 | if (!currentFilters) { 3337 | return; 3338 | } 3339 | 3340 | // Sync filters from Analysis to History 3341 | const filterTextInput = document.getElementById('filter-text'); 3342 | const projectFilterSelect = document.getElementById('filter-project'); 3343 | const marketFilterSelect = document.getElementById('filter-market'); 3344 | 3345 | if (filterTextInput) { 3346 | filterTextInput.value = currentFilters.rawText || ''; 3347 | } 3348 | 3349 | if (projectFilterSelect) { 3350 | projectFilterSelect.value = currentFilters.project || ''; 3351 | } 3352 | 3353 | if (marketFilterSelect) { 3354 | const targetMarket = currentFilters.market || 'all'; 3355 | marketFilterSelect.value = marketFilterSelect.querySelector(`option[value="${targetMarket}"]`) ? targetMarket : 'all'; 3356 | } 3357 | 3358 | // Sync tag checkboxes 3359 | const tagCheckboxes = document.querySelectorAll('.tag-checkbox'); 3360 | if (tagCheckboxes.length > 0) { 3361 | const activeTags = new Set(currentFilters.tags || []); 3362 | tagCheckboxes.forEach(cb => { 3363 | cb.checked = activeTags.has(cb.value); 3364 | }); 3365 | } 3366 | 3367 | // Update filter display elements 3368 | if (typeof updateFilterSummary === 'function') { 3369 | updateFilterSummary(); 3370 | } 3371 | if (typeof updateFilterChips === 'function') { 3372 | updateFilterChips(); 3373 | } 3374 | } 3375 | 3376 | function syncAllFilterDisplays() { 3377 | // Update both History and Analysis filter displays to match global state 3378 | if (typeof syncAnalysisFiltersWithHistoryFilters === 'function') { 3379 | syncAnalysisFiltersWithHistoryFilters(); 3380 | } 3381 | if (typeof syncHistoryFiltersWithAnalysisFilters === 'function') { 3382 | syncHistoryFiltersWithAnalysisFilters(); 3383 | } 3384 | } 3385 | 3386 | function updateAnalysisFilterSummary() { 3387 | const projectFilter = document.getElementById('analysis-project-filter'); 3388 | const marketFilter = document.getElementById('analysis-market-filter'); 3389 | const tagCheckboxes = document.querySelectorAll('.analysis-tag-checkbox:checked'); 3390 | const summary = document.getElementById('analysis-filter-summary'); 3391 | 3392 | if (!summary) return; 3393 | 3394 | let filterCount = 0; 3395 | if (projectFilter && projectFilter.value) filterCount++; 3396 | if (marketFilter && marketFilter.value && marketFilter.value !== 'all') filterCount++; 3397 | if (tagCheckboxes.length > 0) filterCount++; 3398 | 3399 | if (filterCount === 0) { 3400 | summary.textContent = 'No filters applied'; 3401 | } else if (filterCount === 1) { 3402 | if (projectFilter && projectFilter.value) { 3403 | const projects = loadProjects(); 3404 | const project = projects.find(p => p.id === projectFilter.value); 3405 | summary.textContent = `Filtered by: ${project?.name || 'Unknown Project'}`; 3406 | } else if (marketFilter && marketFilter.value && marketFilter.value !== 'all') { 3407 | const marketOption = getMarketOption(marketFilter.value); 3408 | summary.textContent = `Filtered by: ${marketOption.label}`; 3409 | } else { 3410 | summary.textContent = `Filtered by: ${tagCheckboxes.length} tag${tagCheckboxes.length > 1 ? 's' : ''}`; 3411 | } 3412 | } else { 3413 | summary.textContent = `${filterCount} filters applied`; 3414 | } 3415 | } 3416 | 3417 | function applyAnalysisFilters() { 3418 | const projectFilter = document.getElementById('analysis-project-filter'); 3419 | const marketFilter = document.getElementById('analysis-market-filter'); 3420 | const checkedTagCheckboxes = document.querySelectorAll('.analysis-tag-checkbox:checked'); 3421 | 3422 | const selectedProject = projectFilter ? projectFilter.value : ''; 3423 | const selectedMarket = marketFilter ? marketFilter.value : 'all'; 3424 | const selectedTags = Array.from(checkedTagCheckboxes).map(cb => cb.value); 3425 | 3426 | // Keep shared filter state in sync so History reflects analysis selections 3427 | const previousFilters = currentFilters || { text: '', rawText: '', project: '', tags: [], market: 'all', isActive: false }; 3428 | currentFilters = { 3429 | text: previousFilters.text || '', 3430 | rawText: previousFilters.rawText || '', 3431 | project: selectedProject, 3432 | tags: selectedTags, 3433 | market: selectedMarket, 3434 | isActive: Boolean( 3435 | (previousFilters.text && previousFilters.text.length) || 3436 | selectedProject || 3437 | selectedTags.length || 3438 | (selectedMarket && selectedMarket !== 'all') 3439 | ) 3440 | }; 3441 | 3442 | _applyToHistory({ projectId: selectedProject, tags: selectedTags, market: selectedMarket, shouldSwitch: false }); 3443 | 3444 | // Hide filter panel 3445 | const panel = document.getElementById('analysis-filter-panel'); 3446 | const toggleText = document.getElementById('analysis-filter-toggle-text'); 3447 | if (panel) panel.style.display = 'none'; 3448 | if (toggleText) toggleText.textContent = 'Filters'; 3449 | 3450 | // Update filter chips display 3451 | updateAnalysisFilterChips(); 3452 | updateAnalysisFilterSummary(); 3453 | 3454 | // Generate filtered analysis 3455 | generateAnalysisReports(); 3456 | } 3457 | 3458 | function clearAnalysisFilters() { 3459 | const projectFilter = document.getElementById('analysis-project-filter'); 3460 | const marketFilter = document.getElementById('analysis-market-filter'); 3461 | const tagCheckboxes = document.querySelectorAll('.analysis-tag-checkbox'); 3462 | 3463 | if (projectFilter) projectFilter.value = ''; 3464 | if (marketFilter) marketFilter.value = 'all'; 3465 | tagCheckboxes.forEach(checkbox => checkbox.checked = false); 3466 | 3467 | updateAnalysisFilterSummary(); 3468 | updateAnalysisFilterChips(); 3469 | 3470 | const previousFilters = currentFilters || { text: '', rawText: '', project: '', tags: [], market: 'all', isActive: false }; 3471 | currentFilters = { 3472 | text: previousFilters.text || '', 3473 | rawText: previousFilters.rawText || '', 3474 | project: '', 3475 | tags: [], 3476 | market: 'all', 3477 | isActive: Boolean(previousFilters.text && previousFilters.text.length) 3478 | }; 3479 | _applyToHistory({ projectId: '', tags: [], market: 'all', shouldSwitch: false }); 3480 | } 3481 | 3482 | function updateAnalysisFilterChips() { 3483 | const activeFiltersDiv = document.getElementById('analysis-active-filters'); 3484 | const filterChips = document.getElementById('analysis-filter-chips'); 3485 | 3486 | if (!activeFiltersDiv || !filterChips) return; 3487 | 3488 | // Clear existing chips 3489 | filterChips.innerHTML = ''; 3490 | 3491 | let hasActiveFilters = false; 3492 | const projectFilter = document.getElementById('analysis-project-filter'); 3493 | const marketFilter = document.getElementById('analysis-market-filter'); 3494 | const tagCheckboxes = document.querySelectorAll('.analysis-tag-checkbox:checked'); 3495 | 3496 | // Project filter chip 3497 | if (projectFilter && projectFilter.value) { 3498 | hasActiveFilters = true; 3499 | const projects = loadProjects(); 3500 | const project = projects.find(p => p.id === projectFilter.value); 3501 | const chip = createFilterChip('project', `<span style="display:flex; align-items:center; gap:4px;"><img src="${projectIconUrl}" alt="Project" style="width: 14px; height: 14px;" />${project?.name || 'Unknown Project'}</span>`, () => { 3502 | projectFilter.value = ''; 3503 | applyAnalysisFilters(); 3504 | }); 3505 | filterChips.appendChild(chip); 3506 | } 3507 | 3508 | if (marketFilter && marketFilter.value && marketFilter.value !== 'all') { 3509 | hasActiveFilters = true; 3510 | const option = getMarketOption(marketFilter.value); 3511 | const chip = createFilterChip('market', `🌍 ${option.label}`, () => { 3512 | marketFilter.value = 'all'; 3513 | applyAnalysisFilters(); 3514 | }); 3515 | filterChips.appendChild(chip); 3516 | } 3517 | 3518 | // Tag filter chips 3519 | if (tagCheckboxes.length > 0) { 3520 | hasActiveFilters = true; 3521 | const tags = loadTags(); 3522 | Array.from(tagCheckboxes).forEach(checkbox => { 3523 | const tag = tags.find(t => t.id === checkbox.value); 3524 | if (tag) { 3525 | const chip = createFilterChip('tag', tag.name, () => { 3526 | checkbox.checked = false; 3527 | applyAnalysisFilters(); 3528 | }); 3529 | filterChips.appendChild(chip); 3530 | } 3531 | }); 3532 | } 3533 | 3534 | // Show/hide active filters section 3535 | activeFiltersDiv.style.display = hasActiveFilters ? 'block' : 'none'; 3536 | } 3537 | 3538 | function getFilteredSearchHistory() { 3539 | const history = loadSearchHistory(); 3540 | const projectFilter = document.getElementById('analysis-project-filter'); 3541 | const marketFilter = document.getElementById('analysis-market-filter'); 3542 | const selectedTags = Array.from(document.querySelectorAll('.analysis-tag-checkbox:checked')) 3543 | .map(cb => cb.value); 3544 | 3545 | return history.filter(item => { 3546 | // Filter by project 3547 | if (projectFilter && projectFilter.value && item.projectId !== projectFilter.value) { 3548 | return false; 3549 | } 3550 | 3551 | if (marketFilter && marketFilter.value && marketFilter.value !== 'all') { 3552 | if ((item.market || null) !== marketFilter.value) { 3553 | return false; 3554 | } 3555 | } 3556 | 3557 | // Filter by tags (AND logic) 3558 | if (selectedTags.length > 0) { 3559 | const itemTags = item.tags || []; 3560 | const hasAllTags = selectedTags.every(tagId => itemTags.includes(tagId)); 3561 | if (!hasAllTags) return false; 3562 | } 3563 | 3564 | return true; 3565 | }); 3566 | } 3567 | 3568 | function generateAnalysisReports() { 3569 | const filteredHistory = getFilteredSearchHistory(); 3570 | 3571 | if (filteredHistory.length === 0) { 3572 | // Show empty state 3573 | const citationTable = document.getElementById('citation-sources-table'); 3574 | const reviewTable = document.getElementById('review-sources-table'); 3575 | const sentimentContent = document.getElementById('sentiment-content'); 3576 | 3577 | if (citationTable) citationTable.innerHTML = '<div style="text-align: center; padding: 40px; color: #6c757d;">No citation sources found</div>'; 3578 | if (reviewTable) reviewTable.innerHTML = '<div style="text-align: center; padding: 40px; color: #6c757d;">No review sources found</div>'; 3579 | if (sentimentContent) sentimentContent.innerHTML = '<div style="text-align: center; padding: 20px; color: #6c757d;">No sentiment data available</div>'; 3580 | return; 3581 | } 3582 | 3583 | // Generate simplified analysis reports 3584 | const citationSources = generateSimpleCitationSources(filteredHistory); 3585 | const reviewSources = generateSimpleReviewSources(filteredHistory); 3586 | 3587 | // Display tables 3588 | const citationTable = document.getElementById('citation-sources-table'); 3589 | const reviewTable = document.getElementById('review-sources-table'); 3590 | 3591 | if (citationTable) { 3592 | citationTable.innerHTML = generateSimpleSourcesHTML(citationSources.slice(0, 10), 'citations'); 3593 | } 3594 | 3595 | if (reviewTable) { 3596 | reviewTable.innerHTML = generateSimpleSourcesHTML(reviewSources.slice(0, 10), 'reviews'); 3597 | } 3598 | 3599 | // Generate sentiment analysis 3600 | generateSimpleSentimentAnalysis(filteredHistory); 3601 | } 3602 | 3603 | // Simplified helper functions for analysis dashboard 3604 | function generateSimpleCitationSources(history) { 3605 | const domainCounts = new Map(); 3606 | 3607 | history.forEach(item => { 3608 | if (item.results?.citations) { 3609 | item.results.citations.forEach(citation => { 3610 | if (citation.url) { 3611 | const domain = extractDomainFromUrl(citation.url); 3612 | if (domain && domain !== 'unknown') { 3613 | domainCounts.set(domain, (domainCounts.get(domain) || 0) + 1); 3614 | } 3615 | } 3616 | }); 3617 | } 3618 | if (item.results?.productLinks) { 3619 | item.results.productLinks.forEach(link => { 3620 | if (link.url) { 3621 | const domain = extractDomainFromUrl(link.url); 3622 | if (domain && domain !== 'unknown') { 3623 | domainCounts.set(domain, (domainCounts.get(domain) || 0) + 1); 3624 | } 3625 | } 3626 | }); 3627 | } 3628 | }); 3629 | 3630 | return Array.from(domainCounts.entries()) 3631 | .map(([domain, count]) => ({ domain, count })) 3632 | .sort((a, b) => b.count - a.count); 3633 | } 3634 | 3635 | function generateSimpleReviewSources(history) { 3636 | const domainCounts = new Map(); 3637 | 3638 | history.forEach(item => { 3639 | if (item.results?.reviews) { 3640 | item.results.reviews.forEach(review => { 3641 | if (review.url) { 3642 | const domain = extractDomainFromUrl(review.url); 3643 | if (domain && domain !== 'unknown') { 3644 | domainCounts.set(domain, (domainCounts.get(domain) || 0) + 1); 3645 | } 3646 | } 3647 | }); 3648 | } 3649 | }); 3650 | 3651 | return Array.from(domainCounts.entries()) 3652 | .map(([domain, count]) => ({ domain, count })) 3653 | .sort((a, b) => b.count - a.count); 3654 | } 3655 | 3656 | function generateSimpleSourcesHTML(sources, type) { 3657 | const headerLabel = type === 'reviews' ? 'Review Sources' : 'Citation Sources'; 3658 | const emptyMessage = type === 'reviews' 3659 | ? 'No review sources found' 3660 | : 'No citation sources found'; 3661 | 3662 | if (sources.length === 0) { 3663 | return `<div style="text-align: center; padding: 40px; color: #6c757d;">${emptyMessage}</div>`; 3664 | } 3665 | 3666 | return ` 3667 | <table style="width: 100%; border-collapse: collapse;"> 3668 | <thead> 3669 | <tr style="background: #f8f9fa;"> 3670 | <th style="padding: 8px; text-align: left; border-bottom: 1px solid #e9ecef;">${headerLabel}</th> 3671 | <th style="padding: 8px; text-align: right; border-bottom: 1px solid #e9ecef;">Count</th> 3672 | </tr> 3673 | </thead> 3674 | <tbody> 3675 | ${sources.map(source => ` 3676 | <tr style="border-bottom: 1px solid #f8f9fa;"> 3677 | <td style="padding: 8px;"> 3678 | <div style="display: flex; align-items: center; gap: 8px;"> 3679 | <img src="${getFaviconUrl(`https://${source.domain}`)}" alt="${source.domain} favicon" style="width: 16px; height: 16px;" onerror="this.style.display='none'"> 3680 | ${source.domain} 3681 | </div> 3682 | </td> 3683 | <td style="padding: 8px; text-align: right; font-weight: bold;">${source.count}</td> 3684 | </tr> 3685 | `).join('')} 3686 | </tbody> 3687 | </table> 3688 | `; 3689 | } 3690 | 3691 | function generateSimpleSentimentAnalysis(history) { 3692 | const sentimentContent = document.getElementById('sentiment-content'); 3693 | if (!sentimentContent) return; 3694 | 3695 | let totalReviews = 0; 3696 | let positiveCount = 0; 3697 | let negativeCount = 0; 3698 | let neutralCount = 0; 3699 | 3700 | history.forEach(search => { 3701 | if (search.results && search.results.reviews) { 3702 | search.results.reviews.forEach(review => { 3703 | totalReviews++; 3704 | 3705 | // Use the existing sentiment property from the review data 3706 | const sentiment = review.sentiment ? review.sentiment.toLowerCase() : 'neutral'; 3707 | 3708 | if (sentiment === 'positive') { 3709 | positiveCount++; 3710 | } else if (sentiment === 'negative') { 3711 | negativeCount++; 3712 | } else { 3713 | neutralCount++; 3714 | } 3715 | }); 3716 | } 3717 | }); 3718 | 3719 | const positivePercent = totalReviews > 0 ? Math.round((positiveCount / totalReviews) * 100) : 0; 3720 | const negativePercent = totalReviews > 0 ? Math.round((negativeCount / totalReviews) * 100) : 0; 3721 | const neutralPercent = totalReviews > 0 ? Math.round((neutralCount / totalReviews) * 100) : 0; 3722 | 3723 | sentimentContent.innerHTML = ` 3724 | <div style="margin-bottom: 16px; font-size: 12px; color: #6c757d;"> 3725 | Based on ${totalReviews} review${totalReviews !== 1 ? 's' : ''} across ${history.length} search${history.length !== 1 ? 'es' : ''} 3726 | </div> 3727 | 3728 | <table style=" 3729 | width: 100%; 3730 | border-collapse: collapse; 3731 | border: 1px solid #e9ecef; 3732 | "> 3733 | <thead> 3734 | <tr style="background: #f8f9fa;"> 3735 | <th style="padding: 8px 12px; text-align: left; border-bottom: 1px solid #e9ecef;">Sentiment</th> 3736 | <th style="padding: 8px 12px; text-align: center; border-bottom: 1px solid #e9ecef;">Count</th> 3737 | <th style="padding: 8px 12px; text-align: center; border-bottom: 1px solid #e9ecef;">Percentage</th> 3738 | <th style="padding: 8px 12px; text-align: center; border-bottom: 1px solid #e9ecef;">Visual</th> 3739 | </tr> 3740 | </thead> 3741 | <tbody> 3742 | <tr style="border-bottom: 1px solid #f8f9fa;"> 3743 | <td style="padding: 8px 12px; color: #28a745; font-weight: 600;"> 3744 | <span style="display: inline-flex; align-items: center; gap: 8px;"> 3745 | <span style=" 3746 | width: 20px; 3747 | height: 20px; 3748 | display: inline-block; 3749 | background-color: currentColor; 3750 | mask: url(${positiveIconUrl}) no-repeat center / contain; 3751 | -webkit-mask: url(${positiveIconUrl}) no-repeat center / contain; 3752 | " aria-hidden="true"></span> 3753 | Positive 3754 | </span> 3755 | </td> 3756 | <td style="padding: 8px 12px; text-align: center; font-weight: bold;">${positiveCount}</td> 3757 | <td style="padding: 8px 12px; text-align: center; font-weight: bold;">${positivePercent}%</td> 3758 | <td style="padding: 8px 12px;"> 3759 | <div style="background: #e9ecef; border-radius: 4px; height: 8px; width: 100%; position: relative;"> 3760 | <div style="background: #28a745; height: 100%; border-radius: 4px; width: ${positivePercent}%; transition: width 0.3s ease;"></div> 3761 | </div> 3762 | </td> 3763 | </tr> 3764 | <tr style="border-bottom: 1px solid #f8f9fa;"> 3765 | <td style="padding: 8px 12px; color: #d39e00; font-weight: 600;"> 3766 | <span style="display: inline-flex; align-items: center; gap: 8px;"> 3767 | <span style=" 3768 | width: 20px; 3769 | height: 20px; 3770 | display: inline-block; 3771 | background-color: currentColor; 3772 | mask: url(${neutralIconUrl}) no-repeat center / contain; 3773 | -webkit-mask: url(${neutralIconUrl}) no-repeat center / contain; 3774 | " aria-hidden="true"></span> 3775 | Neutral 3776 | </span> 3777 | </td> 3778 | <td style="padding: 8px 12px; text-align: center; font-weight: bold;">${neutralCount}</td> 3779 | <td style="padding: 8px 12px; text-align: center; font-weight: bold;">${neutralPercent}%</td> 3780 | <td style="padding: 8px 12px;"> 3781 | <div style="background: #e9ecef; border-radius: 4px; height: 8px; width: 100%; position: relative;"> 3782 | <div style="background: #ffc107; height: 100%; border-radius: 4px; width: ${neutralPercent}%; transition: width 0.3s ease;"></div> 3783 | </div> 3784 | </td> 3785 | </tr> 3786 | <tr> 3787 | <td style="padding: 8px 12px; color: #dc3545; font-weight: 600;"> 3788 | <span style="display: inline-flex; align-items: center; gap: 8px;"> 3789 | <span style=" 3790 | width: 20px; 3791 | height: 20px; 3792 | display: inline-block; 3793 | background-color: currentColor; 3794 | mask: url(${negativeIconUrl}) no-repeat center / contain; 3795 | -webkit-mask: url(${negativeIconUrl}) no-repeat center / contain; 3796 | " aria-hidden="true"></span> 3797 | Negative 3798 | </span> 3799 | </td> 3800 | <td style="padding: 8px 12px; text-align: center; font-weight: bold;">${negativeCount}</td> 3801 | <td style="padding: 8px 12px; text-align: center; font-weight: bold;">${negativePercent}%</td> 3802 | <td style="padding: 8px 12px;"> 3803 | <div style="background: #e9ecef; border-radius: 4px; height: 8px; width: 100%; position: relative;"> 3804 | <div style="background: #dc3545; height: 100%; border-radius: 4px; width: ${negativePercent}%; transition: width 0.3s ease;"></div> 3805 | </div> 3806 | </td> 3807 | </tr> 3808 | </tbody> 3809 | </table> 3810 | `; 3811 | } 3812 | 3813 | 3814 | // Legacy function for backward compatibility 3815 | 3816 | // Function to show the collapse toggle after results are displayed 3817 | function showCollapseToggle() { 3818 | const collapseToggle = document.getElementById('collapse-toggle'); 3819 | if (collapseToggle) { 3820 | collapseToggle.style.display = 'block'; 3821 | } 3822 | } 3823 | 3824 | // Search functionality functions 3825 | // History Management Functions 3826 | function sanitizeForStorage(data) { 3827 | try { 3828 | const seen = new WeakSet(); 3829 | const json = JSON.stringify(data, (_, value) => { 3830 | if (typeof value === 'bigint') { 3831 | return value.toString(); 3832 | } 3833 | if (typeof value === 'function' || typeof value === 'symbol') { 3834 | return undefined; 3835 | } 3836 | if (value && typeof value === 'object') { 3837 | if (seen.has(value)) { 3838 | return undefined; 3839 | } 3840 | seen.add(value); 3841 | } 3842 | return value; 3843 | }); 3844 | return JSON.parse(json); 3845 | } catch (error) { 3846 | console.error('ChatGPT Product Info: Failed to sanitize search results for history storage.', error); 3847 | return { 3848 | summary: data?.summary || null, 3849 | rationale: data?.rationale || null, 3850 | reviewSummary: data?.reviewSummary || null, 3851 | products: data?.products || [], 3852 | productLinks: data?.productLinks || [], 3853 | reviews: data?.reviews || [], 3854 | multiResults: data?.multiResults || null, 3855 | fallback: true 3856 | }; 3857 | } 3858 | } 3859 | 3860 | function saveSearchToHistory(query, results, searchType = 'single', tags = [], projectId = null, marketValue = null) { 3861 | try { 3862 | const history = JSON.parse(localStorage.getItem('chatgpt-product-search-history') || '[]'); 3863 | const sanitizedResults = sanitizeForStorage(results); 3864 | const marketOption = marketValue ? getMarketOption(marketValue) : getSelectedMarketSettings(); 3865 | const historyItem = { 3866 | id: Date.now() + Math.random().toString(36).substr(2, 9), 3867 | query: query, 3868 | results: sanitizedResults, 3869 | searchType: searchType, 3870 | timestamp: Date.now(), 3871 | date: new Date().toLocaleString(), 3872 | tags: Array.isArray(tags) ? tags : [], 3873 | projectId: projectId, 3874 | version: 2, 3875 | market: marketOption.value, 3876 | marketLabel: marketOption.label, 3877 | marketCode: marketOption.code 3878 | }; 3879 | 3880 | if (tags && Array.isArray(tags)) { 3881 | tags.forEach(tagId => { 3882 | if (tagId) updateTagUsage(tagId); 3883 | }); 3884 | } 3885 | 3886 | if (projectId) { 3887 | updateProjectSearchCount(projectId); 3888 | } 3889 | 3890 | history.unshift(historyItem); 3891 | 3892 | if (history.length > 50) { 3893 | history.splice(50); 3894 | } 3895 | 3896 | localStorage.setItem('chatgpt-product-search-history', JSON.stringify(history)); 3897 | return historyItem.id; 3898 | } catch (error) { 3899 | console.error('ChatGPT Product Info: Failed to save search history.', error); 3900 | alert('Unable to save this search to history. Check the console for details.'); 3901 | return null; 3902 | } 3903 | } 3904 | 3905 | function updateHistoryEntry(historyId, updates = {}) { 3906 | try { 3907 | const history = JSON.parse(localStorage.getItem('chatgpt-product-search-history') || '[]'); 3908 | const index = history.findIndex(item => item.id === historyId); 3909 | if (index === -1) { 3910 | return false; 3911 | } 3912 | 3913 | const existingItem = history[index]; 3914 | const updatedItem = { ...existingItem }; 3915 | 3916 | if (Object.prototype.hasOwnProperty.call(updates, 'results')) { 3917 | updatedItem.results = sanitizeForStorage(updates.results); 3918 | } 3919 | if (Object.prototype.hasOwnProperty.call(updates, 'query')) { 3920 | updatedItem.query = updates.query; 3921 | } 3922 | if (Object.prototype.hasOwnProperty.call(updates, 'searchType')) { 3923 | updatedItem.searchType = updates.searchType; 3924 | } 3925 | if (Object.prototype.hasOwnProperty.call(updates, 'tags')) { 3926 | updatedItem.tags = Array.isArray(updates.tags) ? updates.tags : []; 3927 | } 3928 | if (Object.prototype.hasOwnProperty.call(updates, 'projectId')) { 3929 | updatedItem.projectId = updates.projectId; 3930 | } 3931 | if (Object.prototype.hasOwnProperty.call(updates, 'market')) { 3932 | updatedItem.market = updates.market; 3933 | } 3934 | if (Object.prototype.hasOwnProperty.call(updates, 'marketLabel')) { 3935 | updatedItem.marketLabel = updates.marketLabel; 3936 | } 3937 | if (Object.prototype.hasOwnProperty.call(updates, 'marketCode')) { 3938 | updatedItem.marketCode = updates.marketCode; 3939 | } 3940 | 3941 | updatedItem.updatedAt = Date.now(); 3942 | 3943 | history[index] = updatedItem; 3944 | localStorage.setItem('chatgpt-product-search-history', JSON.stringify(history)); 3945 | return true; 3946 | } catch (error) { 3947 | console.error('ChatGPT Product Info: Failed to update search history.', error); 3948 | alert('Unable to update this search in history. Check the console for details.'); 3949 | return false; 3950 | } 3951 | } 3952 | 3953 | function loadSearchHistory() { 3954 | try { 3955 | return JSON.parse(localStorage.getItem('chatgpt-product-search-history') || '[]'); 3956 | } catch (error) { 3957 | return []; 3958 | } 3959 | } 3960 | 3961 | function clearAllHistory() { 3962 | if (confirm('Are you sure you want to clear all search history? This action cannot be undone.')) { 3963 | localStorage.removeItem('chatgpt-product-search-history'); 3964 | 3965 | // Reset all project search counts to 0 3966 | const projects = loadProjects(); 3967 | projects.forEach(project => { 3968 | project.searchCount = 0; 3969 | }); 3970 | saveProjects(projects); 3971 | 3972 | // Reset all tag usage counts to 0 3973 | const tags = loadTags(); 3974 | tags.forEach(tag => { 3975 | tag.usageCount = 0; 3976 | }); 3977 | saveTags(tags); 3978 | 3979 | loadHistory(); 3980 | 3981 | // Update sidebar to reflect reset counts 3982 | populateProjectsList(); 3983 | populateTagsList(); 3984 | } 3985 | } 3986 | 3987 | // ===== ENHANCED DATA MANAGEMENT FUNCTIONS ===== 3988 | // Tags and Projects Management - Phase 1 Implementation 3989 | 3990 | function loadTags() { 3991 | try { 3992 | return JSON.parse(localStorage.getItem('chatgpt-product-search-tags') || '[]'); 3993 | } catch (error) { 3994 | return []; 3995 | } 3996 | } 3997 | 3998 | function saveTags(tags) { 3999 | try { 4000 | localStorage.setItem('chatgpt-product-search-tags', JSON.stringify(tags)); 4001 | return true; 4002 | } catch (error) { 4003 | return false; 4004 | } 4005 | } 4006 | 4007 | function loadProjects() { 4008 | try { 4009 | return JSON.parse(localStorage.getItem('chatgpt-product-search-projects') || '[]'); 4010 | } catch (error) { 4011 | return []; 4012 | } 4013 | } 4014 | 4015 | function saveProjects(projects) { 4016 | try { 4017 | localStorage.setItem('chatgpt-product-search-projects', JSON.stringify(projects)); 4018 | return true; 4019 | } catch (error) { 4020 | return false; 4021 | } 4022 | } 4023 | 4024 | function createTag(name, color = '#5b8def') { 4025 | const tags = loadTags(); 4026 | 4027 | // Check for duplicate names 4028 | if (tags.find(tag => tag.name.toLowerCase() === name.toLowerCase())) { 4029 | throw new Error('A tag with this name already exists'); 4030 | } 4031 | 4032 | const newTag = { 4033 | id: Date.now() + Math.random().toString(36).substr(2, 9), 4034 | name: name.trim(), 4035 | color: color, 4036 | created: Date.now(), 4037 | usageCount: 0 4038 | }; 4039 | 4040 | tags.push(newTag); 4041 | saveTags(tags); 4042 | return newTag; 4043 | } 4044 | 4045 | function createProject(name, description = '') { 4046 | const projects = loadProjects(); 4047 | 4048 | // Check for duplicate names 4049 | if (projects.find(project => project.name.toLowerCase() === name.toLowerCase())) { 4050 | throw new Error('A project with this name already exists'); 4051 | } 4052 | 4053 | const newProject = { 4054 | id: Date.now() + Math.random().toString(36).substr(2, 9), 4055 | name: name.trim(), 4056 | description: description.trim(), 4057 | created: Date.now(), 4058 | searchCount: 0 4059 | }; 4060 | 4061 | projects.push(newProject); 4062 | saveProjects(projects); 4063 | return newProject; 4064 | } 4065 | 4066 | function deleteTag(tagId) { 4067 | const tags = loadTags(); 4068 | const filteredTags = tags.filter(tag => tag.id !== tagId); 4069 | 4070 | if (filteredTags.length === tags.length) { 4071 | throw new Error('Tag not found'); 4072 | } 4073 | 4074 | // Remove tag from all searches 4075 | const history = loadSearchHistory(); 4076 | const updatedHistory = history.map(search => ({ 4077 | ...search, 4078 | tags: (search.tags || []).filter(id => id !== tagId) 4079 | })); 4080 | 4081 | localStorage.setItem('chatgpt-product-search-history', JSON.stringify(updatedHistory)); 4082 | saveTags(filteredTags); 4083 | return true; 4084 | } 4085 | 4086 | function deleteProject(projectId) { 4087 | const projects = loadProjects(); 4088 | const filteredProjects = projects.filter(project => project.id !== projectId); 4089 | 4090 | if (filteredProjects.length === projects.length) { 4091 | throw new Error('Project not found'); 4092 | } 4093 | 4094 | // Remove project from all searches 4095 | const history = loadSearchHistory(); 4096 | const updatedHistory = history.map(search => ({ 4097 | ...search, 4098 | projectId: search.projectId === projectId ? null : search.projectId 4099 | })); 4100 | 4101 | localStorage.setItem('chatgpt-product-search-history', JSON.stringify(updatedHistory)); 4102 | saveProjects(filteredProjects); 4103 | return true; 4104 | } 4105 | 4106 | function updateTagUsage(tagId) { 4107 | const tags = loadTags(); 4108 | const tag = tags.find(t => t.id === tagId); 4109 | if (tag) { 4110 | tag.usageCount = (tag.usageCount || 0) + 1; 4111 | saveTags(tags); 4112 | } 4113 | } 4114 | 4115 | function updateProjectSearchCount(projectId) { 4116 | const projects = loadProjects(); 4117 | const project = projects.find(p => p.id === projectId); 4118 | if (project) { 4119 | project.searchCount = (project.searchCount || 0) + 1; 4120 | saveProjects(projects); 4121 | } 4122 | } 4123 | 4124 | function decrementTagUsage(tagId) { 4125 | const tags = loadTags(); 4126 | const tag = tags.find(t => t.id === tagId); 4127 | if (tag) { 4128 | tag.usageCount = Math.max((tag.usageCount || 0) - 1, 0); 4129 | saveTags(tags); 4130 | } 4131 | } 4132 | 4133 | function decrementProjectSearchCount(projectId) { 4134 | const projects = loadProjects(); 4135 | const project = projects.find(p => p.id === projectId); 4136 | if (project) { 4137 | project.searchCount = Math.max((project.searchCount || 0) - 1, 0); 4138 | saveProjects(projects); 4139 | } 4140 | } 4141 | 4142 | function recalculateAllCounts() { 4143 | // Recalculate project and tag counts from actual search history 4144 | const history = loadSearchHistory(); 4145 | const projects = loadProjects(); 4146 | const tags = loadTags(); 4147 | 4148 | // Reset all counts 4149 | projects.forEach(project => project.searchCount = 0); 4150 | tags.forEach(tag => tag.usageCount = 0); 4151 | 4152 | // Count actual usage from history 4153 | history.forEach(item => { 4154 | if (item.projectId) { 4155 | const project = projects.find(p => p.id === item.projectId); 4156 | if (project) { 4157 | project.searchCount = (project.searchCount || 0) + 1; 4158 | } 4159 | } 4160 | 4161 | if (item.tags && Array.isArray(item.tags)) { 4162 | item.tags.forEach(tagId => { 4163 | const tag = tags.find(t => t.id === tagId); 4164 | if (tag) { 4165 | tag.usageCount = (tag.usageCount || 0) + 1; 4166 | } 4167 | }); 4168 | } 4169 | }); 4170 | 4171 | // Save updated counts 4172 | saveProjects(projects); 4173 | saveTags(tags); 4174 | 4175 | // Update sidebar display 4176 | populateProjectsList(); 4177 | populateTagsList(); 4178 | } 4179 | 4180 | // Migration function for existing data 4181 | function migrateSearchHistoryData() { 4182 | try { 4183 | const history = loadSearchHistory(); 4184 | let migrationNeeded = false; 4185 | 4186 | const migratedHistory = history.map(search => { 4187 | // Check if this search needs migration (version 2 format) 4188 | if (!search.version || search.version < 2) { 4189 | migrationNeeded = true; 4190 | return { 4191 | ...search, 4192 | tags: search.tags || [], 4193 | projectId: search.projectId || null, 4194 | version: 2 4195 | }; 4196 | } 4197 | return search; 4198 | }); 4199 | 4200 | if (migrationNeeded) { 4201 | localStorage.setItem('chatgpt-product-search-history', JSON.stringify(migratedHistory)); 4202 | } 4203 | 4204 | return true; 4205 | } catch (error) { 4206 | return false; 4207 | } 4208 | } 4209 | 4210 | // ===== END ENHANCED DATA MANAGEMENT FUNCTIONS ===== 4211 | 4212 | // ===== WELCOME STATE HELPER ===== 4213 | 4214 | function createWelcomeState() { 4215 | return ` 4216 | <div id="welcome-state" style=" 4217 | text-align: center; 4218 | padding: 60px 40px; 4219 | color: #5e6f9b; 4220 | display: flex; 4221 | flex-direction: column; 4222 | align-items: center; 4223 | justify-content: center; 4224 | height: 100%; 4225 | min-height: 300px; 4226 | "> 4227 | <img src="${searchIconUrl}" alt="Search" style="width: 52px; height: 52px; margin-bottom: 22px; opacity: 0.9; filter: hue-rotate(18deg) saturate(1.1);" /> 4228 | <h3 style=" 4229 | margin: 0 0 12px 0; 4230 | font-size: 20px; 4231 | font-weight: 600; 4232 | color: #27325f; 4233 | ">Product Search</h3> 4234 | <p style=" 4235 | margin: 0 0 24px 0; 4236 | font-size: 16px; 4237 | line-height: 1.5; 4238 | max-width: 400px; 4239 | color: #465584; 4240 | ">Search for product reviews, comparisons, and detailed information from across the web</p> 4241 | <div style=" 4242 | padding: 4px 0 4px 18px; 4243 | border-left: 4px solid #5b8def; 4244 | max-width: 520px; 4245 | text-align: left; 4246 | "> 4247 | <div style="font-weight: 600; margin-bottom: 8px; color: #27325f;">Try searching for:</div> 4248 | <div style="color: #556694; font-size: 14px; line-height: 1.6;"> 4249 | • "iPhone 17 Pro camera quality"<br> 4250 | • "Nike Air Max running shoes"<br> 4251 | • "MacBook Air M3 performance"<br> 4252 | • "Pets Deli Hunde Nassfutter reviews" 4253 | </div> 4254 | </div> 4255 | <div id="auth-status" style=" 4256 | margin-top: 20px; 4257 | padding: 8px 16px; 4258 | border-radius: 20px; 4259 | font-size: 13px; 4260 | font-weight: 500; 4261 | background: #fff3cd; 4262 | color: #856404; 4263 | border: 1px solid #ffeeba; 4264 | display: inline-flex; 4265 | align-items: center; 4266 | gap: 8px; 4267 | "><span class="status-icon status-icon--medium status-icon--warning" aria-hidden="true"></span><span>Checking authentication...</span></div> 4268 | </div> 4269 | `; 4270 | } 4271 | 4272 | // ===== END WELCOME STATE HELPER ===== 4273 | 4274 | // ===== EXPORT/IMPORT FUNCTIONALITY ===== 4275 | 4276 | function initializeExportImportTab() { 4277 | // Update data counts 4278 | updateDataCounts(); 4279 | 4280 | // Populate project selector 4281 | populateProjectSelector(); 4282 | 4283 | // Set up event listeners 4284 | const exportBtn = document.getElementById('export-data-btn'); 4285 | const importBtn = document.getElementById('import-data-btn'); 4286 | const selectFileBtn = document.getElementById('select-import-file-btn'); 4287 | const fileInput = document.getElementById('import-file-input'); 4288 | const exportScopeAll = document.getElementById('export-scope-all'); 4289 | const exportScopeProject = document.getElementById('export-scope-project'); 4290 | 4291 | if (exportBtn) { 4292 | exportBtn.addEventListener('click', exportData); 4293 | } 4294 | 4295 | if (importBtn) { 4296 | importBtn.addEventListener('click', importData); 4297 | } 4298 | 4299 | if (selectFileBtn && fileInput) { 4300 | selectFileBtn.addEventListener('click', () => fileInput.click()); 4301 | fileInput.addEventListener('change', handleFileSelect); 4302 | } 4303 | 4304 | // Export scope change listeners 4305 | if (exportScopeAll) { 4306 | exportScopeAll.addEventListener('change', handleExportScopeChange); 4307 | } 4308 | if (exportScopeProject) { 4309 | exportScopeProject.addEventListener('change', handleExportScopeChange); 4310 | } 4311 | 4312 | // Project selection change listener 4313 | const projectSelector = document.getElementById('export-project-selector'); 4314 | if (projectSelector) { 4315 | projectSelector.addEventListener('change', handleProjectSelectionChange); 4316 | } 4317 | } 4318 | 4319 | function updateDataCounts() { 4320 | const exportScope = document.querySelector('input[name="export-scope"]:checked')?.value; 4321 | const selectedProjectId = document.getElementById('export-project-selector')?.value; 4322 | 4323 | const tagsCountEl = document.getElementById('tags-count'); 4324 | const projectsCountEl = document.getElementById('projects-count'); 4325 | const historyCountEl = document.getElementById('history-count'); 4326 | 4327 | if (exportScope === 'project' && selectedProjectId) { 4328 | // Calculate counts for selected project only 4329 | const projectCounts = calculateProjectCounts(selectedProjectId); 4330 | 4331 | if (tagsCountEl) tagsCountEl.textContent = projectCounts.tags; 4332 | if (projectsCountEl) projectsCountEl.textContent = 1; // Always 1 for single project 4333 | if (historyCountEl) historyCountEl.textContent = projectCounts.history; 4334 | } else { 4335 | // Show total counts for all data 4336 | const tags = loadTags(); 4337 | const projects = loadProjects(); 4338 | const history = loadSearchHistory(); 4339 | 4340 | if (tagsCountEl) tagsCountEl.textContent = tags.length; 4341 | if (projectsCountEl) projectsCountEl.textContent = projects.length; 4342 | if (historyCountEl) historyCountEl.textContent = history.length; 4343 | } 4344 | } 4345 | 4346 | function calculateProjectCounts(projectId) { 4347 | // Get searches for this project 4348 | const projectHistory = loadSearchHistory().filter(item => item.projectId === projectId); 4349 | 4350 | // Count unique tags used in this project 4351 | const usedTagIds = new Set(); 4352 | projectHistory.forEach(item => { 4353 | if (item.tags && Array.isArray(item.tags)) { 4354 | item.tags.forEach(tagId => usedTagIds.add(tagId)); 4355 | } 4356 | }); 4357 | 4358 | return { 4359 | tags: usedTagIds.size, 4360 | history: projectHistory.length 4361 | }; 4362 | } 4363 | 4364 | function populateProjectSelector() { 4365 | const selector = document.getElementById('export-project-selector'); 4366 | if (!selector) return; 4367 | 4368 | const projects = loadProjects(); 4369 | 4370 | // Clear existing options except the first one 4371 | selector.innerHTML = '<option value="">Choose a project...</option>'; 4372 | 4373 | // Add project options 4374 | projects.forEach(project => { 4375 | const option = document.createElement('option'); 4376 | option.value = project.id; 4377 | option.textContent = project.name; 4378 | selector.appendChild(option); 4379 | }); 4380 | 4381 | // If no projects, disable the selector 4382 | if (projects.length === 0) { 4383 | const option = document.createElement('option'); 4384 | option.value = ''; 4385 | option.textContent = 'No projects available'; 4386 | option.disabled = true; 4387 | selector.appendChild(option); 4388 | selector.disabled = true; 4389 | } else { 4390 | selector.disabled = false; 4391 | } 4392 | 4393 | // Re-add event listener after repopulating 4394 | selector.removeEventListener('change', handleProjectSelectionChange); 4395 | selector.addEventListener('change', handleProjectSelectionChange); 4396 | 4397 | // Update counts when project list changes 4398 | updateDataCounts(); 4399 | } 4400 | 4401 | function handleExportScopeChange() { 4402 | const projectScope = document.getElementById('export-scope-project'); 4403 | const projectContainer = document.getElementById('project-selection-container'); 4404 | 4405 | if (projectScope && projectContainer) { 4406 | if (projectScope.checked) { 4407 | projectContainer.style.display = 'block'; 4408 | // Refresh project list in case it changed 4409 | populateProjectSelector(); 4410 | } else { 4411 | projectContainer.style.display = 'none'; 4412 | } 4413 | } 4414 | 4415 | // Update counts when scope changes 4416 | updateDataCounts(); 4417 | } 4418 | 4419 | function handleProjectSelectionChange() { 4420 | // Update counts when project selection changes 4421 | updateDataCounts(); 4422 | } 4423 | 4424 | function exportData() { 4425 | try { 4426 | const exportTagsChecked = document.getElementById('export-tags')?.checked; 4427 | const exportProjectsChecked = document.getElementById('export-projects')?.checked; 4428 | const exportHistoryChecked = document.getElementById('export-history')?.checked; 4429 | const exportScope = document.querySelector('input[name="export-scope"]:checked')?.value; 4430 | const selectedProjectId = document.getElementById('export-project-selector')?.value; 4431 | 4432 | if (!exportTagsChecked && !exportProjectsChecked && !exportHistoryChecked) { 4433 | showExportImportStatus('Please select at least one data type to export.', 'error'); 4434 | return; 4435 | } 4436 | 4437 | // Validate project selection if single project scope is chosen 4438 | if (exportScope === 'project' && !selectedProjectId) { 4439 | showExportImportStatus('Please select a project to export.', 'error'); 4440 | return; 4441 | } 4442 | 4443 | const exportData = { 4444 | version: '1.0', 4445 | exportDate: new Date().toISOString(), 4446 | exportScope: exportScope || 'all', 4447 | data: {} 4448 | }; 4449 | 4450 | // Add project info if exporting single project 4451 | if (exportScope === 'project' && selectedProjectId) { 4452 | const allProjects = loadProjects(); 4453 | const selectedProject = allProjects.find(p => p.id === selectedProjectId); 4454 | if (selectedProject) { 4455 | exportData.projectInfo = { 4456 | id: selectedProject.id, 4457 | name: selectedProject.name, 4458 | created: selectedProject.created 4459 | }; 4460 | } 4461 | } 4462 | 4463 | if (exportTagsChecked) { 4464 | if (exportScope === 'project' && selectedProjectId) { 4465 | // Get only tags used in this project's searches 4466 | const projectHistory = loadSearchHistory().filter(item => item.projectId === selectedProjectId); 4467 | const usedTagIds = new Set(); 4468 | projectHistory.forEach(item => { 4469 | if (item.tags && Array.isArray(item.tags)) { 4470 | item.tags.forEach(tagId => usedTagIds.add(tagId)); 4471 | } 4472 | }); 4473 | const allTags = loadTags(); 4474 | exportData.data.tags = allTags.filter(tag => usedTagIds.has(tag.id)); 4475 | } else { 4476 | exportData.data.tags = loadTags(); 4477 | } 4478 | } 4479 | 4480 | if (exportProjectsChecked) { 4481 | if (exportScope === 'project' && selectedProjectId) { 4482 | // Export only the selected project 4483 | const allProjects = loadProjects(); 4484 | exportData.data.projects = allProjects.filter(project => project.id === selectedProjectId); 4485 | } else { 4486 | exportData.data.projects = loadProjects(); 4487 | } 4488 | } 4489 | 4490 | if (exportHistoryChecked) { 4491 | if (exportScope === 'project' && selectedProjectId) { 4492 | // Export only searches from this project 4493 | exportData.data.history = loadSearchHistory().filter(item => item.projectId === selectedProjectId); 4494 | } else { 4495 | exportData.data.history = loadSearchHistory(); 4496 | } 4497 | } 4498 | 4499 | // Create and download file 4500 | const dataStr = JSON.stringify(exportData, null, 2); 4501 | const dataBlob = new Blob([dataStr], { type: 'application/json' }); 4502 | const url = URL.createObjectURL(dataBlob); 4503 | 4504 | const link = document.createElement('a'); 4505 | const filenameSuffix = exportScope === 'project' && selectedProjectId ? 4506 | `-${exportData.projectInfo?.name?.replace(/[^a-z0-9]/gi, '_').toLowerCase() || 'project'}` : 4507 | ''; 4508 | link.href = url; 4509 | link.download = `chatgpt-product-search-backup${filenameSuffix}-${new Date().toISOString().split('T')[0]}.json`; 4510 | 4511 | document.body.appendChild(link); 4512 | link.click(); 4513 | document.body.removeChild(link); 4514 | 4515 | URL.revokeObjectURL(url); 4516 | 4517 | const message = exportScope === 'project' ? 4518 | `Project "${exportData.projectInfo?.name || 'Unknown'}" exported successfully!` : 4519 | 'Data exported successfully!'; 4520 | showExportImportStatus(message, 'success'); 4521 | 4522 | } catch (error) { 4523 | console.error('Export failed:', error); 4524 | showExportImportStatus('Export failed. Please try again.', 'error'); 4525 | } 4526 | } 4527 | 4528 | let pendingImportData = null; 4529 | 4530 | function handleFileSelect(event) { 4531 | const file = event.target.files[0]; 4532 | if (!file) return; 4533 | 4534 | const fileInfo = document.getElementById('selected-file-info'); 4535 | if (fileInfo) { 4536 | fileInfo.textContent = `Selected: ${file.name} (${(file.size / 1024).toFixed(2)} KB)`; 4537 | fileInfo.style.display = 'block'; 4538 | } 4539 | 4540 | const reader = new FileReader(); 4541 | reader.onload = function(e) { 4542 | try { 4543 | const importedData = JSON.parse(e.target.result); 4544 | 4545 | if (validateImportData(importedData)) { 4546 | pendingImportData = importedData; 4547 | displayImportPreview(importedData); 4548 | enableImportButton(); 4549 | } else { 4550 | showExportImportStatus('Invalid file format. Please select a valid export file.', 'error'); 4551 | disableImportButton(); 4552 | } 4553 | } catch (error) { 4554 | showExportImportStatus('Error reading file. Please ensure it\'s a valid JSON file.', 'error'); 4555 | disableImportButton(); 4556 | } 4557 | }; 4558 | 4559 | reader.readAsText(file); 4560 | } 4561 | 4562 | function validateImportData(data) { 4563 | if (!data || typeof data !== 'object') return false; 4564 | if (!data.version || !data.data) return false; 4565 | 4566 | const { tags, projects, history } = data.data; 4567 | 4568 | // Validate tags structure 4569 | if (tags && (!Array.isArray(tags) || !tags.every(tag => 4570 | tag.id && tag.name && typeof tag.created === 'number'))) { 4571 | return false; 4572 | } 4573 | 4574 | // Validate projects structure 4575 | if (projects && (!Array.isArray(projects) || !projects.every(project => 4576 | project.id && project.name && typeof project.created === 'number'))) { 4577 | return false; 4578 | } 4579 | 4580 | // Validate history structure 4581 | if (history && (!Array.isArray(history) || !history.every(item => 4582 | item.id && item.query && item.timestamp))) { 4583 | return false; 4584 | } 4585 | 4586 | return true; 4587 | } 4588 | 4589 | function displayImportPreview(importedData) { 4590 | const preview = document.getElementById('import-preview'); 4591 | const previewContent = document.getElementById('import-preview-content'); 4592 | 4593 | if (!preview || !previewContent) return; 4594 | 4595 | const { tags, projects, history } = importedData.data; 4596 | let previewHTML = ''; 4597 | 4598 | if (tags && tags.length > 0) { 4599 | previewHTML += `<div><strong>Tags:</strong> ${tags.length} items</div>`; 4600 | } 4601 | 4602 | if (projects && projects.length > 0) { 4603 | previewHTML += `<div><strong>Projects:</strong> ${projects.length} items</div>`; 4604 | } 4605 | 4606 | if (history && history.length > 0) { 4607 | previewHTML += `<div><strong>History:</strong> ${history.length} items</div>`; 4608 | } 4609 | 4610 | previewHTML += `<div style="margin-top: 8px; font-size: 12px; color: #6c757d;">Export Date: ${new Date(importedData.exportDate).toLocaleString()}</div>`; 4611 | 4612 | previewContent.innerHTML = previewHTML; 4613 | preview.style.display = 'block'; 4614 | } 4615 | 4616 | function enableImportButton() { 4617 | const importBtn = document.getElementById('import-data-btn'); 4618 | if (importBtn) { 4619 | importBtn.style.background = '#28a745'; 4620 | importBtn.style.cursor = 'pointer'; 4621 | importBtn.disabled = false; 4622 | } 4623 | } 4624 | 4625 | function disableImportButton() { 4626 | const importBtn = document.getElementById('import-data-btn'); 4627 | if (importBtn) { 4628 | importBtn.style.background = '#6c757d'; 4629 | importBtn.style.cursor = 'not-allowed'; 4630 | importBtn.disabled = true; 4631 | } 4632 | 4633 | const preview = document.getElementById('import-preview'); 4634 | if (preview) { 4635 | preview.style.display = 'none'; 4636 | } 4637 | } 4638 | 4639 | function importData() { 4640 | if (!pendingImportData) { 4641 | showExportImportStatus('No file selected for import.', 'error'); 4642 | return; 4643 | } 4644 | 4645 | try { 4646 | const { tags, projects, history } = pendingImportData.data; 4647 | let importResults = { added: 0, skipped: 0 }; 4648 | 4649 | // ID mappings to maintain relationships 4650 | const tagIdMapping = {}; // oldId -> newId 4651 | const projectIdMapping = {}; // oldId -> newId 4652 | 4653 | // Import tags 4654 | if (tags && tags.length > 0) { 4655 | const existingTags = loadTags(); 4656 | const existingTagNames = existingTags.map(t => t.name.toLowerCase()); 4657 | 4658 | tags.forEach(tag => { 4659 | if (!existingTagNames.includes(tag.name.toLowerCase())) { 4660 | const newId = Date.now() + Math.random().toString(36).substr(2, 9); 4661 | tagIdMapping[tag.id] = newId; // Map old ID to new ID 4662 | 4663 | existingTags.push({ 4664 | ...tag, 4665 | id: newId 4666 | }); 4667 | importResults.added++; 4668 | } else { 4669 | // Find existing tag ID for mapping 4670 | const existingTag = existingTags.find(t => t.name.toLowerCase() === tag.name.toLowerCase()); 4671 | if (existingTag) { 4672 | tagIdMapping[tag.id] = existingTag.id; // Map old ID to existing ID 4673 | } 4674 | importResults.skipped++; 4675 | } 4676 | }); 4677 | 4678 | saveTags(existingTags); 4679 | } 4680 | 4681 | // Import projects 4682 | if (projects && projects.length > 0) { 4683 | const existingProjects = loadProjects(); 4684 | const existingProjectNames = existingProjects.map(p => p.name.toLowerCase()); 4685 | 4686 | projects.forEach(project => { 4687 | if (!existingProjectNames.includes(project.name.toLowerCase())) { 4688 | const newId = Date.now() + Math.random().toString(36).substr(2, 9); 4689 | projectIdMapping[project.id] = newId; // Map old ID to new ID 4690 | 4691 | existingProjects.push({ 4692 | ...project, 4693 | id: newId 4694 | }); 4695 | importResults.added++; 4696 | } else { 4697 | // Find existing project ID for mapping 4698 | const existingProject = existingProjects.find(p => p.name.toLowerCase() === project.name.toLowerCase()); 4699 | if (existingProject) { 4700 | projectIdMapping[project.id] = existingProject.id; // Map old ID to existing ID 4701 | } 4702 | importResults.skipped++; 4703 | } 4704 | }); 4705 | 4706 | saveProjects(existingProjects); 4707 | } 4708 | 4709 | // Import history with updated ID references 4710 | if (history && history.length > 0) { 4711 | const existingHistory = loadSearchHistory(); 4712 | 4713 | // Create a set of unique signatures from existing history for better duplicate detection 4714 | const existingSignatures = new Set( 4715 | existingHistory.map(h => `${h.query}|${h.timestamp}|${h.searchType || 'single'}`) 4716 | ); 4717 | 4718 | history.forEach(item => { 4719 | // Create unique signature for this item 4720 | const itemSignature = `${item.query}|${item.timestamp}|${item.searchType || 'single'}`; 4721 | 4722 | if (!existingSignatures.has(itemSignature)) { 4723 | const newHistoryItem = { 4724 | ...item, 4725 | id: Date.now() + Math.random().toString(36).substr(2, 9) // Generate new ID to avoid conflicts 4726 | }; 4727 | 4728 | // Update project reference if it exists and we have a mapping 4729 | if (item.projectId && projectIdMapping[item.projectId]) { 4730 | newHistoryItem.projectId = projectIdMapping[item.projectId]; 4731 | } else if (item.projectId && !projectIdMapping[item.projectId]) { 4732 | // Project reference exists but no mapping found, remove reference 4733 | newHistoryItem.projectId = null; 4734 | } 4735 | 4736 | // Update tag references if they exist and we have mappings 4737 | if (item.tags && Array.isArray(item.tags)) { 4738 | newHistoryItem.tags = item.tags 4739 | .map(tagId => tagIdMapping[tagId]) // Map to new IDs 4740 | .filter(tagId => tagId !== undefined); // Remove unmapped tags 4741 | } 4742 | 4743 | existingHistory.push(newHistoryItem); 4744 | existingSignatures.add(itemSignature); // Add to signatures to prevent duplicates in this import batch 4745 | importResults.added++; 4746 | } else { 4747 | importResults.skipped++; 4748 | } 4749 | }); 4750 | 4751 | localStorage.setItem('chatgpt-product-search-history', JSON.stringify(existingHistory)); 4752 | } 4753 | 4754 | // Update UI 4755 | updateDataCounts(); 4756 | populateTagsManagement(); 4757 | populateProjectsManagement(); 4758 | populateProjectsList(); 4759 | populateTagsList(); 4760 | 4761 | // Recalculate counts to reflect the imported history associations 4762 | recalculateAllCounts(); 4763 | 4764 | // Clear pending data 4765 | pendingImportData = null; 4766 | disableImportButton(); 4767 | 4768 | // Clear file input 4769 | const fileInput = document.getElementById('import-file-input'); 4770 | if (fileInput) fileInput.value = ''; 4771 | 4772 | const fileInfo = document.getElementById('selected-file-info'); 4773 | if (fileInfo) fileInfo.style.display = 'none'; 4774 | 4775 | showExportImportStatus( 4776 | `Import completed! Added ${importResults.added} items, skipped ${importResults.skipped} duplicates.`, 4777 | 'success' 4778 | ); 4779 | 4780 | } catch (error) { 4781 | console.error('Import failed:', error); 4782 | showExportImportStatus('Import failed. Please try again.', 'error'); 4783 | } 4784 | } 4785 | 4786 | function showExportImportStatus(message, type) { 4787 | const statusEl = document.getElementById('export-import-status'); 4788 | if (!statusEl) return; 4789 | 4790 | statusEl.textContent = message; 4791 | statusEl.style.display = 'block'; 4792 | 4793 | if (type === 'success') { 4794 | statusEl.style.background = '#d4edda'; 4795 | statusEl.style.color = '#155724'; 4796 | statusEl.style.border = '1px solid #c3e6cb'; 4797 | } else if (type === 'error') { 4798 | statusEl.style.background = '#f8d7da'; 4799 | statusEl.style.color = '#721c24'; 4800 | statusEl.style.border = '1px solid #f5c6cb'; 4801 | } 4802 | 4803 | setTimeout(() => { 4804 | statusEl.style.display = 'none'; 4805 | }, 5000); 4806 | } 4807 | 4808 | // ===== END EXPORT/IMPORT FUNCTIONALITY ===== 4809 | 4810 | // ===== SIDEBAR FUNCTIONALITY - Phase 2 ===== 4811 | 4812 | function initializeSidebar() { 4813 | // Populate sidebar with existing data 4814 | populateProjectsList(); 4815 | populateTagsList(); 4816 | 4817 | // Add event listeners for sidebar buttons 4818 | const settingsBtn = document.getElementById('settings-btn'); 4819 | const helpBtn = document.getElementById('help-btn'); 4820 | const addProjectBtn = document.getElementById('add-project-btn'); 4821 | const addTagBtn = document.getElementById('add-tag-btn'); 4822 | 4823 | if (settingsBtn) { 4824 | settingsBtn.addEventListener('click', openSettingsModal); 4825 | } 4826 | 4827 | if (helpBtn) { 4828 | helpBtn.addEventListener('click', resetTutorial); 4829 | } 4830 | 4831 | if (addProjectBtn) { 4832 | addProjectBtn.addEventListener('click', quickAddProject); 4833 | } 4834 | 4835 | if (addTagBtn) { 4836 | addTagBtn.addEventListener('click', quickAddTag); 4837 | } 4838 | } 4839 | 4840 | function escapeAttributeValue(value) { 4841 | if (typeof value !== 'string') { 4842 | return ''; 4843 | } 4844 | 4845 | return value 4846 | .replace(/&/g, '&') 4847 | .replace(/</g, '<') 4848 | .replace(/>/g, '>') 4849 | .replace(/"/g, '"'); 4850 | } 4851 | 4852 | function escapeHTML(value) { 4853 | if (typeof value !== 'string') { 4854 | return ''; 4855 | } 4856 | 4857 | return value 4858 | .replace(/&/g, '&') 4859 | .replace(/</g, '<') 4860 | .replace(/>/g, '>'); 4861 | } 4862 | 4863 | function populateProjectsList() { 4864 | const projectsList = document.getElementById('projects-list'); 4865 | if (!projectsList) return; 4866 | 4867 | const projects = loadProjects(); 4868 | 4869 | if (projects.length === 0) { 4870 | projectsList.innerHTML = ` 4871 | <div style=" 4872 | padding: 8px; 4873 | text-align: center; 4874 | color: #6c757d; 4875 | font-size: 12px; 4876 | font-style: italic; 4877 | ">No projects yet</div> 4878 | `; 4879 | return; 4880 | } 4881 | 4882 | projectsList.innerHTML = projects.map(project => { 4883 | const descriptionAttr = project.description 4884 | ? ` title="${escapeAttributeValue(project.description)}"` 4885 | : ''; 4886 | 4887 | return ` 4888 | <div class="sidebar-project" data-project-id="${project.id}"> 4889 | <div style=" 4890 | display: flex; 4891 | justify-content: space-between; 4892 | align-items: center; 4893 | "> 4894 | <span${descriptionAttr} style="font-weight: 500; display: flex; align-items: center; gap: 6px;"><img src="${projectIconUrl}" alt="Project" style="width: 16px; height: 16px;" />${project.name}</span> 4895 | <span style=" 4896 | font-size: 11px; 4897 | color: #6c757d; 4898 | background: #f8f9fa; 4899 | padding: 1px 4px; 4900 | border-radius: 8px; 4901 | ">${project.searchCount || 0}</span> 4902 | </div> 4903 | </div> 4904 | `; 4905 | }).join(''); 4906 | 4907 | // Add click handlers for project filtering 4908 | document.querySelectorAll('.sidebar-project').forEach(element => { 4909 | element.addEventListener('click', () => { 4910 | const projectId = element.getAttribute('data-project-id'); 4911 | filterByProject(projectId); 4912 | }); 4913 | }); 4914 | } 4915 | 4916 | function populateTagsList() { 4917 | const tagsList = document.getElementById('tags-list'); 4918 | if (!tagsList) return; 4919 | 4920 | const tags = loadTags(); 4921 | 4922 | if (tags.length === 0) { 4923 | tagsList.innerHTML = ` 4924 | <div style=" 4925 | padding: 8px; 4926 | text-align: center; 4927 | color: #6c757d; 4928 | font-size: 12px; 4929 | font-style: italic; 4930 | ">No tags yet</div> 4931 | `; 4932 | return; 4933 | } 4934 | 4935 | tagsList.innerHTML = tags.map(tag => ` 4936 | <div class="sidebar-tag" data-tag-id="${tag.id}" style=" 4937 | border: 1px solid ${tag.color}55; 4938 | background: ${tag.color}26; 4939 | color: #142049; 4940 | "> 4941 | <span style=" 4942 | display: flex; 4943 | align-items: center; 4944 | gap: 4px; 4945 | "> 4946 | <img src="${tagIconUrl}" alt="Tag" style="width: 14px; height: 14px;" />${tag.name} 4947 | </span> 4948 | <span style=" 4949 | font-size: 10px; 4950 | color: #2d3d6e; 4951 | background: rgba(255, 255, 255, 0.8); 4952 | padding: 1px 4px; 4953 | border-radius: 6px; 4954 | ">${tag.usageCount || 0}</span> 4955 | </div> 4956 | `).join(''); 4957 | 4958 | // Add click handlers for tag filtering 4959 | document.querySelectorAll('.sidebar-tag').forEach(element => { 4960 | element.addEventListener('click', () => { 4961 | const tagId = element.getAttribute('data-tag-id'); 4962 | filterByTag(tagId); 4963 | }); 4964 | }); 4965 | } 4966 | 4967 | function quickAddProject() { 4968 | const name = prompt('Enter project name:'); 4969 | if (!name || !name.trim()) return; 4970 | 4971 | try { 4972 | const project = createProject(name.trim()); 4973 | populateProjectsList(); 4974 | // Refresh project selector in export section if it exists 4975 | if (document.getElementById('export-project-selector')) { 4976 | populateProjectSelector(); 4977 | } 4978 | alert(`Project "${project.name}" created successfully!`); 4979 | } catch (error) { 4980 | alert(`Error: ${error.message}`); 4981 | } 4982 | } 4983 | 4984 | function quickAddTag() { 4985 | const name = prompt('Enter tag name:'); 4986 | if (!name || !name.trim()) return; 4987 | 4988 | try { 4989 | const tag = createTag(name.trim()); 4990 | populateTagsList(); 4991 | alert(`Tag "${tag.name}" created successfully!`); 4992 | } catch (error) { 4993 | alert(`Error: ${error.message}`); 4994 | } 4995 | } 4996 | 4997 | // filterByProject function removed - duplicate implementation exists below 4998 | 4999 | // filterByTag function removed - duplicate implementation exists below 5000 | 5001 | function openSettingsModal() { 5002 | // Remove existing settings modal if present 5003 | const existingModal = document.getElementById('settings-modal'); 5004 | if (existingModal) { 5005 | existingModal.remove(); 5006 | } 5007 | 5008 | const settingsModalHTML = ` 5009 | <div id="settings-modal" style=" 5010 | position: fixed; 5011 | top: 0; 5012 | left: 0; 5013 | width: 100%; 5014 | height: 100%; 5015 | background: rgba(0, 0, 0, 0.5); 5016 | display: flex; 5017 | align-items: center; 5018 | justify-content: center; 5019 | z-index: 10002; 5020 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 5021 | "> 5022 | <div style=" 5023 | background: white; 5024 | width: 700px; 5025 | max-width: 90vw; 5026 | height: 500px; 5027 | max-height: 90vh; 5028 | border-radius: 8px; 5029 | display: flex; 5030 | flex-direction: column; 5031 | overflow: hidden; 5032 | box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); 5033 | "> 5034 | <!-- Modal Header --> 5035 | <div style=" 5036 | background: #f8f9fa; 5037 | padding: 16px 20px; 5038 | display: flex; 5039 | justify-content: space-between; 5040 | align-items: center; 5041 | border-bottom: 1px solid #e9ecef; 5042 | "> 5043 | <h2 style=" 5044 | font-size: 18px; 5045 | font-weight: 600; 5046 | margin: 0; 5047 | color: #495057; display: flex; align-items: center;"><img src="${settingsIconUrl}" alt="Settings" style="width: 20px;height: 20px;margin-right: 5px;" />Settings</h2> 5048 | <button id="close-settings-modal" style=" 5049 | background: none; 5050 | border: none; 5051 | color: #6c757d; 5052 | font-size: 20px; 5053 | width: 30px; 5054 | height: 30px; 5055 | border-radius: 4px; 5056 | cursor: pointer; 5057 | display: flex; 5058 | align-items: center; 5059 | justify-content: center; 5060 | ">×</button> 5061 | </div> 5062 | 5063 | <!-- Settings Tabs --> 5064 | <div style=" 5065 | display: flex; 5066 | background: #f8f9fa; 5067 | border-bottom: 1px solid #e9ecef; 5068 | "> 5069 | <button id="tags-settings-tab" class="settings-tab-button active-settings-tab" style=" 5070 | flex: 1; 5071 | padding: 12px 20px; 5072 | border: none; 5073 | background: white; 5074 | color: #495057; 5075 | font-size: 14px; 5076 | font-weight: 500; 5077 | cursor: pointer; 5078 | border-bottom: 2px solid #5b8def; 5079 | transition: all 0.2s ease; 5080 | display: flex; 5081 | align-items: center; 5082 | justify-content: center; 5083 | gap: 8px; 5084 | "><img src="${tagIconUrl}" alt="Tags" style="width: 20px; height: 20px;" />Tags</button> 5085 | <button id="projects-settings-tab" class="settings-tab-button" style=" 5086 | flex: 1; 5087 | padding: 12px 20px; 5088 | border: none; 5089 | background: #f8f9fa; 5090 | color: #6c757d; 5091 | font-size: 14px; 5092 | font-weight: 500; 5093 | cursor: pointer; 5094 | border-bottom: 2px solid transparent; 5095 | transition: all 0.2s ease; 5096 | display: flex; 5097 | align-items: center; 5098 | justify-content: center; 5099 | gap: 8px; 5100 | "><img src="${projectIconUrl}" alt="Projects" style="width: 20px; height: 20px;" />Projects</button> 5101 | <button id="export-import-settings-tab" class="settings-tab-button" style=" 5102 | flex: 1; 5103 | padding: 12px 20px; 5104 | border: none; 5105 | background: #f8f9fa; 5106 | color: #6c757d; 5107 | font-size: 14px; 5108 | font-weight: 500; 5109 | cursor: pointer; 5110 | border-bottom: 2px solid transparent; 5111 | transition: all 0.2s ease; 5112 | display: flex; 5113 | align-items: center; 5114 | justify-content: center; 5115 | gap: 8px; 5116 | "><img src="${databaseIconUrl}" alt="Export/Import" style="width: 20px; height: 20px;" />Export/Import</button> 5117 | </div> 5118 | 5119 | <!-- Settings Content --> 5120 | <div style=" 5121 | flex: 1; 5122 | overflow: hidden; 5123 | display: flex; 5124 | flex-direction: column; 5125 | "> 5126 | <!-- Tags Settings --> 5127 | <div id="tags-settings-content" style=" 5128 | flex: 1; 5129 | padding: 20px; 5130 | overflow-y: auto; 5131 | display: block; 5132 | "> 5133 | <div style=" 5134 | display: flex; 5135 | justify-content: space-between; 5136 | align-items: center; 5137 | margin-bottom: 20px; 5138 | "> 5139 | <h3 style="margin: 0; color: #495057;">Manage Tags</h3> 5140 | <button id="create-tag-btn" style=" 5141 | background: #5b8def; 5142 | color: white; 5143 | border: none; 5144 | padding: 8px 16px; 5145 | border-radius: 4px; 5146 | font-size: 14px; 5147 | cursor: pointer; 5148 | ">+ New Tag</button> 5149 | </div> 5150 | <div id="tags-management-list"> 5151 | <!-- Tags list will be populated here --> 5152 | </div> 5153 | </div> 5154 | 5155 | <!-- Projects Settings --> 5156 | <div id="projects-settings-content" style=" 5157 | flex: 1; 5158 | padding: 20px; 5159 | overflow-y: auto; 5160 | display: none; 5161 | "> 5162 | <div style=" 5163 | display: flex; 5164 | justify-content: space-between; 5165 | align-items: center; 5166 | margin-bottom: 20px; 5167 | "> 5168 | <h3 style="margin: 0; color: #495057;">Manage Projects</h3> 5169 | <button id="create-project-btn" style=" 5170 | background: #5b8def; 5171 | color: white; 5172 | border: none; 5173 | padding: 8px 16px; 5174 | border-radius: 4px; 5175 | font-size: 14px; 5176 | cursor: pointer; 5177 | ">+ New Project</button> 5178 | </div> 5179 | <div id="projects-management-list"> 5180 | <!-- Projects list will be populated here --> 5181 | </div> 5182 | </div> 5183 | 5184 | <!-- Export/Import Settings --> 5185 | <div id="export-import-settings-content" style=" 5186 | flex: 1; 5187 | padding: 20px; 5188 | overflow-y: auto; 5189 | display: none; 5190 | "> 5191 | <h3 style="margin: 0 0 20px 0; color: #495057;">Export/Import Data</h3> 5192 | 5193 | <!-- Export Section --> 5194 | <div style=" 5195 | background: #f8f9fa; 5196 | border-radius: 6px; 5197 | padding: 16px; 5198 | margin-bottom: 24px; 5199 | border: 1px solid #e9ecef; 5200 | "> 5201 | <h4 style=" 5202 | margin: 0 0 12px 0; 5203 | color: #495057; 5204 | font-size: 16px; 5205 | display: flex; 5206 | align-items: center; 5207 | gap: 8px; 5208 | ">📤 Export Data</h4> 5209 | <p style=" 5210 | margin: 0 0 16px 0; 5211 | color: #6c757d; 5212 | font-size: 14px; 5213 | line-height: 1.4; 5214 | ">Export your tags, projects, and search history to a JSON file for backup or transfer to another browser.</p> 5215 | 5216 | <!-- Export Scope Selection --> 5217 | <div style="margin-bottom: 16px; padding: 12px; background: white; border: 1px solid #dee2e6; border-radius: 4px;"> 5218 | <h5 style="margin: 0 0 8px 0; color: #495057; font-size: 14px; font-weight: 600;">Export Scope</h5> 5219 | <div style="display: flex; gap: 16px; align-items: center;"> 5220 | <label style=" 5221 | display: flex; 5222 | align-items: center; 5223 | gap: 6px; 5224 | font-size: 14px; 5225 | color: #495057; 5226 | cursor: pointer; 5227 | "> 5228 | <input type="radio" name="export-scope" id="export-scope-all" value="all" checked style="margin: 0;"> 5229 | All Data 5230 | </label> 5231 | <label style=" 5232 | display: flex; 5233 | align-items: center; 5234 | gap: 6px; 5235 | font-size: 14px; 5236 | color: #495057; 5237 | cursor: pointer; 5238 | "> 5239 | <input type="radio" name="export-scope" id="export-scope-project" value="project" style="margin: 0;"> 5240 | Single Project 5241 | </label> 5242 | </div> 5243 | 5244 | <!-- Project Selection (hidden by default) --> 5245 | <div id="project-selection-container" style=" 5246 | margin-top: 12px; 5247 | display: none; 5248 | "> 5249 | <label style=" 5250 | display: block; 5251 | font-size: 13px; 5252 | color: #6c757d; 5253 | margin-bottom: 4px; 5254 | ">Select Project:</label> 5255 | <select id="export-project-selector" style=" 5256 | width: 100%; 5257 | padding: 6px 8px; 5258 | border: 1px solid #ced4da; 5259 | border-radius: 4px; 5260 | font-size: 14px; 5261 | background: white; 5262 | "> 5263 | <option value="">Choose a project...</option> 5264 | </select> 5265 | </div> 5266 | </div> 5267 | 5268 | <!-- Data Type Selection --> 5269 | <div style="margin-bottom: 16px;"> 5270 | <h5 style="margin: 0 0 8px 0; color: #495057; font-size: 14px; font-weight: 600;">Data Types</h5> 5271 | <div style="display: flex; flex-wrap: wrap; gap: 8px;"> 5272 | <label style=" 5273 | display: flex; 5274 | align-items: center; 5275 | gap: 6px; 5276 | font-size: 14px; 5277 | color: #495057; 5278 | cursor: pointer; 5279 | "> 5280 | <input type="checkbox" id="export-tags" checked style="margin: 0;"> 5281 | Tags (<span id="tags-count">0</span>) 5282 | </label> 5283 | <label style=" 5284 | display: flex; 5285 | align-items: center; 5286 | gap: 6px; 5287 | font-size: 14px; 5288 | color: #495057; 5289 | cursor: pointer; 5290 | "> 5291 | <input type="checkbox" id="export-projects" checked style="margin: 0;"> 5292 | Projects (<span id="projects-count">0</span>) 5293 | </label> 5294 | <label style=" 5295 | display: flex; 5296 | align-items: center; 5297 | gap: 6px; 5298 | font-size: 14px; 5299 | color: #495057; 5300 | cursor: pointer; 5301 | "> 5302 | <input type="checkbox" id="export-history" checked style="margin: 0;"> 5303 | History (<span id="history-count">0</span>) 5304 | </label> 5305 | </div> 5306 | </div> 5307 | 5308 | <button id="export-data-btn" style=" 5309 | background: #28a745; 5310 | color: white; 5311 | border: none; 5312 | padding: 10px 20px; 5313 | border-radius: 4px; 5314 | font-size: 14px; 5315 | cursor: pointer; 5316 | display: flex; 5317 | align-items: center; 5318 | gap: 8px; 5319 | ">Export Selected Data</button> 5320 | </div> 5321 | 5322 | <!-- Import Section --> 5323 | <div style=" 5324 | background: #f8f9fa; 5325 | border-radius: 6px; 5326 | padding: 16px; 5327 | border: 1px solid #e9ecef; 5328 | "> 5329 | <h4 style=" 5330 | margin: 0 0 12px 0; 5331 | color: #495057; 5332 | font-size: 16px; 5333 | display: flex; 5334 | align-items: center; 5335 | gap: 8px; 5336 | ">📥 Import Data</h4> 5337 | <p style=" 5338 | margin: 0 0 16px 0; 5339 | color: #6c757d; 5340 | font-size: 14px; 5341 | line-height: 1.4; 5342 | ">Import data from a previously exported JSON file. Duplicate items will be skipped automatically.</p> 5343 | 5344 | <div style="margin-bottom: 16px;"> 5345 | <input type="file" id="import-file-input" accept=".json" style=" 5346 | display: none; 5347 | "> 5348 | <button id="select-import-file-btn" style=" 5349 | background: #007bff; 5350 | color: white; 5351 | border: none; 5352 | padding: 10px 20px; 5353 | border-radius: 4px; 5354 | font-size: 14px; 5355 | cursor: pointer; 5356 | display: flex; 5357 | align-items: center; 5358 | gap: 8px; 5359 | ">Select JSON File</button> 5360 | <div id="selected-file-info" style=" 5361 | margin-top: 8px; 5362 | font-size: 13px; 5363 | color: #6c757d; 5364 | display: none; 5365 | "></div> 5366 | </div> 5367 | 5368 | <div id="import-preview" style=" 5369 | display: none; 5370 | background: white; 5371 | border: 1px solid #dee2e6; 5372 | border-radius: 4px; 5373 | padding: 12px; 5374 | margin-bottom: 16px; 5375 | max-height: 200px; 5376 | overflow-y: auto; 5377 | "> 5378 | <h5 style="margin: 0 0 8px 0; color: #495057; font-size: 14px;">Import Preview:</h5> 5379 | <div id="import-preview-content"></div> 5380 | </div> 5381 | 5382 | <button id="import-data-btn" style=" 5383 | background: #6c757d; 5384 | color: white; 5385 | border: none; 5386 | padding: 10px 20px; 5387 | border-radius: 4px; 5388 | font-size: 14px; 5389 | cursor: not-allowed; 5390 | display: flex; 5391 | align-items: center; 5392 | gap: 8px; 5393 | " disabled>Import Data</button> 5394 | </div> 5395 | 5396 | <div id="export-import-status" style=" 5397 | margin-top: 16px; 5398 | padding: 8px 12px; 5399 | border-radius: 4px; 5400 | font-size: 14px; 5401 | display: none; 5402 | "></div> 5403 | </div> 5404 | </div> 5405 | 5406 | <!-- Modal Footer --> 5407 | <div style=" 5408 | background: #f8f9fa; 5409 | padding: 16px 20px; 5410 | border-top: 1px solid #e9ecef; 5411 | display: flex; 5412 | justify-content: flex-end; 5413 | gap: 12px; 5414 | "> 5415 | <button id="cancel-settings" style=" 5416 | background: #6c757d; 5417 | color: white; 5418 | border: none; 5419 | padding: 8px 16px; 5420 | border-radius: 4px; 5421 | font-size: 14px; 5422 | cursor: pointer; 5423 | ">Cancel</button> 5424 | <button id="save-settings" style=" 5425 | background: #28a745; 5426 | color: white; 5427 | border: none; 5428 | padding: 8px 16px; 5429 | border-radius: 4px; 5430 | font-size: 14px; 5431 | cursor: pointer; 5432 | ">Save Changes</button> 5433 | </div> 5434 | </div> 5435 | </div> 5436 | `; 5437 | 5438 | // Create and append modal 5439 | const modalDiv = document.createElement('div'); 5440 | modalDiv.innerHTML = settingsModalHTML; 5441 | const modal = modalDiv.firstElementChild; 5442 | document.body.appendChild(modal); 5443 | 5444 | // Initialize settings modal 5445 | initializeSettingsModal(); 5446 | } 5447 | 5448 | function initializeSettingsModal() { 5449 | // Add event listeners for modal controls 5450 | const closeBtn = document.getElementById('close-settings-modal'); 5451 | const cancelBtn = document.getElementById('cancel-settings'); 5452 | const saveBtn = document.getElementById('save-settings'); 5453 | const tagsTab = document.getElementById('tags-settings-tab'); 5454 | const projectsTab = document.getElementById('projects-settings-tab'); 5455 | const exportImportTab = document.getElementById('export-import-settings-tab'); 5456 | const createTagBtn = document.getElementById('create-tag-btn'); 5457 | const createProjectBtn = document.getElementById('create-project-btn'); 5458 | 5459 | // Close modal handlers 5460 | if (closeBtn) { 5461 | closeBtn.addEventListener('click', closeSettingsModal); 5462 | } 5463 | if (cancelBtn) { 5464 | cancelBtn.addEventListener('click', closeSettingsModal); 5465 | } 5466 | if (saveBtn) { 5467 | saveBtn.addEventListener('click', saveSettingsAndClose); 5468 | } 5469 | 5470 | // Tab switching 5471 | if (tagsTab) { 5472 | tagsTab.addEventListener('click', () => switchSettingsTab('tags')); 5473 | } 5474 | if (projectsTab) { 5475 | projectsTab.addEventListener('click', () => switchSettingsTab('projects')); 5476 | } 5477 | if (exportImportTab) { 5478 | exportImportTab.addEventListener('click', () => switchSettingsTab('export-import')); 5479 | } 5480 | 5481 | // Create buttons 5482 | if (createTagBtn) { 5483 | createTagBtn.addEventListener('click', showCreateTagForm); 5484 | } 5485 | if (createProjectBtn) { 5486 | createProjectBtn.addEventListener('click', showCreateProjectForm); 5487 | } 5488 | 5489 | // Close on outside click 5490 | const modal = document.getElementById('settings-modal'); 5491 | if (modal) { 5492 | modal.addEventListener('click', (e) => { 5493 | if (e.target === modal) { 5494 | closeSettingsModal(); 5495 | } 5496 | }); 5497 | } 5498 | 5499 | // Populate initial content 5500 | populateTagsManagement(); 5501 | populateProjectsManagement(); 5502 | initializeExportImportTab(); 5503 | } 5504 | 5505 | function switchSettingsTab(tab) { 5506 | const tagsTab = document.getElementById('tags-settings-tab'); 5507 | const projectsTab = document.getElementById('projects-settings-tab'); 5508 | const exportImportTab = document.getElementById('export-import-settings-tab'); 5509 | const tagsContent = document.getElementById('tags-settings-content'); 5510 | const projectsContent = document.getElementById('projects-settings-content'); 5511 | const exportImportContent = document.getElementById('export-import-settings-content'); 5512 | 5513 | // Reset all tabs to inactive state 5514 | const tabs = [tagsTab, projectsTab, exportImportTab]; 5515 | const contents = [tagsContent, projectsContent, exportImportContent]; 5516 | 5517 | tabs.forEach(tabEl => { 5518 | if (tabEl) { 5519 | tabEl.style.background = '#f8f9fa'; 5520 | tabEl.style.color = '#6c757d'; 5521 | tabEl.style.borderBottom = '2px solid transparent'; 5522 | } 5523 | }); 5524 | 5525 | contents.forEach(contentEl => { 5526 | if (contentEl) { 5527 | contentEl.style.display = 'none'; 5528 | } 5529 | }); 5530 | 5531 | // Activate selected tab 5532 | if (tab === 'tags' && tagsTab && tagsContent) { 5533 | tagsTab.style.background = 'white'; 5534 | tagsTab.style.color = '#495057'; 5535 | tagsTab.style.borderBottom = '2px solid #5b8def'; 5536 | tagsContent.style.display = 'block'; 5537 | } else if (tab === 'projects' && projectsTab && projectsContent) { 5538 | projectsTab.style.background = 'white'; 5539 | projectsTab.style.color = '#495057'; 5540 | projectsTab.style.borderBottom = '2px solid #5b8def'; 5541 | projectsContent.style.display = 'block'; 5542 | } else if (tab === 'export-import' && exportImportTab && exportImportContent) { 5543 | exportImportTab.style.background = 'white'; 5544 | exportImportTab.style.color = '#495057'; 5545 | exportImportTab.style.borderBottom = '2px solid #5b8def'; 5546 | exportImportContent.style.display = 'block'; 5547 | } 5548 | } 5549 | 5550 | function populateTagsManagement() { 5551 | const tagsManagementList = document.getElementById('tags-management-list'); 5552 | if (!tagsManagementList) return; 5553 | 5554 | const tags = loadTags(); 5555 | 5556 | if (tags.length === 0) { 5557 | tagsManagementList.innerHTML = ` 5558 | <div style=" 5559 | text-align: center; 5560 | padding: 40px; 5561 | color: #6c757d; 5562 | font-style: italic; 5563 | "> 5564 | No tags created yet. Click "New Tag" to create your first tag. 5565 | </div> 5566 | `; 5567 | return; 5568 | } 5569 | 5570 | tagsManagementList.innerHTML = tags.map(tag => ` 5571 | <div class="tag-management-item" data-tag-id="${tag.id}" style=" 5572 | border: 1px solid #e9ecef; 5573 | border-radius: 8px; 5574 | padding: 16px; 5575 | margin-bottom: 12px; 5576 | background: white; 5577 | "> 5578 | <div style=" 5579 | display: flex; 5580 | justify-content: space-between; 5581 | align-items: center; 5582 | margin-bottom: 8px; 5583 | "> 5584 | <div style=" 5585 | display: flex; 5586 | align-items: center; 5587 | gap: 12px; 5588 | "> 5589 | <input type="color" value="${tag.color}" class="tag-color-input" style=" 5590 | width: 30px; 5591 | height: 30px; 5592 | border: none; 5593 | border-radius: 4px; 5594 | cursor: pointer; 5595 | "> 5596 | <input type="text" value="${tag.name}" class="tag-name-input" style=" 5597 | border: 1px solid #e9ecef; 5598 | border-radius: 4px; 5599 | padding: 6px 8px; 5600 | font-size: 14px; 5601 | flex: 1; 5602 | min-width: 200px; 5603 | "> 5604 | </div> 5605 | <div style=" 5606 | display: flex; 5607 | gap: 8px; 5608 | align-items: center; 5609 | "> 5610 | <span style=" 5611 | background: #f8f9fa; 5612 | padding: 4px 8px; 5613 | border-radius: 12px; 5614 | font-size: 12px; 5615 | color: #6c757d; 5616 | ">${tag.usageCount || 0} uses</span> 5617 | <button class="delete-tag-btn" data-tag-id="${tag.id}" style=" 5618 | background: #dc3545; 5619 | color: white; 5620 | border: none; 5621 | padding: 6px 12px; 5622 | border-radius: 4px; 5623 | font-size: 12px; 5624 | cursor: pointer; 5625 | ">Delete</button> 5626 | </div> 5627 | </div> 5628 | <div style=" 5629 | font-size: 12px; 5630 | color: #6c757d; 5631 | ">Created: ${new Date(tag.created).toLocaleDateString()}</div> 5632 | </div> 5633 | `).join(''); 5634 | 5635 | // Add event listeners for delete buttons 5636 | document.querySelectorAll('.delete-tag-btn').forEach(btn => { 5637 | btn.addEventListener('click', (e) => { 5638 | const tagId = btn.getAttribute('data-tag-id'); 5639 | deleteTagFromSettings(tagId); 5640 | }); 5641 | }); 5642 | } 5643 | 5644 | function populateProjectsManagement() { 5645 | const projectsManagementList = document.getElementById('projects-management-list'); 5646 | if (!projectsManagementList) return; 5647 | 5648 | const projects = loadProjects(); 5649 | 5650 | if (projects.length === 0) { 5651 | projectsManagementList.innerHTML = ` 5652 | <div style=" 5653 | text-align: center; 5654 | padding: 40px; 5655 | color: #6c757d; 5656 | font-style: italic; 5657 | "> 5658 | No projects created yet. Click "New Project" to create your first project. 5659 | </div> 5660 | `; 5661 | return; 5662 | } 5663 | 5664 | projectsManagementList.innerHTML = projects.map(project => ` 5665 | <div class="project-management-item" data-project-id="${project.id}" style=" 5666 | border: 1px solid #e9ecef; 5667 | border-radius: 8px; 5668 | padding: 16px; 5669 | margin-bottom: 12px; 5670 | background: white; 5671 | "> 5672 | <div style=" 5673 | display: flex; 5674 | justify-content: space-between; 5675 | align-items: flex-start; 5676 | margin-bottom: 8px; 5677 | "> 5678 | <div style="flex: 1; margin-right: 16px;"> 5679 | <input type="text" value="${project.name}" class="project-name-input" style=" 5680 | border: 1px solid #e9ecef; 5681 | border-radius: 4px; 5682 | padding: 6px 8px; 5683 | font-size: 14px; 5684 | width: 100%; 5685 | margin-bottom: 8px; 5686 | font-weight: 500; 5687 | "> 5688 | <textarea class="project-description-input" placeholder="Project description (optional)" style=" 5689 | border: 1px solid #e9ecef; 5690 | border-radius: 4px; 5691 | padding: 6px 8px; 5692 | font-size: 13px; 5693 | width: 100%; 5694 | min-height: 60px; 5695 | resize: vertical; 5696 | font-family: inherit; 5697 | ">${project.description || ''}</textarea> 5698 | </div> 5699 | <div style=" 5700 | display: flex; 5701 | flex-direction: column; 5702 | gap: 8px; 5703 | align-items: flex-end; 5704 | "> 5705 | <span style=" 5706 | background: #f8f9fa; 5707 | padding: 4px 8px; 5708 | border-radius: 12px; 5709 | font-size: 12px; 5710 | color: #6c757d; 5711 | ">${project.searchCount || 0} searches</span> 5712 | <button class="delete-project-btn" data-project-id="${project.id}" style=" 5713 | background: #dc3545; 5714 | color: white; 5715 | border: none; 5716 | padding: 6px 12px; 5717 | border-radius: 4px; 5718 | font-size: 12px; 5719 | cursor: pointer; 5720 | ">Delete</button> 5721 | </div> 5722 | </div> 5723 | <div style=" 5724 | font-size: 12px; 5725 | color: #6c757d; 5726 | ">Created: ${new Date(project.created).toLocaleDateString()}</div> 5727 | </div> 5728 | `).join(''); 5729 | 5730 | // Add event listeners for delete buttons 5731 | document.querySelectorAll('.delete-project-btn').forEach(btn => { 5732 | btn.addEventListener('click', (e) => { 5733 | const projectId = btn.getAttribute('data-project-id'); 5734 | deleteProjectFromSettings(projectId); 5735 | }); 5736 | }); 5737 | } 5738 | 5739 | function showCreateTagForm() { 5740 | const name = prompt('Enter tag name:'); 5741 | if (!name || !name.trim()) return; 5742 | 5743 | const color = prompt('Enter tag color (hex):', '#5b8def'); 5744 | if (!color) return; 5745 | 5746 | try { 5747 | createTag(name.trim(), color); 5748 | populateTagsManagement(); 5749 | alert(`Tag "${name}" created successfully!`); 5750 | } catch (error) { 5751 | alert(`Error: ${error.message}`); 5752 | } 5753 | } 5754 | 5755 | function showCreateProjectForm() { 5756 | const name = prompt('Enter project name:'); 5757 | if (!name || !name.trim()) return; 5758 | 5759 | const description = prompt('Enter project description (optional):') || ''; 5760 | 5761 | try { 5762 | createProject(name.trim(), description.trim()); 5763 | populateProjectsManagement(); 5764 | // Refresh project selector in export section if it exists 5765 | if (document.getElementById('export-project-selector')) { 5766 | populateProjectSelector(); 5767 | } 5768 | alert(`Project "${name}" created successfully!`); 5769 | } catch (error) { 5770 | alert(`Error: ${error.message}`); 5771 | } 5772 | } 5773 | 5774 | function deleteTagFromSettings(tagId) { 5775 | const tag = loadTags().find(t => t.id === tagId); 5776 | if (!tag) return; 5777 | 5778 | if (confirm(`Are you sure you want to delete the tag "${tag.name}"? This will remove it from all searches.`)) { 5779 | try { 5780 | deleteTag(tagId); 5781 | populateTagsManagement(); 5782 | alert('Tag deleted successfully!'); 5783 | } catch (error) { 5784 | alert(`Error: ${error.message}`); 5785 | } 5786 | } 5787 | } 5788 | 5789 | function deleteProjectFromSettings(projectId) { 5790 | const project = loadProjects().find(p => p.id === projectId); 5791 | if (!project) return; 5792 | 5793 | if (confirm(`Are you sure you want to delete the project "${project.name}"? This will remove it from all searches.`)) { 5794 | try { 5795 | deleteProject(projectId); 5796 | populateProjectsManagement(); 5797 | alert('Project deleted successfully!'); 5798 | } catch (error) { 5799 | alert(`Error: ${error.message}`); 5800 | } 5801 | } 5802 | } 5803 | 5804 | function saveSettingsAndClose() { 5805 | // Save all changes from the forms 5806 | try { 5807 | // Save tag changes 5808 | const tagItems = document.querySelectorAll('.tag-management-item'); 5809 | tagItems.forEach(item => { 5810 | const tagId = item.getAttribute('data-tag-id'); 5811 | const nameInput = item.querySelector('.tag-name-input'); 5812 | const colorInput = item.querySelector('.tag-color-input'); 5813 | 5814 | if (nameInput && colorInput) { 5815 | const tags = loadTags(); 5816 | const tag = tags.find(t => t.id === tagId); 5817 | if (tag) { 5818 | tag.name = nameInput.value.trim(); 5819 | tag.color = colorInput.value; 5820 | } 5821 | saveTags(tags); 5822 | } 5823 | }); 5824 | 5825 | // Save project changes 5826 | const projectItems = document.querySelectorAll('.project-management-item'); 5827 | projectItems.forEach(item => { 5828 | const projectId = item.getAttribute('data-project-id'); 5829 | const nameInput = item.querySelector('.project-name-input'); 5830 | const descInput = item.querySelector('.project-description-input'); 5831 | 5832 | if (nameInput && descInput) { 5833 | const projects = loadProjects(); 5834 | const project = projects.find(p => p.id === projectId); 5835 | if (project) { 5836 | project.name = nameInput.value.trim(); 5837 | project.description = descInput.value.trim(); 5838 | } 5839 | saveProjects(projects); 5840 | } 5841 | }); 5842 | 5843 | // Refresh sidebar 5844 | populateProjectsList(); 5845 | populateTagsList(); 5846 | 5847 | // Close modal 5848 | closeSettingsModal(); 5849 | 5850 | alert('Settings saved successfully!'); 5851 | } catch (error) { 5852 | alert(`Error saving settings: ${error.message}`); 5853 | } 5854 | } 5855 | 5856 | function closeSettingsModal() { 5857 | const modal = document.getElementById('settings-modal'); 5858 | if (modal) { 5859 | modal.remove(); 5860 | } 5861 | } 5862 | 5863 | // ===== POST-SEARCH TAGGING FUNCTIONALITY - Phase 4 ===== 5864 | 5865 | function showPostSearchTagging(query, results, searchType, historyItemId = null) { 5866 | // Remove any existing tagging or edit interfaces 5867 | const existingInterface = document.getElementById('post-search-tagging'); 5868 | const existingToggle = document.getElementById('post-search-toggle'); 5869 | const editInterface = document.getElementById('edit-organization-interface'); 5870 | const editToggle = document.getElementById('edit-organization-toggle'); 5871 | 5872 | if (existingInterface) { 5873 | existingInterface.remove(); 5874 | } 5875 | if (existingToggle) { 5876 | existingToggle.remove(); 5877 | } 5878 | if (editInterface) { 5879 | editInterface.remove(); 5880 | } 5881 | if (editToggle) { 5882 | editToggle.remove(); 5883 | } 5884 | 5885 | // Add toggle button for post-search tagging 5886 | addPostSearchTaggingToggle(query, results, searchType, historyItemId); 5887 | } 5888 | 5889 | function addPostSearchTaggingToggle(query, results, searchType, historyItemId) { 5890 | const toggleHTML = ` 5891 | <div id="post-search-toggle" style=" 5892 | background: #f8f9fa; 5893 | border: 1px solid #e9ecef; 5894 | border-radius: 8px; 5895 | padding: 12px 16px; 5896 | margin: 16px 0; 5897 | border-left: 4px solid #28a745; 5898 | cursor: pointer; 5899 | display: flex; 5900 | justify-content: space-between; 5901 | align-items: center; 5902 | "> 5903 | <div style=" 5904 | display: flex; 5905 | align-items: center; 5906 | gap: 8px; 5907 | "> 5908 | <img src="${tagIconUrl}" alt="Tags" style="width: 16px; height: 16px;" /> 5909 | <span style=" 5910 | font-weight: 600; 5911 | color: #155724; 5912 | font-size: 14px; 5913 | ">Organize Search</span> 5914 | <span style=" 5915 | font-size: 12px; 5916 | color: #6c757d; 5917 | ">Add tags and assign to project</span> 5918 | </div> 5919 | <span id="post-search-arrow" style=" 5920 | font-size: 14px; 5921 | color: #155724; 5922 | transition: transform 0.2s ease; 5923 | ">▼</span> 5924 | </div> 5925 | <div id="post-search-content" style="display: none;"> 5926 | </div> 5927 | `; 5928 | 5929 | // Insert the toggle at the top of results container 5930 | const resultsContainer = document.getElementById('results-container'); 5931 | if (resultsContainer) { 5932 | resultsContainer.insertAdjacentHTML('afterbegin', toggleHTML); 5933 | 5934 | // Add click handler for toggle 5935 | const toggle = document.getElementById('post-search-toggle'); 5936 | const content = document.getElementById('post-search-content'); 5937 | const arrow = document.getElementById('post-search-arrow'); 5938 | if (toggle) { 5939 | if (historyItemId) { 5940 | toggle.dataset.historyId = historyItemId; 5941 | } else { 5942 | delete toggle.dataset.historyId; 5943 | } 5944 | } 5945 | 5946 | toggle.addEventListener('click', () => { 5947 | const isHidden = content.style.display === 'none'; 5948 | content.style.display = isHidden ? 'block' : 'none'; 5949 | arrow.style.transform = isHidden ? 'rotate(180deg)' : 'rotate(0deg)'; 5950 | arrow.textContent = isHidden ? '▲' : '▼'; 5951 | 5952 | if (isHidden) { 5953 | // Show the tagging interface 5954 | const currentHistoryId = toggle?.dataset?.historyId || historyItemId; 5955 | showActualPostSearchTagging(query, results, searchType, currentHistoryId || null); 5956 | } else { 5957 | // Clear the content 5958 | content.innerHTML = ''; 5959 | } 5960 | }); 5961 | } 5962 | } 5963 | 5964 | function showActualPostSearchTagging(query, results, searchType, historyItemId) { 5965 | const tags = loadTags(); 5966 | const projects = loadProjects(); 5967 | 5968 | // Create tagging interface 5969 | const taggingHTML = ` 5970 | <div id="post-search-tagging" style=" 5971 | background: #f8f9fa; 5972 | border: 1px solid #e9ecef; 5973 | border-radius: 8px; 5974 | padding: 16px; 5975 | margin: 16px 0; 5976 | border-left: 4px solid #28a745; 5977 | "> 5978 | <div style=" 5979 | display: flex; 5980 | justify-content: space-between; 5981 | align-items: center; 5982 | margin-bottom: 12px; 5983 | "> 5984 | <h4 style=" 5985 | margin: 0; 5986 | color: #495057; 5987 | font-size: 14px; 5988 | font-weight: 600; 5989 | display: flex; 5990 | align-items: center; 5991 | gap: 8px; 5992 | "><img src="${tagIconUrl}" alt="Tags" style="width: 16px; height: 16px;" />Organize This Search</h4> 5993 | <div style=" 5994 | font-size: 12px; 5995 | color: #6c757d; 5996 | ">Optional - help organize your research</div> 5997 | </div> 5998 | 5999 | <div style=" 6000 | display: grid; 6001 | grid-template-columns: 1fr 1fr; 6002 | gap: 12px; 6003 | margin-bottom: 12px; 6004 | "> 6005 | <div> 6006 | <label style=" 6007 | display: block; 6008 | font-size: 12px; 6009 | font-weight: 600; 6010 | color: #6c757d; 6011 | margin-bottom: 4px; 6012 | ">Project</label> 6013 | <select id="tagging-project" style=" 6014 | width: 100%; 6015 | padding: 6px 8px; 6016 | border: 1px solid #dee2e6; 6017 | border-radius: 4px; 6018 | font-size: 13px; 6019 | background: white; 6020 | "> 6021 | <option value="">No project</option> 6022 | ${projects.map(project => ` 6023 | <option value="${project.id}">${project.name}</option> 6024 | `).join('')} 6025 | </select> 6026 | </div> 6027 | 6028 | <div> 6029 | <label style=" 6030 | display: block; 6031 | font-size: 12px; 6032 | font-weight: 600; 6033 | color: #6c757d; 6034 | margin-bottom: 4px; 6035 | ">Tags</label> 6036 | <div id="tagging-tags" style=" 6037 | min-height: 32px; 6038 | border: 1px solid #dee2e6; 6039 | border-radius: 4px; 6040 | padding: 4px; 6041 | background: white; 6042 | display: flex; 6043 | flex-wrap: wrap; 6044 | gap: 4px; 6045 | align-items: center; 6046 | "> 6047 | <button id="add-tag-to-search" style=" 6048 | background: none; 6049 | border: 1px dashed #5b8def; 6050 | color: #5b8def; 6051 | padding: 2px 6px; 6052 | border-radius: 12px; 6053 | font-size: 11px; 6054 | cursor: pointer; 6055 | ">+ Add Tag</button> 6056 | </div> 6057 | </div> 6058 | </div> 6059 | 6060 | <div style=" 6061 | display: flex; 6062 | justify-content: flex-end; 6063 | gap: 8px; 6064 | padding-top: 8px; 6065 | border-top: 1px solid #e9ecef; 6066 | "> 6067 | <button id="skip-tagging" style=" 6068 | background: #6c757d; 6069 | color: white; 6070 | border: none; 6071 | padding: 6px 12px; 6072 | border-radius: 4px; 6073 | font-size: 12px; 6074 | cursor: pointer; 6075 | ">Skip</button> 6076 | <button id="save-with-tags" style=" 6077 | background: #28a745; 6078 | color: white; 6079 | border: none; 6080 | padding: 6px 12px; 6081 | border-radius: 4px; 6082 | font-size: 12px; 6083 | cursor: pointer; 6084 | ">Save to History</button> 6085 | </div> 6086 | </div> 6087 | `; 6088 | 6089 | // Insert the interface in the content area 6090 | const content = document.getElementById('post-search-content'); 6091 | if (content) { 6092 | content.innerHTML = taggingHTML; 6093 | 6094 | // Initialize the interface 6095 | initializePostSearchTagging(query, results, searchType, historyItemId); 6096 | 6097 | // No auto-hide since it's now manually controlled 6098 | } 6099 | } 6100 | 6101 | function initializePostSearchTagging(query, results, searchType, historyItemId) { 6102 | const selectedTags = new Set(); 6103 | let currentHistoryId = historyItemId || null; 6104 | const toggleElement = document.getElementById('post-search-toggle'); 6105 | 6106 | const setToggleHistoryId = (id) => { 6107 | if (!toggleElement) { 6108 | return; 6109 | } 6110 | if (id) { 6111 | toggleElement.dataset.historyId = id; 6112 | } else { 6113 | delete toggleElement.dataset.historyId; 6114 | } 6115 | }; 6116 | 6117 | setToggleHistoryId(currentHistoryId); 6118 | 6119 | // Add tag button functionality 6120 | const addTagBtn = document.getElementById('add-tag-to-search'); 6121 | if (addTagBtn) { 6122 | addTagBtn.addEventListener('click', () => { 6123 | showTagSelector(selectedTags); 6124 | }); 6125 | } 6126 | 6127 | // Skip button 6128 | const skipBtn = document.getElementById('skip-tagging'); 6129 | if (skipBtn) { 6130 | skipBtn.addEventListener('click', () => { 6131 | if (!currentHistoryId) { 6132 | currentHistoryId = saveSearchWithSelectedTags(null, query, results, searchType, selectedTags) || currentHistoryId; 6133 | setToggleHistoryId(currentHistoryId); 6134 | } 6135 | const taggingInterface = document.getElementById('post-search-tagging'); 6136 | if (taggingInterface) { 6137 | taggingInterface.remove(); 6138 | } 6139 | }); 6140 | } 6141 | 6142 | // Save button 6143 | const saveBtn = document.getElementById('save-with-tags'); 6144 | if (saveBtn) { 6145 | saveBtn.addEventListener('click', () => { 6146 | currentHistoryId = saveSearchWithSelectedTags(currentHistoryId, query, results, searchType, selectedTags) || currentHistoryId; 6147 | setToggleHistoryId(currentHistoryId); 6148 | const taggingInterface = document.getElementById('post-search-tagging'); 6149 | if (taggingInterface) { 6150 | taggingInterface.remove(); 6151 | } 6152 | }); 6153 | } 6154 | } 6155 | 6156 | function showTagSelector(selectedTags) { 6157 | const tags = loadTags(); 6158 | const tagsContainer = document.getElementById('tagging-tags'); 6159 | if (!tagsContainer) return; 6160 | 6161 | // Create dropdown for tag selection 6162 | const dropdown = document.createElement('select'); 6163 | dropdown.style.cssText = ` 6164 | width: 100%; 6165 | padding: 6px 8px; 6166 | border: 1px solid #dee2e6; 6167 | border-radius: 4px; 6168 | background: white; 6169 | font-size: 12px; 6170 | margin: 2px 0; 6171 | `; 6172 | 6173 | dropdown.innerHTML = ` 6174 | <option value="">Select a tag...</option> 6175 | ${tags.filter(tag => !selectedTags.has(tag.id)).map(tag => ` 6176 | <option value="${tag.id}">${tag.name}</option> 6177 | `).join('')} 6178 | `; 6179 | 6180 | dropdown.addEventListener('change', (e) => { 6181 | if (e.target.value) { 6182 | selectedTags.add(e.target.value); 6183 | updateTagsDisplay(selectedTags); 6184 | dropdown.remove(); 6185 | } 6186 | }); 6187 | 6188 | dropdown.addEventListener('blur', () => { 6189 | setTimeout(() => dropdown.remove(), 100); 6190 | }); 6191 | 6192 | // Remove any existing dropdowns first 6193 | const existingDropdown = tagsContainer.querySelector('select'); 6194 | if (existingDropdown) { 6195 | existingDropdown.remove(); 6196 | } 6197 | 6198 | // Insert dropdown in a clean way 6199 | tagsContainer.insertBefore(dropdown, tagsContainer.firstChild); 6200 | dropdown.focus(); 6201 | } 6202 | 6203 | function updateTagsDisplay(selectedTags) { 6204 | const tagsContainer = document.getElementById('tagging-tags'); 6205 | if (!tagsContainer) return; 6206 | 6207 | const tags = loadTags(); 6208 | 6209 | // Clear existing tags display and any dropdowns 6210 | tagsContainer.innerHTML = ''; 6211 | 6212 | // Add selected tags 6213 | selectedTags.forEach(tagId => { 6214 | const tag = tags.find(t => t.id === tagId); 6215 | if (tag) { 6216 | const tagElement = document.createElement('span'); 6217 | tagElement.style.cssText = ` 6218 | background: ${tag.color}20; 6219 | color: ${tag.color}; 6220 | border: 1px solid ${tag.color}40; 6221 | padding: 2px 6px; 6222 | border-radius: 12px; 6223 | font-size: 11px; 6224 | display: flex; 6225 | align-items: center; 6226 | gap: 4px; 6227 | `; 6228 | tagElement.innerHTML = ` 6229 | ${tag.name} 6230 | <button class="remove-tag-btn" data-tag-id="${tagId}" style=" 6231 | background: none; 6232 | border: none; 6233 | color: ${tag.color}; 6234 | cursor: pointer; 6235 | padding: 0; 6236 | width: 14px; 6237 | height: 14px; 6238 | display: flex; 6239 | align-items: center; 6240 | justify-content: center; 6241 | font-size: 10px; 6242 | ">×</button> 6243 | `; 6244 | 6245 | // Add event listener for remove button 6246 | const removeBtn = tagElement.querySelector('.remove-tag-btn'); 6247 | removeBtn.addEventListener('click', () => { 6248 | selectedTags.delete(tagId); 6249 | updateTagsDisplay(selectedTags); 6250 | }); 6251 | tagsContainer.appendChild(tagElement); 6252 | } 6253 | }); 6254 | 6255 | // Re-add the add button 6256 | const addButton = document.createElement('button'); 6257 | addButton.id = 'add-tag-to-search'; 6258 | addButton.style.cssText = ` 6259 | background: none; 6260 | border: 1px dashed #5b8def; 6261 | color: #5b8def; 6262 | padding: 2px 6px; 6263 | border-radius: 12px; 6264 | font-size: 11px; 6265 | cursor: pointer; 6266 | `; 6267 | addButton.textContent = '+ Add Tag'; 6268 | addButton.addEventListener('click', () => showTagSelector(selectedTags)); 6269 | tagsContainer.appendChild(addButton); 6270 | } 6271 | 6272 | function saveSearchWithSelectedTags(historyId, query, results, searchType, selectedTags = new Set()) { 6273 | const projectSelect = document.getElementById('tagging-project'); 6274 | const selectedProject = projectSelect ? projectSelect.value || null : null; 6275 | const normalizedTags = selectedTags instanceof Set ? Array.from(selectedTags) : Array.from(new Set(selectedTags || [])); 6276 | let effectiveHistoryId = historyId || null; 6277 | 6278 | if (effectiveHistoryId) { 6279 | const history = loadSearchHistory(); 6280 | const existingItem = history.find(item => item.id === effectiveHistoryId); 6281 | const previousTags = existingItem ? existingItem.tags || [] : []; 6282 | const previousProject = existingItem ? existingItem.projectId || null : null; 6283 | 6284 | const newTags = normalizedTags.filter(tag => !previousTags.includes(tag)); 6285 | if (newTags.length > 0) { 6286 | newTags.forEach(tagId => { 6287 | if (tagId) updateTagUsage(tagId); 6288 | }); 6289 | } 6290 | 6291 | if (selectedProject && selectedProject !== previousProject) { 6292 | updateProjectSearchCount(selectedProject); 6293 | } 6294 | 6295 | const updated = updateHistoryEntry(effectiveHistoryId, { 6296 | results, 6297 | query, 6298 | searchType, 6299 | tags: normalizedTags, 6300 | projectId: selectedProject 6301 | }); 6302 | 6303 | if (!updated) { 6304 | effectiveHistoryId = saveSearchToHistory(query, results, searchType, normalizedTags, selectedProject); 6305 | } 6306 | } else { 6307 | effectiveHistoryId = saveSearchToHistory(query, results, searchType, normalizedTags, selectedProject); 6308 | } 6309 | 6310 | // Update sidebar to reflect new usage 6311 | populateProjectsList(); 6312 | populateTagsList(); 6313 | 6314 | return effectiveHistoryId; 6315 | } 6316 | 6317 | // ===== EDIT EXISTING SEARCH ORGANIZATION - Phase 4.6 ===== 6318 | 6319 | function showEditOrganizationInterface(historyItem, mountTarget) { 6320 | // Remove any existing edit interface or post-search tagging 6321 | const existingInterface = document.getElementById('edit-organization-interface'); 6322 | const existingToggle = document.getElementById('edit-organization-toggle'); 6323 | const postSearchInterface = document.getElementById('post-search-tagging'); 6324 | const postSearchToggle = document.getElementById('post-search-toggle'); 6325 | 6326 | if (existingInterface) { 6327 | existingInterface.remove(); 6328 | } 6329 | if (existingToggle) { 6330 | existingToggle.remove(); 6331 | } 6332 | if (postSearchInterface) { 6333 | postSearchInterface.remove(); 6334 | } 6335 | if (postSearchToggle) { 6336 | postSearchToggle.remove(); 6337 | } 6338 | 6339 | // Add toggle button for edit organization 6340 | addEditOrganizationToggle(historyItem, mountTarget); 6341 | } 6342 | 6343 | function addEditOrganizationToggle(historyItem, mountTarget) { 6344 | const toggleHTML = ` 6345 | <div id="edit-organization-toggle" style=" 6346 | background: #fff3cd; 6347 | border: 1px solid #ffeaa7; 6348 | border-radius: 8px; 6349 | padding: 12px 16px; 6350 | margin: 16px 0; 6351 | border-left: 4px solid #f39c12; 6352 | cursor: pointer; 6353 | display: flex; 6354 | justify-content: space-between; 6355 | align-items: center; 6356 | "> 6357 | <div style=" 6358 | display: flex; 6359 | align-items: center; 6360 | gap: 8px; 6361 | "> 6362 | <img src="${tagIconUrl}" alt="Tags" style="width: 16px; height: 16px;" /> 6363 | <span style=" 6364 | font-weight: 600; 6365 | color: #856404; 6366 | font-size: 14px; 6367 | ">Edit Organization</span> 6368 | <span style=" 6369 | font-size: 12px; 6370 | color: #6c757d; 6371 | ">Modify tags and project assignment</span> 6372 | </div> 6373 | <span id="edit-organization-arrow" style=" 6374 | font-size: 14px; 6375 | color: #856404; 6376 | transition: transform 0.2s ease; 6377 | ">▼</span> 6378 | </div> 6379 | <div id="edit-organization-content" style="display: none;"> 6380 | </div> 6381 | `; 6382 | 6383 | // Insert the toggle at the top of results container 6384 | let targetContainer = null; 6385 | if (mountTarget instanceof HTMLElement) { 6386 | targetContainer = mountTarget; 6387 | } else if (typeof mountTarget === 'string') { 6388 | targetContainer = document.getElementById(mountTarget); 6389 | } 6390 | if (!targetContainer) { 6391 | targetContainer = document.getElementById('results-container'); 6392 | } 6393 | 6394 | if (targetContainer) { 6395 | targetContainer.insertAdjacentHTML('afterbegin', toggleHTML); 6396 | 6397 | // Add click handler for toggle 6398 | const toggle = targetContainer.querySelector('#edit-organization-toggle'); 6399 | const content = targetContainer.querySelector('#edit-organization-content'); 6400 | const arrow = targetContainer.querySelector('#edit-organization-arrow'); 6401 | 6402 | if (!toggle || !content || !arrow) { 6403 | return; 6404 | } 6405 | 6406 | toggle.addEventListener('click', () => { 6407 | const isHidden = content.style.display === 'none'; 6408 | content.style.display = isHidden ? 'block' : 'none'; 6409 | arrow.style.transform = isHidden ? 'rotate(180deg)' : 'rotate(0deg)'; 6410 | arrow.textContent = isHidden ? '▲' : '▼'; 6411 | 6412 | if (isHidden) { 6413 | // Show the edit interface 6414 | showActualEditOrganizationInterface(historyItem); 6415 | } else { 6416 | // Clear the content 6417 | content.innerHTML = ''; 6418 | } 6419 | }); 6420 | } 6421 | } 6422 | 6423 | function showActualEditOrganizationInterface(historyItem) { 6424 | const tags = loadTags(); 6425 | const projects = loadProjects(); 6426 | const currentTags = historyItem.tags || []; 6427 | const currentProject = historyItem.projectId || ''; 6428 | 6429 | // Create edit interface 6430 | const editHTML = ` 6431 | <div id="edit-organization-interface" style=" 6432 | background: #fff3cd; 6433 | border: 1px solid #ffeaa7; 6434 | border-radius: 8px; 6435 | padding: 16px; 6436 | margin: 16px 0; 6437 | border-left: 4px solid #f39c12; 6438 | "> 6439 | <div style=" 6440 | display: flex; 6441 | justify-content: space-between; 6442 | align-items: center; 6443 | margin-bottom: 12px; 6444 | "> 6445 | <h4 style=" 6446 | margin: 0; 6447 | color: #856404; 6448 | font-size: 14px; 6449 | font-weight: 600; 6450 | display: flex; 6451 | align-items: center; 6452 | gap: 8px; 6453 | "><img src="${editIconUrl}" alt="Edit" style="width: 16px; height: 16px;" />Edit Organization</h4> 6454 | <div style=" 6455 | font-size: 12px; 6456 | color: #856404; 6457 | ">Reopened from history • ${historyItem.date}</div> 6458 | </div> 6459 | 6460 | <div style=" 6461 | display: grid; 6462 | grid-template-columns: 1fr 1fr; 6463 | gap: 12px; 6464 | margin-bottom: 12px; 6465 | "> 6466 | <div> 6467 | <label style=" 6468 | display: block; 6469 | font-size: 12px; 6470 | font-weight: 600; 6471 | color: #856404; 6472 | margin-bottom: 4px; 6473 | ">Project</label> 6474 | <select id="edit-project" style=" 6475 | width: 100%; 6476 | padding: 6px 8px; 6477 | border: 1px solid #f1c40f; 6478 | border-radius: 4px; 6479 | font-size: 13px; 6480 | background: white; 6481 | "> 6482 | <option value="">No project</option> 6483 | ${projects.map(project => ` 6484 | <option value="${project.id}" ${project.id === currentProject ? 'selected' : ''}>${project.name}</option> 6485 | `).join('')} 6486 | </select> 6487 | </div> 6488 | 6489 | <div> 6490 | <label style=" 6491 | display: block; 6492 | font-size: 12px; 6493 | font-weight: 600; 6494 | color: #856404; 6495 | margin-bottom: 4px; 6496 | ">Tags</label> 6497 | <div id="edit-tags" style=" 6498 | min-height: 32px; 6499 | border: 1px solid #f1c40f; 6500 | border-radius: 4px; 6501 | padding: 4px; 6502 | background: white; 6503 | display: flex; 6504 | flex-wrap: wrap; 6505 | gap: 4px; 6506 | align-items: center; 6507 | "> 6508 | <button id="add-tag-to-edit" style=" 6509 | background: none; 6510 | border: 1px dashed #f39c12; 6511 | color: #f39c12; 6512 | padding: 2px 6px; 6513 | border-radius: 12px; 6514 | font-size: 11px; 6515 | cursor: pointer; 6516 | ">+ Add Tag</button> 6517 | </div> 6518 | </div> 6519 | </div> 6520 | 6521 | <div style=" 6522 | display: flex; 6523 | justify-content: space-between; 6524 | align-items: center; 6525 | padding-top: 8px; 6526 | border-top: 1px solid #f1c40f; 6527 | "> 6528 | <div style=" 6529 | font-size: 12px; 6530 | color: #856404; 6531 | ">Changes will update this search in your history</div> 6532 | <div style="display: flex; gap: 8px;"> 6533 | <button id="cancel-edit" style=" 6534 | background: #6c757d; 6535 | color: white; 6536 | border: none; 6537 | padding: 6px 12px; 6538 | border-radius: 4px; 6539 | font-size: 12px; 6540 | cursor: pointer; 6541 | ">Cancel</button> 6542 | <button id="save-edit" style=" 6543 | background: #f39c12; 6544 | color: white; 6545 | border: none; 6546 | padding: 6px 12px; 6547 | border-radius: 4px; 6548 | font-size: 12px; 6549 | cursor: pointer; 6550 | ">Save Changes</button> 6551 | </div> 6552 | </div> 6553 | </div> 6554 | `; 6555 | 6556 | // Insert the interface in the content area 6557 | const content = document.getElementById('edit-organization-content'); 6558 | if (content) { 6559 | content.innerHTML = editHTML; 6560 | 6561 | // Initialize the interface with current tags/project 6562 | initializeEditOrganizationInterface(historyItem, currentTags); 6563 | } 6564 | } 6565 | 6566 | function initializeEditOrganizationInterface(historyItem, selectedTags) { 6567 | const selectedTagsSet = new Set(selectedTags); 6568 | 6569 | // Display current tags 6570 | updateEditTagsDisplay(selectedTagsSet); 6571 | 6572 | // Add tag button functionality 6573 | const addTagBtn = document.getElementById('add-tag-to-edit'); 6574 | if (addTagBtn) { 6575 | addTagBtn.addEventListener('click', () => { 6576 | showEditTagSelector(selectedTagsSet); 6577 | }); 6578 | } 6579 | 6580 | // Cancel button 6581 | const cancelBtn = document.getElementById('cancel-edit'); 6582 | if (cancelBtn) { 6583 | cancelBtn.addEventListener('click', () => { 6584 | const editInterface = document.getElementById('edit-organization-interface'); 6585 | if (editInterface) { 6586 | editInterface.remove(); 6587 | } 6588 | }); 6589 | } 6590 | 6591 | // Save button 6592 | const saveBtn = document.getElementById('save-edit'); 6593 | if (saveBtn) { 6594 | saveBtn.addEventListener('click', () => { 6595 | saveEditedOrganization(historyItem, selectedTagsSet); 6596 | }); 6597 | } 6598 | } 6599 | 6600 | function showEditTagSelector(selectedTags) { 6601 | const tags = loadTags(); 6602 | const tagsContainer = document.getElementById('edit-tags'); 6603 | if (!tagsContainer) return; 6604 | 6605 | // Create dropdown for tag selection 6606 | const dropdown = document.createElement('select'); 6607 | dropdown.style.cssText = ` 6608 | width: 100%; 6609 | padding: 6px 8px; 6610 | border: 1px solid #f1c40f; 6611 | border-radius: 4px; 6612 | background: white; 6613 | font-size: 12px; 6614 | margin: 2px 0; 6615 | `; 6616 | 6617 | dropdown.innerHTML = ` 6618 | <option value="">Select a tag...</option> 6619 | ${tags.filter(tag => !selectedTags.has(tag.id)).map(tag => ` 6620 | <option value="${tag.id}">${tag.name}</option> 6621 | `).join('')} 6622 | `; 6623 | 6624 | dropdown.addEventListener('change', (e) => { 6625 | if (e.target.value) { 6626 | selectedTags.add(e.target.value); 6627 | updateEditTagsDisplay(selectedTags); 6628 | dropdown.remove(); 6629 | } 6630 | }); 6631 | 6632 | dropdown.addEventListener('blur', () => { 6633 | setTimeout(() => dropdown.remove(), 100); 6634 | }); 6635 | 6636 | // Remove any existing dropdowns first 6637 | const existingDropdown = tagsContainer.querySelector('select'); 6638 | if (existingDropdown) { 6639 | existingDropdown.remove(); 6640 | } 6641 | 6642 | // Insert dropdown in a clean way 6643 | tagsContainer.insertBefore(dropdown, tagsContainer.firstChild); 6644 | dropdown.focus(); 6645 | } 6646 | 6647 | function updateEditTagsDisplay(selectedTags) { 6648 | const tagsContainer = document.getElementById('edit-tags'); 6649 | if (!tagsContainer) return; 6650 | 6651 | const tags = loadTags(); 6652 | 6653 | // Clear existing tags display and any dropdowns 6654 | tagsContainer.innerHTML = ''; 6655 | 6656 | // Add selected tags 6657 | selectedTags.forEach(tagId => { 6658 | const tag = tags.find(t => t.id === tagId); 6659 | if (tag) { 6660 | const tagElement = document.createElement('span'); 6661 | tagElement.style.cssText = ` 6662 | background: ${tag.color}20; 6663 | color: ${tag.color}; 6664 | border: 1px solid ${tag.color}40; 6665 | padding: 2px 6px; 6666 | border-radius: 12px; 6667 | font-size: 11px; 6668 | display: flex; 6669 | align-items: center; 6670 | gap: 4px; 6671 | `; 6672 | tagElement.innerHTML = ` 6673 | <img src="${tagIconUrl}" alt="Tag" style="width: 14px; height: 14px;" /> 6674 | <span>${tag.name}</span> 6675 | <button class="remove-edit-tag-btn" data-tag-id="${tagId}" style=" 6676 | background: none; 6677 | border: none; 6678 | color: ${tag.color}; 6679 | cursor: pointer; 6680 | padding: 0; 6681 | width: 14px; 6682 | height: 14px; 6683 | display: flex; 6684 | align-items: center; 6685 | justify-content: center; 6686 | font-size: 10px; 6687 | ">×</button> 6688 | `; 6689 | 6690 | // Add event listener for remove button 6691 | const removeBtn = tagElement.querySelector('.remove-edit-tag-btn'); 6692 | removeBtn.addEventListener('click', () => { 6693 | selectedTags.delete(tagId); 6694 | updateEditTagsDisplay(selectedTags); 6695 | }); 6696 | tagsContainer.appendChild(tagElement); 6697 | } 6698 | }); 6699 | 6700 | // Re-add the add button 6701 | const addButton = document.createElement('button'); 6702 | addButton.id = 'add-tag-to-edit'; 6703 | addButton.style.cssText = ` 6704 | background: none; 6705 | border: 1px dashed #f39c12; 6706 | color: #f39c12; 6707 | padding: 2px 6px; 6708 | border-radius: 12px; 6709 | font-size: 11px; 6710 | cursor: pointer; 6711 | `; 6712 | addButton.textContent = '+ Add Tag'; 6713 | addButton.addEventListener('click', () => showEditTagSelector(selectedTags)); 6714 | tagsContainer.appendChild(addButton); 6715 | } 6716 | 6717 | function saveEditedOrganization(historyItem, selectedTags) { 6718 | const projectSelect = document.getElementById('edit-project'); 6719 | const newProject = projectSelect ? projectSelect.value || null : null; 6720 | const newTags = Array.from(selectedTags); 6721 | 6722 | // Update the history item 6723 | const history = loadSearchHistory(); 6724 | const itemIndex = history.findIndex(h => h.id === historyItem.id); 6725 | 6726 | if (itemIndex !== -1) { 6727 | // Remove old tag usage counts 6728 | if (historyItem.tags) { 6729 | historyItem.tags.forEach(tagId => { 6730 | const tags = loadTags(); 6731 | const tag = tags.find(t => t.id === tagId); 6732 | if (tag && tag.usageCount > 0) { 6733 | tag.usageCount = Math.max(0, tag.usageCount - 1); 6734 | } 6735 | saveTags(tags); 6736 | }); 6737 | } 6738 | 6739 | // Remove old project count 6740 | if (historyItem.projectId) { 6741 | const projects = loadProjects(); 6742 | const project = projects.find(p => p.id === historyItem.projectId); 6743 | if (project && project.searchCount > 0) { 6744 | project.searchCount = Math.max(0, project.searchCount - 1); 6745 | } 6746 | saveProjects(projects); 6747 | } 6748 | 6749 | // Update history item 6750 | history[itemIndex] = { 6751 | ...historyItem, 6752 | tags: newTags, 6753 | projectId: newProject 6754 | }; 6755 | 6756 | // Update new tag usage counts 6757 | newTags.forEach(tagId => { 6758 | updateTagUsage(tagId); 6759 | }); 6760 | 6761 | // Update new project count 6762 | if (newProject) { 6763 | updateProjectSearchCount(newProject); 6764 | } 6765 | 6766 | // Save updated history 6767 | localStorage.setItem('chatgpt-product-search-history', JSON.stringify(history)); 6768 | 6769 | // Update sidebar to reflect changes 6770 | populateProjectsList(); 6771 | populateTagsList(); 6772 | 6773 | // Remove edit interface 6774 | const editInterface = document.getElementById('edit-organization-interface'); 6775 | if (editInterface) { 6776 | editInterface.remove(); 6777 | } 6778 | 6779 | 6780 | // Show confirmation 6781 | const resultsContainer = document.getElementById('results-container'); 6782 | if (resultsContainer) { 6783 | const confirmation = document.createElement('div'); 6784 | confirmation.style.cssText = ` 6785 | background: #d4edda; 6786 | border: 1px solid #c3e6cb; 6787 | border-radius: 4px; 6788 | padding: 8px 12px; 6789 | margin: 16px 0; 6790 | color: #155724; 6791 | font-size: 13px; 6792 | text-align: center; 6793 | `; 6794 | confirmation.innerHTML = formatStatusMessage('success', 'Organization updated successfully!'); 6795 | confirmation.style.display = 'inline-flex'; 6796 | confirmation.style.alignItems = 'center'; 6797 | confirmation.style.justifyContent = 'center'; 6798 | confirmation.style.gap = '8px'; 6799 | resultsContainer.insertAdjacentElement('afterbegin', confirmation); 6800 | 6801 | // Remove confirmation after 3 seconds 6802 | setTimeout(() => { 6803 | confirmation.remove(); 6804 | }, 3000); 6805 | } 6806 | } 6807 | } 6808 | 6809 | // ===== END EDIT EXISTING SEARCH ORGANIZATION ===== 6810 | 6811 | // ===== END POST-SEARCH TAGGING FUNCTIONALITY ===== 6812 | 6813 | // ===== HISTORY ENHANCEMENT FUNCTIONS - Phase 4 ===== 6814 | 6815 | function generateHistoryTagsAndProject(item) { 6816 | const tags = loadTags(); 6817 | const projects = loadProjects(); 6818 | const itemTags = Array.isArray(item.tags) ? item.tags : []; 6819 | const itemProject = item.projectId; 6820 | 6821 | const chips = []; 6822 | 6823 | if (itemProject) { 6824 | const project = projects.find(p => p.id === itemProject); 6825 | if (project) { 6826 | chips.push(` 6827 | <span class="history-chip history-chip--project" ${project.description ? `title="${escapeAttributeValue(project.description)}"` : ''}> 6828 | <img src="${projectIconUrl}" alt="" aria-hidden="true" /> 6829 | <span>${escapeHTML(project.name)}</span> 6830 | </span> 6831 | `); 6832 | } 6833 | } 6834 | 6835 | if (itemTags.length > 0) { 6836 | itemTags.forEach(tagId => { 6837 | const tag = tags.find(t => t.id === tagId); 6838 | if (tag) { 6839 | const safeName = escapeHTML(tag.name); 6840 | const dotColor = tag.color || '#5b8def'; 6841 | chips.push(` 6842 | <span class="history-chip history-chip--tag" style="--chip-tag-color: ${dotColor};"> 6843 | <span class="history-chip-dot"></span> 6844 | <span>${safeName}</span> 6845 | </span> 6846 | `); 6847 | } 6848 | }); 6849 | } 6850 | 6851 | if (chips.length === 0) { 6852 | return ''; 6853 | } 6854 | 6855 | return `<div class="history-row-labels">${chips.join('')}</div>`; 6856 | } 6857 | 6858 | // ===== END HISTORY ENHANCEMENT FUNCTIONS ===== 6859 | 6860 | // ===== ADVANCED FILTERING SYSTEM - Phase 5 ===== 6861 | 6862 | let historyDayFormatter; 6863 | let historyTimeFormatter; 6864 | 6865 | try { 6866 | historyDayFormatter = new Intl.DateTimeFormat(undefined, { 6867 | day: 'numeric', 6868 | month: 'long', 6869 | year: 'numeric' 6870 | }); 6871 | } catch (error) { 6872 | historyDayFormatter = { 6873 | format: date => date.toLocaleDateString() 6874 | }; 6875 | } 6876 | 6877 | try { 6878 | historyTimeFormatter = new Intl.DateTimeFormat(undefined, { 6879 | hour: '2-digit', 6880 | minute: '2-digit' 6881 | }); 6882 | } catch (error) { 6883 | historyTimeFormatter = { 6884 | format: date => date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) 6885 | }; 6886 | } 6887 | 6888 | // Global filter state 6889 | let currentFilters = { 6890 | text: '', 6891 | rawText: '', 6892 | project: '', 6893 | tags: [], 6894 | market: 'all', 6895 | isActive: false 6896 | }; 6897 | 6898 | function initializeAdvancedFiltering() { 6899 | // Populate filter dropdowns and checkboxes 6900 | populateFilterOptions(); 6901 | 6902 | // Add event listeners 6903 | const toggleBtn = document.getElementById('toggle-filters'); 6904 | const applyBtn = document.getElementById('apply-filters'); 6905 | const clearBtn = document.getElementById('clear-filters'); 6906 | const filterText = document.getElementById('filter-text'); 6907 | const filterProject = document.getElementById('filter-project'); 6908 | const filterMarket = document.getElementById('filter-market'); 6909 | 6910 | if (toggleBtn) { 6911 | toggleBtn.addEventListener('click', toggleFilterPanel); 6912 | } 6913 | 6914 | if (applyBtn) { 6915 | applyBtn.addEventListener('click', applyFilters); 6916 | } 6917 | 6918 | if (clearBtn) { 6919 | clearBtn.addEventListener('click', clearAllFilters); 6920 | } 6921 | 6922 | if (filterText) { 6923 | filterText.addEventListener('input', updateFilterSummary); 6924 | } 6925 | 6926 | if (filterProject) { 6927 | filterProject.addEventListener('change', updateFilterSummary); 6928 | } 6929 | 6930 | if (filterMarket) { 6931 | filterMarket.addEventListener('change', updateFilterSummary); 6932 | } 6933 | } 6934 | 6935 | function populateFilterOptions() { 6936 | const projects = loadProjects(); 6937 | const tags = loadTags(); 6938 | const marketSelect = document.getElementById('filter-market'); 6939 | 6940 | // Populate projects dropdown 6941 | const projectSelect = document.getElementById('filter-project'); 6942 | if (projectSelect) { 6943 | projectSelect.innerHTML = '<option value="">All Projects</option>'; 6944 | projects.forEach(project => { 6945 | const option = document.createElement('option'); 6946 | option.value = project.id; 6947 | option.textContent = project.name; 6948 | projectSelect.appendChild(option); 6949 | }); 6950 | } 6951 | 6952 | if (marketSelect) { 6953 | const previousValue = currentFilters.market || 'all'; 6954 | marketSelect.innerHTML = '<option value="all">All Markets</option>'; 6955 | MARKET_OPTIONS.forEach(option => { 6956 | const optionEl = document.createElement('option'); 6957 | optionEl.value = option.value; 6958 | optionEl.textContent = option.label; 6959 | marketSelect.appendChild(optionEl); 6960 | }); 6961 | if (previousValue && marketSelect.querySelector(`option[value="${previousValue}"]`)) { 6962 | marketSelect.value = previousValue; 6963 | } else { 6964 | marketSelect.value = 'all'; 6965 | currentFilters.market = 'all'; 6966 | } 6967 | } 6968 | 6969 | // Populate tags checkboxes 6970 | const tagsContainer = document.getElementById('filter-tags'); 6971 | if (tagsContainer) { 6972 | tagsContainer.innerHTML = ''; 6973 | 6974 | if (tags.length === 0) { 6975 | tagsContainer.innerHTML = ` 6976 | <div style=" 6977 | color: #6c757d; 6978 | font-size: 12px; 6979 | font-style: italic; 6980 | padding: 8px; 6981 | ">No tags available</div> 6982 | `; 6983 | } else { 6984 | tags.forEach(tag => { 6985 | const tagCheckbox = document.createElement('label'); 6986 | tagCheckbox.style.cssText = ` 6987 | display: flex; 6988 | align-items: center; 6989 | gap: 6px; 6990 | padding: 4px 8px; 6991 | border-radius: 12px; 6992 | background: ${tag.color}15; 6993 | border: 1px solid ${tag.color}30; 6994 | cursor: pointer; 6995 | font-size: 12px; 6996 | color: ${tag.color}; 6997 | white-space: nowrap; 6998 | `; 6999 | 7000 | tagCheckbox.innerHTML = ` 7001 | <input type="checkbox" value="${tag.id}" style=" 7002 | margin: 0; 7003 | width: 12px; 7004 | height: 12px; 7005 | " /> 7006 | <img src="${tagIconUrl}" alt="Tag" style="width: 14px; height: 14px;" /> 7007 | <span>${tag.name}</span> 7008 | `; 7009 | 7010 | const checkbox = tagCheckbox.querySelector('input'); 7011 | checkbox.addEventListener('change', updateFilterSummary); 7012 | 7013 | tagsContainer.appendChild(tagCheckbox); 7014 | }); 7015 | } 7016 | } 7017 | 7018 | updateFilterSummary(); 7019 | } 7020 | 7021 | function toggleFilterPanel() { 7022 | const panel = document.getElementById('filter-panel'); 7023 | const toggleText = document.getElementById('filter-toggle-text'); 7024 | 7025 | if (panel && toggleText) { 7026 | const isVisible = panel.style.display !== 'none'; 7027 | panel.style.display = isVisible ? 'none' : 'block'; 7028 | toggleText.textContent = isVisible ? 'Filters' : 'Hide Filters'; 7029 | } 7030 | } 7031 | 7032 | function updateFilterSummary() { 7033 | const filterText = document.getElementById('filter-text'); 7034 | const filterProject = document.getElementById('filter-project'); 7035 | const filterMarket = document.getElementById('filter-market'); 7036 | const tagCheckboxes = document.querySelectorAll('#filter-tags input[type="checkbox"]:checked'); 7037 | const summary = document.getElementById('filter-summary'); 7038 | 7039 | if (!summary) return; 7040 | 7041 | let activeCount = 0; 7042 | let summaryParts = []; 7043 | 7044 | if (filterText && filterText.value.trim()) { 7045 | activeCount++; 7046 | summaryParts.push('text search'); 7047 | } 7048 | 7049 | if (filterProject && filterProject.value) { 7050 | activeCount++; 7051 | const selectedProject = loadProjects().find(p => p.id === filterProject.value); 7052 | summaryParts.push(`project: ${selectedProject?.name || 'Unknown'}`); 7053 | } 7054 | 7055 | if (filterMarket && filterMarket.value && filterMarket.value !== 'all') { 7056 | activeCount++; 7057 | const marketOption = getMarketOption(filterMarket.value); 7058 | summaryParts.push(`market: ${marketOption.label}`); 7059 | } 7060 | 7061 | if (tagCheckboxes.length > 0) { 7062 | activeCount++; 7063 | summaryParts.push(`${tagCheckboxes.length} tag${tagCheckboxes.length > 1 ? 's' : ''}`); 7064 | } 7065 | 7066 | if (activeCount === 0) { 7067 | summary.textContent = 'No filters applied'; 7068 | } else { 7069 | summary.textContent = `${activeCount} filter${activeCount > 1 ? 's' : ''} ready: ${summaryParts.join(', ')}`; 7070 | } 7071 | } 7072 | 7073 | function applyFilters() { 7074 | const filterText = document.getElementById('filter-text'); 7075 | const filterProject = document.getElementById('filter-project'); 7076 | const tagCheckboxes = document.querySelectorAll('#filter-tags input[type="checkbox"]:checked'); 7077 | 7078 | const rawText = filterText ? filterText.value.trim() : ''; 7079 | const normalizedText = rawText.toLowerCase(); 7080 | 7081 | // Update global filter state 7082 | const filterMarket = document.getElementById('filter-market'); 7083 | 7084 | const nextProject = filterProject ? filterProject.value : ''; 7085 | const nextTags = Array.from(tagCheckboxes).map(cb => cb.value); 7086 | const nextMarket = filterMarket ? filterMarket.value : 'all'; 7087 | 7088 | currentFilters = { 7089 | text: normalizedText, 7090 | rawText: rawText, 7091 | project: nextProject, 7092 | tags: nextTags, 7093 | market: nextMarket, 7094 | isActive: Boolean(normalizedText || nextProject || nextTags.length > 0 || (nextMarket && nextMarket !== 'all')) 7095 | }; 7096 | 7097 | // Apply filters to history 7098 | const history = loadSearchHistory(); 7099 | const filteredHistory = applyAdvancedFilters(history); 7100 | 7101 | showHistoryListView(); 7102 | renderHistoryList(filteredHistory); 7103 | 7104 | // Update filter chips display 7105 | updateFilterChips(); 7106 | updateFilterSummary(); 7107 | 7108 | // Sync filters to Analysis tab 7109 | syncAnalysisFiltersWithHistoryFilters(); 7110 | 7111 | // Hide filter panel 7112 | const panel = document.getElementById('filter-panel'); 7113 | const toggleText = document.getElementById('filter-toggle-text'); 7114 | if (panel) panel.style.display = 'none'; 7115 | if (toggleText) toggleText.textContent = 'Filters'; 7116 | 7117 | } 7118 | 7119 | function applyAdvancedFilters(history) { 7120 | if (!currentFilters.isActive && !currentFilters.text && !currentFilters.project && currentFilters.tags.length === 0 && (!currentFilters.market || currentFilters.market === 'all')) { 7121 | return history; 7122 | } 7123 | 7124 | return history.filter(item => { 7125 | // Text filter - search in query, rationale, and review summary 7126 | if (currentFilters.text) { 7127 | const searchableText = [ 7128 | item.query || '', 7129 | item.results?.rationale || '', 7130 | item.results?.reviewSummary || '', 7131 | ...(item.results?.reviews || []).map(r => r.content || ''), 7132 | ...(item.results?.citations || []).map(c => c.snippet || ''), 7133 | ...(item.results?.productLinks || []).map(p => p.title || '') 7134 | ].join(' ').toLowerCase(); 7135 | 7136 | if (!searchableText.includes(currentFilters.text)) { 7137 | return false; 7138 | } 7139 | } 7140 | 7141 | // Project filter 7142 | if (currentFilters.project) { 7143 | if (item.projectId !== currentFilters.project) { 7144 | return false; 7145 | } 7146 | } 7147 | 7148 | // Tags filter - item must have ALL selected tags (AND logic) 7149 | if (currentFilters.tags.length > 0) { 7150 | const itemTags = item.tags || []; 7151 | if (!currentFilters.tags.every(tagId => itemTags.includes(tagId))) { 7152 | return false; 7153 | } 7154 | } 7155 | 7156 | // Market filter - match stored market value (legacy entries may not have market) 7157 | if (currentFilters.market && currentFilters.market !== 'all') { 7158 | const itemMarket = item.market || null; 7159 | if (itemMarket !== currentFilters.market) { 7160 | return false; 7161 | } 7162 | } 7163 | 7164 | return true; 7165 | }); 7166 | } 7167 | 7168 | function updateFilterChips() { 7169 | const activeFiltersDiv = document.getElementById('active-filters'); 7170 | const filterChips = document.getElementById('filter-chips'); 7171 | 7172 | if (!activeFiltersDiv || !filterChips) return; 7173 | 7174 | // Clear existing chips 7175 | filterChips.innerHTML = ''; 7176 | 7177 | let hasActiveFilters = false; 7178 | 7179 | // Text filter chip 7180 | if (currentFilters.text) { 7181 | hasActiveFilters = true; 7182 | const chip = createFilterChip('text', `Text: "${currentFilters.text}"`, () => { 7183 | const filterText = document.getElementById('filter-text'); 7184 | if (filterText) filterText.value = ''; 7185 | applyFilters(); 7186 | }); 7187 | filterChips.appendChild(chip); 7188 | } 7189 | 7190 | // Project filter chip 7191 | if (currentFilters.project) { 7192 | hasActiveFilters = true; 7193 | const project = loadProjects().find(p => p.id === currentFilters.project); 7194 | const chip = createFilterChip('project', `<span style="display:flex; align-items:center; gap:4px;"><img src="${projectIconUrl}" alt="Project" style="width: 14px; height: 14px;" />${project?.name || 'Unknown Project'}</span>`, () => { 7195 | const filterProject = document.getElementById('filter-project'); 7196 | if (filterProject) filterProject.value = ''; 7197 | applyFilters(); 7198 | }); 7199 | filterChips.appendChild(chip); 7200 | } 7201 | 7202 | if (currentFilters.market && currentFilters.market !== 'all') { 7203 | hasActiveFilters = true; 7204 | const option = getMarketOption(currentFilters.market); 7205 | const chip = createFilterChip('market', `🌍 ${option.label}`, () => { 7206 | const filterMarket = document.getElementById('filter-market'); 7207 | if (filterMarket) filterMarket.value = 'all'; 7208 | applyFilters(); 7209 | }); 7210 | filterChips.appendChild(chip); 7211 | } 7212 | 7213 | // Tag filter chips 7214 | if (currentFilters.tags.length > 0) { 7215 | hasActiveFilters = true; 7216 | const tags = loadTags(); 7217 | currentFilters.tags.forEach(tagId => { 7218 | const tag = tags.find(t => t.id === tagId); 7219 | if (tag) { 7220 | const chip = createFilterChip('tag', tag.name, () => { 7221 | const checkbox = document.querySelector(`#filter-tags input[value="${tagId}"]`); 7222 | if (checkbox) checkbox.checked = false; 7223 | applyFilters(); 7224 | }, tag.color); 7225 | filterChips.appendChild(chip); 7226 | } 7227 | }); 7228 | } 7229 | 7230 | // Show/hide active filters section 7231 | activeFiltersDiv.style.display = hasActiveFilters ? 'block' : 'none'; 7232 | } 7233 | 7234 | function createFilterChip(type, text, onRemove, color = '#5b8def') { 7235 | const chip = document.createElement('div'); 7236 | chip.style.cssText = ` 7237 | display: flex; 7238 | align-items: center; 7239 | gap: 6px; 7240 | background: ${color}15; 7241 | color: ${color}; 7242 | border: 1px solid ${color}30; 7243 | padding: 4px 8px; 7244 | border-radius: 12px; 7245 | font-size: 12px; 7246 | white-space: nowrap; 7247 | `; 7248 | 7249 | chip.innerHTML = ` 7250 | <span>${text}</span> 7251 | <button style=" 7252 | background: none; 7253 | border: none; 7254 | color: ${color}; 7255 | cursor: pointer; 7256 | padding: 0; 7257 | width: 14px; 7258 | height: 14px; 7259 | display: flex; 7260 | align-items: center; 7261 | justify-content: center; 7262 | font-size: 10px; 7263 | border-radius: 50%; 7264 | ">×</button> 7265 | `; 7266 | 7267 | const removeBtn = chip.querySelector('button'); 7268 | removeBtn.addEventListener('click', onRemove); 7269 | 7270 | return chip; 7271 | } 7272 | 7273 | function clearAllFilters() { 7274 | // Reset form 7275 | const filterText = document.getElementById('filter-text'); 7276 | const filterProject = document.getElementById('filter-project'); 7277 | const filterMarket = document.getElementById('filter-market'); 7278 | const tagCheckboxes = document.querySelectorAll('#filter-tags input[type="checkbox"]'); 7279 | 7280 | if (filterText) filterText.value = ''; 7281 | if (filterProject) filterProject.value = ''; 7282 | if (filterMarket) filterMarket.value = 'all'; 7283 | tagCheckboxes.forEach(cb => cb.checked = false); 7284 | 7285 | // Reset global state 7286 | currentFilters = { 7287 | text: '', 7288 | rawText: '', 7289 | project: '', 7290 | tags: [], 7291 | market: 'all', 7292 | isActive: false 7293 | }; 7294 | 7295 | // Show all history 7296 | const history = loadSearchHistory(); 7297 | renderHistoryList(history); 7298 | 7299 | // Update UI 7300 | updateFilterSummary(); 7301 | updateFilterChips(); 7302 | 7303 | } 7304 | 7305 | // ===== SIDEBAR-TO-FILTER INTEGRATION ===== 7306 | 7307 | function filterByProject(projectId) { 7308 | const tab = typeof _activeTab === 'function' ? _activeTab() : (typeof getActiveTab === 'function' ? getActiveTab() : 'history'); 7309 | if (tab === 'reports') { 7310 | _applyToAnalysis({ projectId }); 7311 | return; 7312 | } 7313 | _applyToHistory({ projectId }); 7314 | } 7315 | 7316 | function filterByTag(tagId) { 7317 | const tab = typeof _activeTab === 'function' ? _activeTab() : (typeof getActiveTab === 'function' ? getActiveTab() : 'history'); 7318 | if (tab === 'reports') { 7319 | _applyToAnalysis({ tagId }); 7320 | return; 7321 | } 7322 | _applyToHistory({ tagId }); 7323 | } 7324 | 7325 | // ===== END SIDEBAR-TO-FILTER INTEGRATION ===== 7326 | 7327 | // ===== END ADVANCED FILTERING SYSTEM ===== 7328 | 7329 | // ===== END SIDEBAR FUNCTIONALITY ===== 7330 | 7331 | 7332 | 7333 | 7334 | 7335 | 7336 | 7337 | function deleteHistoryItem(itemId) { 7338 | try { 7339 | const history = loadSearchHistory(); 7340 | const itemToDelete = history.find(item => item.id === itemId); 7341 | 7342 | // Decrement counts before deleting 7343 | if (itemToDelete) { 7344 | // Decrement project count 7345 | if (itemToDelete.projectId) { 7346 | decrementProjectSearchCount(itemToDelete.projectId); 7347 | } 7348 | 7349 | // Decrement tag usage counts 7350 | if (itemToDelete.tags && Array.isArray(itemToDelete.tags)) { 7351 | itemToDelete.tags.forEach(tagId => { 7352 | if (tagId) { 7353 | decrementTagUsage(tagId); 7354 | } 7355 | }); 7356 | } 7357 | } 7358 | 7359 | const filteredHistory = history.filter(item => item.id !== itemId); 7360 | localStorage.setItem('chatgpt-product-search-history', JSON.stringify(filteredHistory)); 7361 | loadHistory(); 7362 | 7363 | // Update sidebar to reflect new counts 7364 | populateProjectsList(); 7365 | populateTagsList(); 7366 | } catch (error) { 7367 | console.error('Error deleting history item:', error); 7368 | } 7369 | } 7370 | 7371 | function resetToCleanSearchState() { 7372 | // Clear search input fields 7373 | const searchQuery = document.getElementById('search-query'); 7374 | const multiSearchQuery = document.getElementById('multi-search-query'); 7375 | const multiProductToggle = document.getElementById('multi-product-toggle'); 7376 | const searchControls = document.getElementById('search-controls'); 7377 | const collapseToggle = document.getElementById('collapse-toggle'); 7378 | const collapseText = document.getElementById('collapse-text'); 7379 | 7380 | if (searchQuery) { 7381 | searchQuery.value = ''; 7382 | } 7383 | if (multiSearchQuery) { 7384 | multiSearchQuery.value = ''; 7385 | } 7386 | if (multiProductToggle) { 7387 | multiProductToggle.checked = false; 7388 | // Trigger change event to update UI 7389 | multiProductToggle.dispatchEvent(new Event('change')); 7390 | } 7391 | 7392 | if (searchControls) { 7393 | searchControls.style.display = 'block'; 7394 | } 7395 | if (collapseText) { 7396 | collapseText.textContent = '▲ Hide'; 7397 | } 7398 | if (collapseToggle) { 7399 | collapseToggle.style.display = 'none'; 7400 | collapseToggle.style.background = 'rgba(0, 123, 255, 0.1)'; 7401 | collapseToggle.style.border = '1px solid rgba(0, 123, 255, 0.2)'; 7402 | collapseToggle.style.color = '#5b8def'; 7403 | } 7404 | 7405 | // Remove any organization interfaces 7406 | const editToggle = document.getElementById('edit-organization-toggle'); 7407 | const editContent = document.getElementById('edit-organization-content'); 7408 | const postSearchToggle = document.getElementById('post-search-toggle'); 7409 | const postSearchContent = document.getElementById('post-search-content'); 7410 | 7411 | if (editToggle) editToggle.remove(); 7412 | if (editContent) editContent.remove(); 7413 | if (postSearchToggle) postSearchToggle.remove(); 7414 | if (postSearchContent) postSearchContent.remove(); 7415 | 7416 | // Reset results container to welcome state 7417 | const resultsContainer = document.getElementById('results-container'); 7418 | if (resultsContainer) { 7419 | resultsContainer.innerHTML = createWelcomeState(); 7420 | // Re-initialize token status after creating new welcome state 7421 | if (typeof initializeTokenStatus === 'function') { 7422 | initializeTokenStatus(); 7423 | } 7424 | } 7425 | } 7426 | 7427 | 7428 | function loadHistory() { 7429 | 7430 | // Force-hide other containers so nothing leaks into History 7431 | const searchArea = document.getElementById('search-area'); 7432 | const resultsContainer = document.getElementById('results-container'); 7433 | const reportsContainer = document.getElementById('reports-container'); 7434 | const historyContainer = document.getElementById('history-container'); 7435 | if (searchArea) searchArea.style.display = 'none'; 7436 | if (resultsContainer) resultsContainer.style.display = 'none'; 7437 | if (reportsContainer) reportsContainer.style.display = 'none'; 7438 | if (historyContainer) historyContainer.style.display = 'block'; 7439 | 7440 | showHistoryListView(); 7441 | 7442 | const history = loadSearchHistory(); 7443 | const historyWelcome = document.getElementById('history-welcome-state'); 7444 | const historyContent = document.getElementById('history-content'); 7445 | const historyList = document.getElementById('history-list'); 7446 | const clearHistoryBtn = document.getElementById('clear-history-btn'); 7447 | 7448 | // Ensure history elements are visible again after visiting other tabs 7449 | ['history-content', 'history-welcome-state', 'history-list'].forEach(id => { 7450 | const el = document.getElementById(id); 7451 | if (el) { 7452 | el.style.visibility = 'visible'; 7453 | } 7454 | }); 7455 | 7456 | if (history.length === 0) { 7457 | historyWelcome.style.display = 'flex'; 7458 | historyContent.style.display = 'none'; 7459 | if (clearHistoryBtn) { 7460 | clearHistoryBtn.style.display = 'none'; 7461 | } 7462 | } else { 7463 | historyWelcome.style.display = 'none'; 7464 | historyContent.style.display = 'block'; 7465 | if (clearHistoryBtn) { 7466 | clearHistoryBtn.style.display = 'block'; 7467 | } 7468 | 7469 | // Initialize advanced filtering system 7470 | initializeAdvancedFiltering(); 7471 | 7472 | // Restore filter UI to match current filter state 7473 | const filterTextField = document.getElementById('filter-text'); 7474 | const filterProjectField = document.getElementById('filter-project'); 7475 | const filterTagCheckboxes = document.querySelectorAll('#filter-tags input[type="checkbox"]'); 7476 | 7477 | if (filterTextField) { 7478 | filterTextField.value = currentFilters.rawText || ''; 7479 | } 7480 | if (filterProjectField) { 7481 | filterProjectField.value = currentFilters.project || ''; 7482 | } 7483 | if (filterTagCheckboxes.length > 0) { 7484 | const activeTags = new Set(currentFilters.tags || []); 7485 | filterTagCheckboxes.forEach(cb => { 7486 | cb.checked = activeTags.has(cb.value); 7487 | }); 7488 | } 7489 | 7490 | currentFilters.isActive = Boolean( 7491 | (currentFilters.rawText && currentFilters.rawText.trim().length) || 7492 | (currentFilters.project && currentFilters.project.length) || 7493 | (currentFilters.tags && currentFilters.tags.length) 7494 | ); 7495 | 7496 | if (typeof updateFilterSummary === 'function') { 7497 | updateFilterSummary(); 7498 | } 7499 | if (typeof updateFilterChips === 'function') { 7500 | updateFilterChips(); 7501 | } 7502 | 7503 | // Apply current filters (if any) or show all 7504 | const filteredHistory = applyAdvancedFilters(history); 7505 | renderHistoryList(filteredHistory); 7506 | } 7507 | } 7508 | 7509 | function renderHistoryList(history) { 7510 | const historyList = document.getElementById('history-list'); 7511 | if (!historyList) { 7512 | return; 7513 | } 7514 | 7515 | const listItems = Array.isArray(history) ? history.slice() : []; 7516 | 7517 | if (listItems.length === 0) { 7518 | historyList.innerHTML = '<div class="history-empty-row">No searches match the current filters.</div>'; 7519 | return; 7520 | } 7521 | 7522 | const groups = []; 7523 | const groupCache = new Map(); 7524 | 7525 | listItems.forEach(item => { 7526 | const itemDate = resolveHistoryDate(item); 7527 | const groupKey = buildHistoryGroupKey(itemDate); 7528 | let group = groupCache.get(groupKey); 7529 | if (!group) { 7530 | group = { 7531 | label: buildHistoryGroupLabel(itemDate), 7532 | items: [] 7533 | }; 7534 | groupCache.set(groupKey, group); 7535 | groups.push(group); 7536 | } 7537 | group.items.push({ item, itemDate }); 7538 | }); 7539 | 7540 | historyList.innerHTML = groups.map(group => ` 7541 | <section class="history-day-group"> 7542 | <header class="history-day-header">${group.label}</header> 7543 | <div class="history-day-body"> 7544 | ${group.items.map(({ item, itemDate }) => renderHistoryRowMarkup(item, itemDate)).join('')} 7545 | </div> 7546 | </section> 7547 | `).join(''); 7548 | 7549 | historyList.querySelectorAll('.reopen-search-btn').forEach(btn => { 7550 | btn.addEventListener('click', event => { 7551 | event.stopPropagation(); 7552 | const itemId = btn.getAttribute('data-id'); 7553 | if (itemId) { 7554 | reopenSearch(itemId); 7555 | } 7556 | }); 7557 | }); 7558 | 7559 | historyList.querySelectorAll('.delete-history-btn').forEach(btn => { 7560 | btn.addEventListener('click', event => { 7561 | event.stopPropagation(); 7562 | const itemId = btn.getAttribute('data-id'); 7563 | if (itemId) { 7564 | deleteHistoryItem(itemId); 7565 | } 7566 | }); 7567 | }); 7568 | 7569 | historyList.querySelectorAll('.history-row').forEach(row => { 7570 | row.addEventListener('click', event => { 7571 | if (event.target.closest('.history-icon-btn')) { 7572 | return; 7573 | } 7574 | const itemId = row.getAttribute('data-id'); 7575 | if (itemId) { 7576 | reopenSearch(itemId); 7577 | } 7578 | }); 7579 | 7580 | row.addEventListener('keydown', event => { 7581 | if (event.defaultPrevented) { 7582 | return; 7583 | } 7584 | if (event.key === 'Enter' || event.key === ' ') { 7585 | event.preventDefault(); 7586 | const itemId = row.getAttribute('data-id'); 7587 | if (itemId) { 7588 | reopenSearch(itemId); 7589 | } 7590 | } 7591 | }); 7592 | }); 7593 | 7594 | historyList.querySelectorAll('.history-query-toggle').forEach(btn => { 7595 | btn.addEventListener('click', event => { 7596 | event.preventDefault(); 7597 | event.stopPropagation(); 7598 | const container = btn.closest('.history-query-group'); 7599 | if (!container) { 7600 | return; 7601 | } 7602 | const isExpanded = container.dataset.expanded === 'true'; 7603 | const total = btn.getAttribute('data-total') || container.querySelectorAll('.history-query-chip').length; 7604 | container.dataset.expanded = isExpanded ? 'false' : 'true'; 7605 | btn.textContent = isExpanded ? `Show all (${total})` : 'Show less'; 7606 | }); 7607 | }); 7608 | 7609 | const historyContainer = document.getElementById('history-container'); 7610 | if (historyContainer) { 7611 | const analysisResults = historyContainer.querySelector('#analysis-results'); 7612 | const analysisContent = historyContainer.querySelector('#analysis-content'); 7613 | const citationTable = historyContainer.querySelector('#citation-sources-table'); 7614 | const reviewTable = historyContainer.querySelector('#review-sources-table'); 7615 | 7616 | if (analysisResults) { 7617 | analysisResults.remove(); 7618 | } 7619 | if (analysisContent) { 7620 | analysisContent.remove(); 7621 | } 7622 | if (citationTable) { 7623 | citationTable.remove(); 7624 | } 7625 | if (reviewTable) { 7626 | reviewTable.remove(); 7627 | } 7628 | } 7629 | } 7630 | 7631 | function resolveHistoryDate(item) { 7632 | if (item && typeof item.timestamp === 'number') { 7633 | const fromTimestamp = new Date(item.timestamp); 7634 | if (!Number.isNaN(fromTimestamp.getTime())) { 7635 | return fromTimestamp; 7636 | } 7637 | } 7638 | 7639 | if (item && typeof item.date === 'string') { 7640 | const parsed = new Date(item.date); 7641 | if (!Number.isNaN(parsed.getTime())) { 7642 | return parsed; 7643 | } 7644 | } 7645 | 7646 | return new Date(); 7647 | } 7648 | 7649 | function buildHistoryGroupKey(date) { 7650 | const year = date.getFullYear(); 7651 | const month = String(date.getMonth() + 1).padStart(2, '0'); 7652 | const day = String(date.getDate()).padStart(2, '0'); 7653 | return `${year}-${month}-${day}`; 7654 | } 7655 | 7656 | function buildHistoryGroupLabel(date) { 7657 | if (historyDayFormatter && typeof historyDayFormatter.format === 'function') { 7658 | try { 7659 | return historyDayFormatter.format(date); 7660 | } catch (error) { 7661 | // Fall through to manual formatting 7662 | } 7663 | } 7664 | return date.toLocaleDateString(); 7665 | } 7666 | 7667 | function formatHistoryTime(date) { 7668 | if (!date) { 7669 | return ''; 7670 | } 7671 | if (historyTimeFormatter && typeof historyTimeFormatter.format === 'function') { 7672 | try { 7673 | return historyTimeFormatter.format(date); 7674 | } catch (error) { 7675 | // Fall through to manual formatting 7676 | } 7677 | } 7678 | return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); 7679 | } 7680 | 7681 | function extractHistoryQueries(item) { 7682 | if (!item) { 7683 | return []; 7684 | } 7685 | 7686 | if (item.searchType === 'multi' && typeof item.query === 'string') { 7687 | const parts = item.query.split('\n') 7688 | .map(q => q.trim()) 7689 | .filter(Boolean); 7690 | if (parts.length) { 7691 | return parts; 7692 | } 7693 | } 7694 | 7695 | if (typeof item.query === 'string' && item.query.trim()) { 7696 | return [item.query.trim()]; 7697 | } 7698 | 7699 | return []; 7700 | } 7701 | 7702 | function renderHistoryQueryChips(queries, searchType) { 7703 | const total = queries.length; 7704 | if (total === 0) { 7705 | return '<div class="history-query-group"><span class="history-query-chip">Untitled search</span></div>'; 7706 | } 7707 | 7708 | const isMulti = searchType === 'multi'; 7709 | const maxVisible = isMulti ? Math.min(2, total) : total; 7710 | 7711 | const chipsHtml = queries.map((text, index) => { 7712 | const extraAttr = index >= maxVisible ? ' data-extra="true"' : ''; 7713 | return `<span class="history-query-chip"${extraAttr}>${escapeHTML(text)}</span>`; 7714 | }).join(''); 7715 | 7716 | const hasHidden = total > maxVisible; 7717 | const toggleHtml = hasHidden 7718 | ? `<button type="button" class="history-query-toggle" data-total="${total}">Show all (${total})</button>` 7719 | : ''; 7720 | 7721 | const expandedAttr = hasHidden ? 'data-expanded="false"' : 'data-expanded="true"'; 7722 | 7723 | return `<div class="history-query-group" ${expandedAttr}>${chipsHtml}${toggleHtml}</div>`; 7724 | } 7725 | 7726 | function renderHistoryMarketInfo(item) { 7727 | if (!item) { 7728 | return ''; 7729 | } 7730 | 7731 | const marketValue = item.market || item.marketCode; 7732 | if (!marketValue) { 7733 | return ''; 7734 | } 7735 | 7736 | const option = getMarketOption(marketValue); 7737 | const fallbackLabel = escapeHTML(item.marketLabel || marketValue); 7738 | 7739 | if (!option) { 7740 | if (!fallbackLabel) { 7741 | return ''; 7742 | } 7743 | return ` 7744 | <span class="history-market"> 7745 | <span class="history-market-text"> 7746 | <span class="history-market-label">${fallbackLabel}</span> 7747 | </span> 7748 | </span> 7749 | `; 7750 | } 7751 | 7752 | const countryLabel = escapeHTML(option.country || option.label || item.marketLabel || marketValue); 7753 | const languageLabel = escapeHTML(option.language || ''); 7754 | const flagSrc = (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function') 7755 | ? chrome.runtime.getURL(option.icon) 7756 | : option.icon; 7757 | const altText = escapeAttributeValue(`${countryLabel} flag`); 7758 | 7759 | return ` 7760 | <span class="history-market"> 7761 | <img src="${flagSrc}" alt="${altText}" /> 7762 | <span class="history-market-text"> 7763 | <span class="history-market-label">${countryLabel}</span> 7764 | ${languageLabel ? `<span class="history-market-language">${languageLabel}</span>` : ''} 7765 | </span> 7766 | </span> 7767 | `; 7768 | } 7769 | 7770 | function renderHistoryRowMarkup(item, itemDate) { 7771 | const queries = extractHistoryQueries(item); 7772 | const queryChipsMarkup = renderHistoryQueryChips(queries, item.searchType); 7773 | 7774 | const tagsHtml = generateHistoryTagsAndProject(item); 7775 | 7776 | const timeLabel = formatHistoryTime(itemDate); 7777 | const marketMarkup = renderHistoryMarketInfo(item); 7778 | 7779 | const metaPieces = []; 7780 | if (marketMarkup) { 7781 | metaPieces.push(marketMarkup); 7782 | } 7783 | if (timeLabel) { 7784 | metaPieces.push(`<span class="history-time">${clockIconSvg}<span>${escapeHTML(timeLabel)}</span></span>`); 7785 | } 7786 | const metaHtml = metaPieces.length ? `<div class="history-meta-group">${metaPieces.join('')}</div>` : ''; 7787 | 7788 | const summary = item?.results?.summary || {}; 7789 | const statsBadges = [ 7790 | { label: 'reviews', value: summary.total_reviews || 0 }, 7791 | { label: 'products', value: summary.total_products || 0 }, 7792 | { label: 'links', value: summary.total_product_links || 0 } 7793 | ].map(stat => `<span><strong>${escapeHTML(String(stat.value))}</strong><span>${escapeHTML(stat.label)}</span></span>`).join(''); 7794 | const statsHtml = `<div class="history-stats">${statsBadges}</div>`; 7795 | 7796 | const accessibleLabel = escapeAttributeValue(queries[0] || 'history item'); 7797 | 7798 | return ` 7799 | <div class="history-row" data-id="${item.id}" role="button" tabindex="0" aria-label="Open history item ${accessibleLabel}"> 7800 | <div class="history-row-left"> 7801 | ${queryChipsMarkup} 7802 | ${tagsHtml || ''} 7803 | </div> 7804 | <div class="history-row-info"> 7805 | ${metaHtml}${statsHtml} 7806 | </div> 7807 | <div class="history-row-actions"> 7808 | <button class="history-icon-btn history-icon-btn--primary reopen-search-btn" data-id="${item.id}" aria-label="Open search ${accessibleLabel}">Open</button> 7809 | <button class="history-icon-btn history-icon-btn--danger delete-history-btn" data-id="${item.id}" aria-label="Delete search ${accessibleLabel}">Delete</button> 7810 | </div> 7811 | </div> 7812 | `; 7813 | } 7814 | 7815 | function toggleMultiProductSearch() { 7816 | const multiProductToggle = document.getElementById('multi-product-toggle'); 7817 | const toggleBackground = document.getElementById('toggle-background'); 7818 | const toggleSlider = document.getElementById('toggle-slider'); 7819 | const singleProductInput = document.getElementById('single-product-input'); 7820 | const multiProductInput = document.getElementById('multi-product-input'); 7821 | 7822 | if (!multiProductToggle) return; 7823 | 7824 | const isMultiMode = multiProductToggle.checked; 7825 | 7826 | if (isMultiMode) { 7827 | // Switch to multi-product mode 7828 | if (singleProductInput) singleProductInput.style.display = 'none'; 7829 | if (multiProductInput) multiProductInput.style.display = 'block'; 7830 | if (toggleBackground) toggleBackground.style.background = '#5b8def'; 7831 | if (toggleSlider) toggleSlider.style.transform = 'translateX(20px)'; 7832 | } else { 7833 | // Switch to single-product mode 7834 | if (singleProductInput) singleProductInput.style.display = 'flex'; 7835 | if (multiProductInput) multiProductInput.style.display = 'none'; 7836 | if (toggleBackground) toggleBackground.style.background = '#dee2e6'; 7837 | if (toggleSlider) toggleSlider.style.transform = 'translateX(0px)'; 7838 | } 7839 | 7840 | moveMarketSelector(isMultiMode); 7841 | } 7842 | 7843 | function showHistoryListView() { 7844 | const historyContent = document.getElementById('history-content'); 7845 | const historyDetailContainer = document.getElementById('history-detail-container'); 7846 | const historyDetailResults = document.getElementById('history-detail-results'); 7847 | const historyDetailTitle = document.getElementById('history-detail-title'); 7848 | const historyDetailMeta = document.getElementById('history-detail-meta'); 7849 | const historyDetailOpenSearch = document.getElementById('history-detail-open-search'); 7850 | 7851 | if (historyContent) { 7852 | historyContent.style.display = 'block'; 7853 | } 7854 | if (historyDetailContainer) { 7855 | historyDetailContainer.style.display = 'none'; 7856 | delete historyDetailContainer.dataset.historyId; 7857 | } 7858 | if (historyDetailResults) { 7859 | historyDetailResults.innerHTML = ''; 7860 | } 7861 | if (historyDetailTitle) { 7862 | historyDetailTitle.innerHTML = ''; 7863 | } 7864 | if (historyDetailMeta) { 7865 | historyDetailMeta.textContent = ''; 7866 | } 7867 | if (historyDetailOpenSearch) { 7868 | historyDetailOpenSearch.disabled = true; 7869 | historyDetailOpenSearch.style.cursor = 'not-allowed'; 7870 | historyDetailOpenSearch.style.opacity = '0.6'; 7871 | delete historyDetailOpenSearch.dataset.historyId; 7872 | } 7873 | } 7874 | 7875 | function showHistoryDetailView(item) { 7876 | const historyContent = document.getElementById('history-content'); 7877 | const historyDetailContainer = document.getElementById('history-detail-container'); 7878 | const historyDetailResults = document.getElementById('history-detail-results'); 7879 | const historyDetailTitle = document.getElementById('history-detail-title'); 7880 | const historyDetailMeta = document.getElementById('history-detail-meta'); 7881 | const historyDetailOpenSearch = document.getElementById('history-detail-open-search'); 7882 | 7883 | if (!historyDetailContainer || !historyDetailResults) { 7884 | openHistoryItemInSearch(item); 7885 | return; 7886 | } 7887 | 7888 | if (historyContent) { 7889 | historyContent.style.display = 'none'; 7890 | } 7891 | 7892 | historyDetailContainer.style.display = 'flex'; 7893 | historyDetailContainer.dataset.historyId = String(item.id); 7894 | 7895 | if (historyDetailTitle) { 7896 | historyDetailTitle.innerHTML = ''; 7897 | 7898 | const chipsWrapper = document.createElement('div'); 7899 | chipsWrapper.style.display = 'flex'; 7900 | chipsWrapper.style.flexWrap = 'wrap'; 7901 | chipsWrapper.style.gap = '8px'; 7902 | chipsWrapper.dataset.expanded = 'false'; 7903 | 7904 | const applyChipStyles = (chipEl) => { 7905 | chipEl.style.display = 'inline-flex'; 7906 | chipEl.style.alignItems = 'center'; 7907 | chipEl.style.background = '#e3f2fd'; 7908 | chipEl.style.color = '#1565c0'; 7909 | chipEl.style.padding = '4px 8px'; 7910 | chipEl.style.borderRadius = '12px'; 7911 | chipEl.style.fontSize = '13px'; 7912 | chipEl.style.border = '1px solid #bbdefb'; 7913 | }; 7914 | 7915 | const queries = (() => { 7916 | if (item.searchType === 'multi' && typeof item.query === 'string') { 7917 | const parts = item.query.split('\n').map(q => q.trim()).filter(Boolean); 7918 | if (parts.length === 0 && item.query) { 7919 | return [item.query]; 7920 | } 7921 | return parts; 7922 | } 7923 | if (item.query) { 7924 | return [item.query]; 7925 | } 7926 | return []; 7927 | })(); 7928 | 7929 | if (queries.length === 0) { 7930 | const chip = document.createElement('span'); 7931 | chip.textContent = 'Untitled search'; 7932 | applyChipStyles(chip); 7933 | chipsWrapper.appendChild(chip); 7934 | } else { 7935 | queries.forEach((text, index) => { 7936 | const chip = document.createElement('span'); 7937 | chip.textContent = text; 7938 | applyChipStyles(chip); 7939 | if (index > 0) { 7940 | chip.dataset.extra = 'true'; 7941 | chip.style.display = 'none'; 7942 | } 7943 | chipsWrapper.appendChild(chip); 7944 | }); 7945 | } 7946 | 7947 | historyDetailTitle.appendChild(chipsWrapper); 7948 | 7949 | if (queries.length > 1) { 7950 | const toggleButton = document.createElement('button'); 7951 | toggleButton.id = 'history-detail-toggle'; 7952 | toggleButton.style.background = 'none'; 7953 | toggleButton.style.border = 'none'; 7954 | toggleButton.style.color = '#5b8def'; 7955 | toggleButton.style.fontSize = '13px'; 7956 | toggleButton.style.cursor = 'pointer'; 7957 | toggleButton.textContent = `Show all (${queries.length})`; 7958 | 7959 | toggleButton.addEventListener('click', () => { 7960 | const isExpanded = chipsWrapper.dataset.expanded === 'true'; 7961 | const nextState = !isExpanded; 7962 | chipsWrapper.dataset.expanded = nextState ? 'true' : 'false'; 7963 | chipsWrapper.querySelectorAll('span[data-extra="true"]').forEach(chip => { 7964 | chip.style.display = nextState ? 'inline-flex' : 'none'; 7965 | }); 7966 | toggleButton.textContent = nextState ? 'Hide queries' : `Show all (${queries.length})`; 7967 | }); 7968 | 7969 | historyDetailTitle.appendChild(toggleButton); 7970 | } else { 7971 | chipsWrapper.dataset.expanded = 'true'; 7972 | } 7973 | } 7974 | 7975 | if (historyDetailMeta) { 7976 | const metaParts = []; 7977 | if (item.date) metaParts.push(item.date); 7978 | metaParts.push(item.searchType === 'multi' ? 'Multi-product search' : 'Single search'); 7979 | if (item.marketLabel) { 7980 | metaParts.push(item.marketLabel); 7981 | } else if (item.marketCode) { 7982 | metaParts.push(item.marketCode); 7983 | } 7984 | historyDetailMeta.textContent = metaParts.filter(Boolean).join(' • '); 7985 | } 7986 | 7987 | if (historyDetailOpenSearch) { 7988 | historyDetailOpenSearch.dataset.historyId = String(item.id); 7989 | historyDetailOpenSearch.disabled = false; 7990 | historyDetailOpenSearch.style.cursor = 'pointer'; 7991 | historyDetailOpenSearch.style.opacity = ''; 7992 | } 7993 | 7994 | historyDetailResults.innerHTML = ''; 7995 | if (item.searchType === 'multi' && item.results?.multiResults) { 7996 | displayMultiResults(item.results.multiResults, historyDetailResults, { suppressHeader: true }); 7997 | } else { 7998 | displayResults(item.results, item.query, historyDetailResults); 7999 | } 8000 | 8001 | showEditOrganizationInterface(item, historyDetailResults); 8002 | 8003 | historyDetailContainer.scrollTop = 0; 8004 | historyDetailResults.scrollTop = 0; 8005 | } 8006 | 8007 | function openHistoryItemInSearch(item) { 8008 | if (!item) { 8009 | return; 8010 | } 8011 | 8012 | showHistoryListView(); 8013 | 8014 | switchTab('search'); 8015 | 8016 | if (item.market) { 8017 | setMarketSelection(item.market); 8018 | } 8019 | 8020 | const searchQuery = document.getElementById('search-query'); 8021 | const multiSearchQuery = document.getElementById('multi-search-query'); 8022 | const multiProductToggle = document.getElementById('multi-product-toggle'); 8023 | 8024 | if (item.searchType === 'multi') { 8025 | if (multiProductToggle) { 8026 | multiProductToggle.checked = true; 8027 | toggleMultiProductSearch(); 8028 | } 8029 | if (multiSearchQuery) { 8030 | multiSearchQuery.value = item.query; 8031 | } 8032 | if (searchQuery) { 8033 | searchQuery.value = ''; 8034 | } 8035 | } else { 8036 | if (multiProductToggle) { 8037 | multiProductToggle.checked = false; 8038 | toggleMultiProductSearch(); 8039 | } 8040 | if (searchQuery) { 8041 | searchQuery.value = item.query; 8042 | } 8043 | if (multiSearchQuery) { 8044 | multiSearchQuery.value = ''; 8045 | } 8046 | } 8047 | 8048 | if (item.searchType === 'multi' && item.results?.multiResults) { 8049 | displayMultiResults(item.results.multiResults); 8050 | } else { 8051 | displayResults(item.results, item.query); 8052 | } 8053 | showCollapseToggle(); 8054 | showEditOrganizationInterface(item); 8055 | } 8056 | 8057 | function openHistoryItemInSearchById(historyId) { 8058 | const history = loadSearchHistory(); 8059 | const item = history.find(h => h.id === historyId); 8060 | if (!item) { 8061 | return; 8062 | } 8063 | openHistoryItemInSearch(item); 8064 | } 8065 | 8066 | function reopenSearch(itemId) { 8067 | const history = loadSearchHistory(); 8068 | const item = history.find(h => h.id === itemId); 8069 | if (!item) { 8070 | return; 8071 | } 8072 | 8073 | showHistoryDetailView(item); 8074 | } 8075 | 8076 | function filterHistory() { 8077 | // Legacy function - now redirects to advanced filtering 8078 | // This maintains compatibility with existing event listeners 8079 | applyFilters(); 8080 | } 8081 | 8082 | function setButtonLoadingState(button, isLoading, overrides = {}) { 8083 | if (!button) { 8084 | return; 8085 | } 8086 | 8087 | const readyAriaLabel = overrides.readyAriaLabel 8088 | ?? button.getAttribute('data-ready-aria-label') 8089 | ?? button.getAttribute('aria-label') 8090 | ?? 'Run search'; 8091 | 8092 | const loadingAriaLabel = overrides.loadingAriaLabel 8093 | ?? button.getAttribute('data-loading-aria-label') 8094 | ?? 'Searching'; 8095 | 8096 | const readyStatusText = overrides.readyStatusText 8097 | ?? button.getAttribute('data-ready-status') 8098 | ?? readyAriaLabel; 8099 | 8100 | const loadingStatusText = overrides.loadingStatusText 8101 | ?? button.getAttribute('data-loading-status') 8102 | ?? loadingAriaLabel; 8103 | 8104 | const statusText = button.querySelector('.search-btn-status'); 8105 | button.dataset.state = isLoading ? 'loading' : 'ready'; 8106 | button.disabled = isLoading; 8107 | button.setAttribute('aria-busy', isLoading ? 'true' : 'false'); 8108 | button.setAttribute('aria-label', isLoading ? loadingAriaLabel : readyAriaLabel); 8109 | if (statusText) { 8110 | statusText.textContent = isLoading ? loadingStatusText : readyStatusText; 8111 | } 8112 | } 8113 | 8114 | async function performSearch() { 8115 | const searchQuery = document.getElementById('search-query'); 8116 | const searchBtn = document.getElementById('search-btn'); 8117 | const resultsContainer = document.getElementById('results-container'); 8118 | 8119 | if (!searchQuery || !searchBtn || !resultsContainer) { 8120 | alert('Modal elements not found. Please try again.'); 8121 | return; 8122 | } 8123 | 8124 | const query = searchQuery.value.trim(); 8125 | 8126 | if (!query) { 8127 | alert('Please enter a search query'); 8128 | return; 8129 | } 8130 | 8131 | const marketSettings = getSelectedMarketSettings(); 8132 | 8133 | // Get token automatically 8134 | let token; 8135 | try { 8136 | token = await getAutomaticToken(); 8137 | } catch (error) { 8138 | alert('Failed to get authentication token. Please make sure you\'re logged in to ChatGPT.'); 8139 | return; 8140 | } 8141 | // Show loading state 8142 | setButtonLoadingState(searchBtn, true); 8143 | resultsContainer.style.display = 'block'; 8144 | resultsContainer.innerHTML = ` 8145 | <div style="text-align: center; padding: 40px; color: #666;"> 8146 | <div class="cpr-loading-spinner"></div> 8147 | <p>Searching for "${query}"...</p> 8148 | </div> 8149 | `; 8150 | 8151 | try { 8152 | const result = await searchProduct(query, token, marketSettings); 8153 | displayResults(result, query); 8154 | const historyId = saveSearchToHistory(query, result, 'single', [], null, marketSettings.value); 8155 | 8156 | // Show post-search tagging interface 8157 | showPostSearchTagging(query, result, 'single', historyId); 8158 | } catch (error) { 8159 | displayError(error.message); 8160 | } finally { 8161 | setButtonLoadingState(searchBtn, false); 8162 | showCollapseToggle(); 8163 | } 8164 | } 8165 | 8166 | async function performMultiSearch() { 8167 | const multiSearchQuery = document.getElementById('multi-search-query'); 8168 | const multiSearchBtn = document.getElementById('multi-search-btn'); 8169 | const resultsContainer = document.getElementById('results-container'); 8170 | 8171 | if (!multiSearchQuery || !multiSearchBtn || !resultsContainer) { 8172 | alert('Modal elements not found. Please try again.'); 8173 | return; 8174 | } 8175 | 8176 | const queries = multiSearchQuery.value.trim().split('\n').filter(q => q.trim()); 8177 | 8178 | // Remove duplicates (case-insensitive) to avoid unnecessary requests 8179 | const uniqueQueries = [...new Set(queries.map(q => q.toLowerCase()))].map(lowerQuery => { 8180 | // Find the original case version of this query 8181 | return queries.find(originalQuery => originalQuery.toLowerCase() === lowerQuery); 8182 | }); 8183 | 8184 | if (uniqueQueries.length === 0) { 8185 | alert('Please enter at least one product name'); 8186 | return; 8187 | } 8188 | 8189 | // Show info if duplicates were removed 8190 | if (queries.length > uniqueQueries.length) { 8191 | } 8192 | 8193 | // If only one unique query remains, treat as single product search 8194 | if (uniqueQueries.length === 1) { 8195 | const singleQuery = document.getElementById('search-query'); 8196 | if (singleQuery) { 8197 | singleQuery.value = uniqueQueries[0]; 8198 | setButtonLoadingState(multiSearchBtn, true, { 8199 | loadingAriaLabel: 'Searching product', 8200 | loadingStatusText: 'Searching product' 8201 | }); 8202 | try { 8203 | await performSearch(); 8204 | } finally { 8205 | setButtonLoadingState(multiSearchBtn, false); 8206 | } 8207 | return; 8208 | } 8209 | } 8210 | 8211 | if (uniqueQueries.length > 10) { 8212 | alert('Maximum 10 products allowed at once to avoid rate limiting'); 8213 | return; 8214 | } 8215 | 8216 | const marketSettings = getSelectedMarketSettings(); 8217 | 8218 | // Get token automatically 8219 | let token; 8220 | try { 8221 | token = await getAutomaticToken(); 8222 | } catch (error) { 8223 | alert('Failed to get authentication token. Please make sure you\'re logged in to ChatGPT.'); 8224 | return; 8225 | } 8226 | 8227 | // Show loading state 8228 | setButtonLoadingState(multiSearchBtn, true, { 8229 | loadingAriaLabel: `Searching ${uniqueQueries.length} products`, 8230 | loadingStatusText: `Searching ${uniqueQueries.length} products` 8231 | }); 8232 | resultsContainer.style.display = 'block'; 8233 | resultsContainer.innerHTML = ` 8234 | <div style="text-align: center; padding: 40px; color: #666;"> 8235 | <div class="cpr-loading-spinner"></div> 8236 | <p>Searching ${uniqueQueries.length} products...</p> 8237 | <div id="progress-status" style="font-size: 14px; color: #999; margin-top: 10px;"> 8238 | Starting searches... 8239 | </div> 8240 | </div> 8241 | `; 8242 | 8243 | const results = []; 8244 | const progressStatus = document.getElementById('progress-status'); 8245 | 8246 | try { 8247 | // Search products one by one 8248 | for (let i = 0; i < uniqueQueries.length; i++) { 8249 | const query = uniqueQueries[i].trim(); 8250 | if (progressStatus) { 8251 | progressStatus.textContent = `Searching "${query}" (${i + 1}/${uniqueQueries.length})...`; 8252 | } 8253 | 8254 | try { 8255 | const result = await searchProduct(query, token, marketSettings); 8256 | results.push({ 8257 | query: query, 8258 | success: true, 8259 | data: result 8260 | }); 8261 | } catch (error) { 8262 | results.push({ 8263 | query: query, 8264 | success: false, 8265 | error: error.message 8266 | }); 8267 | } 8268 | 8269 | // Add a small delay between requests to be respectful 8270 | if (i < uniqueQueries.length - 1) { 8271 | await new Promise(resolve => setTimeout(resolve, 1000)); 8272 | } 8273 | } 8274 | 8275 | displayMultiResults(results); 8276 | 8277 | // Show post-search tagging interface for multi-search 8278 | const queriesText = uniqueQueries.join('\n'); 8279 | const combinedResults = { 8280 | summary: { 8281 | total_reviews: results.reduce((acc, r) => acc + (r.success ? (r.data.summary?.total_reviews || 0) : 0), 0), 8282 | total_products: results.reduce((acc, r) => acc + (r.success ? (r.data.summary?.total_products || 0) : 0), 0), 8283 | total_product_links: results.reduce((acc, r) => acc + (r.success ? (r.data.summary?.total_product_links || 0) : 0), 0), 8284 | review_themes: [] 8285 | }, 8286 | multiResults: results, 8287 | rationale: `Multi-product search for ${queries.length} products`, 8288 | reviewSummary: `Combined results from ${results.filter(r => r.success).length} successful searches` 8289 | }; 8290 | const historyId = saveSearchToHistory(queriesText, combinedResults, 'multi', [], null, marketSettings.value); 8291 | showPostSearchTagging(queriesText, combinedResults, 'multi', historyId); 8292 | } catch (error) { 8293 | displayError(error.message); 8294 | } finally { 8295 | setButtonLoadingState(multiSearchBtn, false); 8296 | showCollapseToggle(); 8297 | } 8298 | } 8299 | 8300 | async function getAutomaticToken() { 8301 | try { 8302 | const response = await fetch("/api/auth/session"); 8303 | if (!response.ok) { 8304 | throw new Error(`Session API responded with: ${response.status} ${response.statusText}`); 8305 | } 8306 | const sessionData = await response.json(); 8307 | 8308 | if (!sessionData.accessToken) { 8309 | throw new Error("No access token found in session. Please make sure you're logged in to ChatGPT."); 8310 | } 8311 | 8312 | // Update the token input field to show it's been fetched 8313 | const tokenInput = document.getElementById('auth-token'); 8314 | if (tokenInput) { 8315 | applyInputStatusStyles(tokenInput, { 8316 | text: 'Token fetched from session', 8317 | iconUrl: checkIconUrl, 8318 | color: '#155724', 8319 | backgroundColor: '#d4edda', 8320 | borderColor: '#c3e6cb' 8321 | }); 8322 | tokenInput.readOnly = true; 8323 | tokenInput.style.cursor = 'not-allowed'; 8324 | } 8325 | 8326 | return sessionData.accessToken; 8327 | } catch (error) { 8328 | // Update the token input field to show error 8329 | const tokenInput = document.getElementById('auth-token'); 8330 | if (tokenInput) { 8331 | applyInputStatusStyles(tokenInput, { 8332 | text: 'Failed to fetch token automatically', 8333 | iconUrl: errorIconUrl, 8334 | color: '#721c24', 8335 | backgroundColor: '#f8d7da', 8336 | borderColor: '#f5c6cb' 8337 | }); 8338 | tokenInput.readOnly = false; 8339 | tokenInput.style.cursor = "text"; 8340 | } 8341 | 8342 | throw error; 8343 | } 8344 | } 8345 | 8346 | async function searchProduct(query, token, marketSettings = getSelectedMarketSettings()) { 8347 | const effectiveMarket = marketSettings || getSelectedMarketSettings(); 8348 | const requestBody = { 8349 | "conversation_id": "", 8350 | "is_client_thread": true, 8351 | "message_id": "", 8352 | "product_query": query, 8353 | "supported_encodings": ["v1"], 8354 | "product_lookup_key": { 8355 | "data": JSON.stringify({ 8356 | "request_query": query, 8357 | "all_ids": {"p2": [""]}, 8358 | "known_ids": {}, 8359 | "metadata_sources": ["p1", "p3"], 8360 | "variant_sources": null, 8361 | "last_variant_group_types": null, 8362 | "merchant_hints": [], 8363 | "provider_title": query 8364 | }), 8365 | "version": "1", 8366 | "variant_options_query": null 8367 | } 8368 | }; 8369 | 8370 | const response = await fetch("https://chatgpt.com/backend-api/search/product_info", { 8371 | "headers": { 8372 | "accept": "text/event-stream", 8373 | "accept-language": effectiveMarket.acceptLanguage, 8374 | "authorization": "Bearer " + token, 8375 | "content-type": "application/json", 8376 | "oai-client-version": "prod-43c98f917bf2c3e3a36183e9548cd048e4e40615", 8377 | "oai-device-id": generateDeviceId(), 8378 | "oai-language": effectiveMarket.oaiLanguage || 'en-US' 8379 | }, 8380 | "referrerPolicy": "strict-origin-when-cross-origin", 8381 | "body": JSON.stringify(requestBody), 8382 | "method": "POST", 8383 | "mode": "cors", 8384 | "credentials": "include" 8385 | }); 8386 | 8387 | if (!response.ok) { 8388 | throw new Error(`HTTP error! status: ${response.status}`); 8389 | } 8390 | 8391 | const responseText = await response.text(); 8392 | return parseProductInfo(responseText); 8393 | } 8394 | 8395 | function generateDeviceId() { 8396 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 8397 | const r = Math.random() * 16 | 0; 8398 | const v = c == 'x' ? r : (r & 0x3 | 0x8); 8399 | return v.toString(16); 8400 | }); 8401 | } 8402 | 8403 | function extractDomainFromUrl(url) { 8404 | try { 8405 | const urlObj = new URL(url); 8406 | return urlObj.hostname.replace('www.', ''); 8407 | } catch (e) { 8408 | return url.split('/')[2] || url; 8409 | } 8410 | } 8411 | 8412 | function getFaviconUrl(url) { 8413 | try { 8414 | const urlObj = new URL(url); 8415 | const domain = urlObj.protocol + '//' + urlObj.hostname; 8416 | return `https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodeURIComponent(domain)}&size=16`; 8417 | } catch (e) { 8418 | // Fallback for invalid URLs 8419 | return `https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodeURIComponent(url)}&size=16`; 8420 | } 8421 | } 8422 | 8423 | // Function to create floating button 8424 | function createFloatingButton() { 8425 | const button = document.createElement('button'); 8426 | button.id = 'openProductSearchModalBtn'; 8427 | button.title = 'Open ChatGPT E-commerce Product Research'; 8428 | const iconBase = (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function') 8429 | ? chrome.runtime.getURL('icons/logobubble.svg') 8430 | : null; 8431 | 8432 | if (iconBase) { 8433 | const iconImg = document.createElement('img'); 8434 | iconImg.src = iconBase; 8435 | iconImg.alt = 'ChatGPT Product Info'; 8436 | iconImg.style.pointerEvents = 'none'; 8437 | button.appendChild(iconImg); 8438 | } else { 8439 | button.textContent = '🛍️'; 8440 | button.classList.add('icon-fallback'); 8441 | } 8442 | document.body.appendChild(button); 8443 | return button; 8444 | } 8445 | 8446 | // Create floating button and add click handler 8447 | const floatingButton = createFloatingButton(); 8448 | floatingButton.addEventListener('click', createModal); 8449 | 8450 | // Initialize token status check 8451 | async function initializeTokenStatus() { 8452 | const tokenInput = document.getElementById('auth-token'); 8453 | const authStatus = document.getElementById('auth-status'); 8454 | 8455 | if (!tokenInput || !authStatus) { 8456 | return; 8457 | } 8458 | 8459 | try { 8460 | const response = await fetch("/api/auth/session"); 8461 | if (response.ok) { 8462 | const sessionData = await response.json(); 8463 | if (sessionData.accessToken) { 8464 | // Update hidden token field 8465 | if (tokenInput) { 8466 | applyInputStatusStyles(tokenInput, { 8467 | text: 'Token ready - session active', 8468 | iconUrl: checkIconUrl, 8469 | color: '#155724', 8470 | backgroundColor: '#d4edda', 8471 | borderColor: '#c3e6cb' 8472 | }); 8473 | tokenInput.readOnly = true; 8474 | tokenInput.style.cursor = 'not-allowed'; 8475 | } 8476 | 8477 | // Update visible auth status 8478 | if (authStatus) { 8479 | applyStatusBanner(authStatus, { 8480 | iconType: 'success', 8481 | text: 'Ready to search', 8482 | color: '#155724', 8483 | backgroundColor: '#d4edda', 8484 | borderColor: '#c3e6cb' 8485 | }); 8486 | } 8487 | 8488 | } else { 8489 | throw new Error("No access token in session"); 8490 | } 8491 | } else { 8492 | throw new Error(`Session check failed: ${response.status}`); 8493 | } 8494 | } catch (error) { 8495 | // Update hidden token field 8496 | if (tokenInput) { 8497 | applyInputStatusStyles(tokenInput, { 8498 | text: 'Please log in to ChatGPT first', 8499 | iconUrl: errorIconUrl, 8500 | color: '#721c24', 8501 | backgroundColor: '#f8d7da', 8502 | borderColor: '#f5c6cb' 8503 | }); 8504 | tokenInput.readOnly = false; 8505 | tokenInput.style.cursor = 'text'; 8506 | } 8507 | 8508 | // Update visible auth status 8509 | if (authStatus) { 8510 | applyStatusBanner(authStatus, { 8511 | iconType: 'error', 8512 | text: 'Please log in to ChatGPT first', 8513 | color: '#721c24', 8514 | backgroundColor: '#f8d7da', 8515 | borderColor: '#f5c6cb' 8516 | }); 8517 | } 8518 | } 8519 | } 8520 | 8521 | // Parse product info from API response 8522 | function parseProductInfo(content) { 8523 | const products = []; 8524 | const reviews = []; 8525 | const productLinks = []; // Store product links from grouped_citation 8526 | const rationaleObj = { text: '' }; // Track the current rationale being built (using object for reference) 8527 | const citations = new Map(); // Store citations by cite key 8528 | const summaryObj = { text: '' }; // Track the current summary being built (using object for reference) 8529 | 8530 | const lines = content.split('\n'); 8531 | let currentEvent = null; 8532 | let currentData = []; 8533 | let eventCount = 0; 8534 | 8535 | for (let i = 0; i < lines.length; i++) { 8536 | const line = lines[i]; 8537 | 8538 | if (line.startsWith('event: ')) { 8539 | if (currentEvent && currentData.length > 0) { 8540 | processEvent(currentEvent, currentData.join('\n'), products, reviews, productLinks, rationaleObj, eventCount, citations, summaryObj); 8541 | eventCount++; 8542 | } 8543 | 8544 | currentEvent = line.replace('event: ', '').trim(); 8545 | currentData = []; 8546 | } else if (line.startsWith('data: ')) { 8547 | currentData.push(line.replace('data: ', '')); 8548 | } else if (line.trim() === '') { 8549 | continue; 8550 | } else { 8551 | currentData.push(line); 8552 | } 8553 | } 8554 | 8555 | if (currentEvent && currentData.length > 0) { 8556 | processEvent(currentEvent, currentData.join('\n'), products, reviews, productLinks, rationaleObj, eventCount, citations, summaryObj); 8557 | eventCount++; 8558 | } 8559 | 8560 | // Map citations to reviews 8561 | for (const review of reviews) { 8562 | if (!review.url && review.cite && citations.has(review.cite)) { 8563 | review.url = citations.get(review.cite).url; 8564 | } 8565 | } 8566 | 8567 | const uniqueMerchants = new Set(); 8568 | for (const product of products) { 8569 | for (const offer of product.offers || []) { 8570 | if (offer.merchant_name) { 8571 | uniqueMerchants.add(offer.merchant_name); 8572 | } 8573 | } 8574 | } 8575 | 8576 | const reviewThemes = [...new Set(reviews.map(review => review.theme))]; 8577 | 8578 | productLinks.forEach((link, index) => { 8579 | }); 8580 | 8581 | // Remove exact duplicates (same title AND url) while preserving different sources 8582 | const deduplicatedLinks = []; 8583 | const seenCombinations = new Set(); 8584 | 8585 | productLinks.forEach(link => { 8586 | const key = `${link.title}|||${link.url}`; 8587 | if (!seenCombinations.has(key)) { 8588 | seenCombinations.add(key); 8589 | deduplicatedLinks.push(link); 8590 | } else { 8591 | } 8592 | }); 8593 | 8594 | 8595 | return { 8596 | products: products, 8597 | productLinks: deduplicatedLinks, // Use deduplicated product links 8598 | reviews: reviews, 8599 | rationale: rationaleObj.text || null, 8600 | reviewSummary: summaryObj.text || null, // Add the built summary 8601 | summary: { 8602 | total_products: products.length, 8603 | total_product_links: deduplicatedLinks.length, 8604 | total_reviews: reviews.length, 8605 | unique_merchants: uniqueMerchants.size, 8606 | review_themes: reviewThemes 8607 | } 8608 | }; 8609 | } 8610 | 8611 | function processEvent(eventType, dataStr, products, reviews, productLinks, rationaleObj, eventIndex, citations, summaryObj) { 8612 | if (eventType !== 'delta' || !dataStr || dataStr === '""') { 8613 | return; 8614 | } 8615 | 8616 | try { 8617 | const data = JSON.parse(dataStr); 8618 | 8619 | if (typeof data !== 'object' || data === null) { 8620 | return; 8621 | } 8622 | 8623 | // Track processed patches to avoid duplicates 8624 | const processedPatches = new Set(); 8625 | 8626 | // Handle direct patch objects (like your examples) 8627 | if (data.p === '/grouped_citation' && data.o === 'replace' && data.v && data.v.url) { 8628 | eventIndex++; 8629 | const citeKey = `turn0search${eventIndex}`; 8630 | citations.set(citeKey, { 8631 | url: data.v.url, 8632 | title: data.v.title || '' 8633 | }); 8634 | 8635 | // Capture ALL grouped_citation objects as product links 8636 | const productLink = { 8637 | title: data.v.title || '', 8638 | url: data.v.url, 8639 | snippet: data.v.snippet || '', 8640 | source: extractDomainFromUrl(data.v.url) 8641 | }; 8642 | 8643 | productLinks.push(productLink); 8644 | } 8645 | 8646 | // Handle direct rationale patches 8647 | if (data.p === '/rationale' && data.o === 'append' && data.v) { 8648 | rationaleObj.text += data.v; 8649 | } 8650 | 8651 | // Handle direct summary patches 8652 | if (data.p === '/summary' && data.o === 'append' && data.v) { 8653 | summaryObj.text += data.v; 8654 | } 8655 | 8656 | // Capture citations from cite_map and rationale patches 8657 | if (data.v && Array.isArray(data.v)) { 8658 | for (const patch of data.v) { 8659 | if (patch.p && patch.p.startsWith('/cite_map/') && patch.o === 'add' && patch.v && patch.v.url) { 8660 | const citeKey = patch.p.replace('/cite_map/', ''); 8661 | citations.set(citeKey, { 8662 | url: patch.v.url, 8663 | title: patch.v.title || '' 8664 | }); 8665 | } 8666 | 8667 | // Capture grouped_citation from data.v array (like your example!) 8668 | if (patch.p === '/grouped_citation' && patch.o === 'replace' && patch.v && patch.v.url) { 8669 | eventIndex++; 8670 | const citeKey = `turn0search${eventIndex}`; 8671 | citations.set(citeKey, { 8672 | url: patch.v.url, 8673 | title: patch.v.title || '' 8674 | }); 8675 | 8676 | // Capture ALL grouped_citation objects as product links 8677 | const productLink = { 8678 | title: patch.v.title || '', 8679 | url: patch.v.url, 8680 | snippet: patch.v.snippet || '', 8681 | source: extractDomainFromUrl(patch.v.url) 8682 | }; 8683 | 8684 | productLinks.push(productLink); 8685 | } 8686 | 8687 | 8688 | // Handle supporting_websites array 8689 | if (patch.p === '/grouped_citation/supporting_websites' && patch.o === 'append' && patch.v && Array.isArray(patch.v)) { 8690 | for (const supportingSite of patch.v) { 8691 | if (supportingSite.url) { 8692 | const productLink = { 8693 | title: supportingSite.title || 'Supporting Link', 8694 | url: supportingSite.url, 8695 | snippet: supportingSite.snippet || '', 8696 | source: extractDomainFromUrl(supportingSite.url) 8697 | }; 8698 | productLinks.push(productLink); 8699 | } 8700 | } 8701 | } 8702 | 8703 | // Capture rationale patches from data.v array 8704 | if (patch.p === '/rationale' && patch.o === 'append' && patch.v) { 8705 | const patchKey = `/rationale-${patch.v}`; 8706 | if (!processedPatches.has(patchKey)) { 8707 | processedPatches.add(patchKey); 8708 | rationaleObj.text += patch.v; 8709 | } 8710 | } 8711 | 8712 | // Capture summary patches from data.v array 8713 | if (patch.p === '/summary' && patch.o === 'append' && patch.v) { 8714 | summaryObj.text += patch.v; 8715 | } 8716 | } 8717 | } 8718 | 8719 | // Also check patch operations for citations and rationale updates 8720 | if (data.o === 'patch' && data.v && Array.isArray(data.v)) { 8721 | for (const patch of data.v) { 8722 | if (patch.p && patch.p.startsWith('/cite_map/') && patch.o === 'add' && patch.v && patch.v.url) { 8723 | const citeKey = patch.p.replace('/cite_map/', ''); 8724 | citations.set(citeKey, { 8725 | url: patch.v.url, 8726 | title: patch.v.title || '' 8727 | }); 8728 | } 8729 | 8730 | if (patch.p === '/grouped_citation' && patch.o === 'replace' && patch.v && patch.v.url) { 8731 | // This is a citation being added/updated 8732 | citations.set(`turn0search${eventIndex}`, { 8733 | url: patch.v.url, 8734 | title: patch.v.title || '' 8735 | }); 8736 | 8737 | // Capture ALL grouped_citation objects as product links 8738 | const productLink = { 8739 | title: patch.v.title || '', 8740 | url: patch.v.url, 8741 | snippet: patch.v.snippet || '', 8742 | source: extractDomainFromUrl(patch.v.url) 8743 | }; 8744 | 8745 | productLinks.push(productLink); 8746 | } 8747 | 8748 | // Handle supporting_websites array in patch operations too 8749 | if (patch.p === '/grouped_citation/supporting_websites' && patch.o === 'append' && patch.v && Array.isArray(patch.v)) { 8750 | for (const supportingSite of patch.v) { 8751 | if (supportingSite.url) { 8752 | const productLink = { 8753 | title: supportingSite.title || 'Supporting Link', 8754 | url: supportingSite.url, 8755 | snippet: supportingSite.snippet || '', 8756 | source: extractDomainFromUrl(supportingSite.url) 8757 | }; 8758 | productLinks.push(productLink); 8759 | } 8760 | } 8761 | } 8762 | 8763 | // Capture rationale patches 8764 | if (patch.p === '/rationale' && patch.o === 'append' && patch.v) { 8765 | const patchKey = `/rationale-${patch.v}`; 8766 | if (!processedPatches.has(patchKey)) { 8767 | processedPatches.add(patchKey); 8768 | rationaleObj.text += patch.v; 8769 | } 8770 | } 8771 | 8772 | // Capture summary patches 8773 | if (patch.p === '/summary' && patch.o === 'append' && patch.v) { 8774 | summaryObj.text += patch.v; 8775 | } 8776 | } 8777 | } 8778 | 8779 | if (data.v && typeof data.v === 'object' && !Array.isArray(data.v)) { 8780 | const vData = data.v; 8781 | 8782 | if (vData.type === 'product_entity' && vData.product) { 8783 | const product = vData.product; 8784 | 8785 | const productInfo = { 8786 | title: product.title || '', 8787 | price: product.price || '', 8788 | url: product.url || '', 8789 | merchants: product.merchants || '', 8790 | description: product.description || null, 8791 | rating: product.rating || null, 8792 | num_reviews: product.num_reviews || null, 8793 | featured_tag: product.featured_tag || null, 8794 | image_urls: product.image_urls || [], 8795 | offers: [] 8796 | }; 8797 | 8798 | if (product.offers) { 8799 | for (const offerData of product.offers) { 8800 | const offer = { 8801 | merchant_name: offerData.merchant_name || '', 8802 | product_name: offerData.product_name || '', 8803 | url: offerData.url || '', 8804 | price: offerData.price || '', 8805 | details: offerData.details || null, 8806 | available: offerData.available !== false, 8807 | tag: offerData.tag?.text || null 8808 | }; 8809 | productInfo.offers.push(offer); 8810 | } 8811 | } 8812 | 8813 | productInfo.variants = []; 8814 | if (product.variants) { 8815 | for (const variant of product.variants) { 8816 | const selectedOption = variant.options?.find(opt => opt.selected)?.label || null; 8817 | productInfo.variants.push({ 8818 | type: variant.label || '', 8819 | selected: selectedOption 8820 | }); 8821 | } 8822 | } 8823 | 8824 | products.push(productInfo); 8825 | } 8826 | 8827 | else if (vData.type === 'product_reviews') { 8828 | // Initialize the current summary from the product_reviews object 8829 | if (vData.summary) { 8830 | summaryObj.text = vData.summary; 8831 | } 8832 | 8833 | const reviewList = vData.reviews || []; 8834 | for (const reviewData of reviewList) { 8835 | const review = { 8836 | source: reviewData.source || '', 8837 | theme: reviewData.theme || '', 8838 | summary: reviewData.summary || '', 8839 | sentiment: reviewData.sentiment || '', 8840 | rating: reviewData.rating || null, 8841 | num_reviews: reviewData.num_reviews || null, 8842 | cite: reviewData.cite || null, 8843 | url: reviewData.url || null 8844 | }; 8845 | reviews.push(review); 8846 | } 8847 | } 8848 | 8849 | else if (vData.type === 'product_rationale') { 8850 | const rationale = vData.rationale || ''; 8851 | if (rationale) { 8852 | // Initialize rationale - set the initial text 8853 | rationaleObj.text = rationale; 8854 | } 8855 | } 8856 | } 8857 | 8858 | else if (data.o === 'add' && data.v) { 8859 | const vData = data.v; 8860 | if (typeof vData === 'object' && vData.type === 'product_reviews') { 8861 | const reviewList = vData.reviews || []; 8862 | for (const reviewData of reviewList) { 8863 | if (typeof reviewData === 'object') { 8864 | const review = { 8865 | source: reviewData.source || '', 8866 | theme: reviewData.theme || '', 8867 | summary: reviewData.summary || '', 8868 | sentiment: reviewData.sentiment || '', 8869 | rating: reviewData.rating || null, 8870 | num_reviews: reviewData.num_reviews || null, 8871 | cite: reviewData.cite || null, 8872 | url: reviewData.url || null 8873 | }; 8874 | reviews.push(review); 8875 | } 8876 | } 8877 | } 8878 | } 8879 | 8880 | else if (data.o === 'patch' && data.v) { 8881 | const vData = data.v; 8882 | if (Array.isArray(vData)) { 8883 | for (const item of vData) { 8884 | if (typeof item === 'object' && item.p === '/reviews' && item.o === 'append') { 8885 | const reviewList = item.v || []; 8886 | if (Array.isArray(reviewList)) { 8887 | for (const reviewData of reviewList) { 8888 | if (typeof reviewData === 'object') { 8889 | const review = { 8890 | source: reviewData.source || '', 8891 | theme: reviewData.theme || '', 8892 | summary: reviewData.summary || '', 8893 | sentiment: reviewData.sentiment || '', 8894 | rating: reviewData.rating || null, 8895 | num_reviews: reviewData.num_reviews || null, 8896 | cite: reviewData.cite || null, 8897 | url: reviewData.url || null 8898 | }; 8899 | reviews.push(review); 8900 | } 8901 | } 8902 | } 8903 | } 8904 | } 8905 | } 8906 | } 8907 | 8908 | if (data.v && Array.isArray(data.v)) { 8909 | for (const item of data.v) { 8910 | if (typeof item === 'object' && item.p === '/reviews' && item.o === 'append') { 8911 | const reviewList = item.v || []; 8912 | if (Array.isArray(reviewList)) { 8913 | for (const reviewData of reviewList) { 8914 | if (typeof reviewData === 'object') { 8915 | const review = { 8916 | source: reviewData.source || '', 8917 | theme: reviewData.theme || '', 8918 | summary: reviewData.summary || '', 8919 | sentiment: reviewData.sentiment || '', 8920 | rating: reviewData.rating || null, 8921 | num_reviews: reviewData.num_reviews || null, 8922 | cite: reviewData.cite || null, 8923 | url: reviewData.url || null 8924 | }; 8925 | reviews.push(review); 8926 | } 8927 | } 8928 | } 8929 | } 8930 | } 8931 | } 8932 | 8933 | } catch (jsonError) { 8934 | return; 8935 | } 8936 | } 8937 | 8938 | // Determine theme sentiment color styling for review themes 8939 | function getThemeColor(theme, reviews) { 8940 | if (!reviews || reviews.length === 0) { 8941 | return { background: '#f8f9fa', color: '#6c757d' }; 8942 | } 8943 | 8944 | const themeReviews = reviews.filter(review => 8945 | review.theme && review.theme.toLowerCase() === theme.toLowerCase() 8946 | ); 8947 | 8948 | if (themeReviews.length === 0) { 8949 | const sentimentCounts = reviews.reduce((acc, review) => { 8950 | acc[review.sentiment] = (acc[review.sentiment] || 0) + 1; 8951 | return acc; 8952 | }, {}); 8953 | 8954 | const totalReviews = reviews.length; 8955 | const positivePercent = (sentimentCounts.positive || 0) / totalReviews; 8956 | const negativePercent = (sentimentCounts.negative || 0) / totalReviews; 8957 | 8958 | if (positivePercent > 0.6) { 8959 | return { background: '#d1f2d1', color: '#2d5a2d' }; 8960 | } 8961 | if (negativePercent > 0.6) { 8962 | return { background: '#f8d7da', color: '#721c24' }; 8963 | } 8964 | 8965 | return { background: '#fff3cd', color: '#856404' }; 8966 | } 8967 | 8968 | const themeSentimentCounts = themeReviews.reduce((acc, review) => { 8969 | acc[review.sentiment] = (acc[review.sentiment] || 0) + 1; 8970 | return acc; 8971 | }, {}); 8972 | 8973 | const themeTotal = themeReviews.length; 8974 | const positivePercent = (themeSentimentCounts.positive || 0) / themeTotal; 8975 | const negativePercent = (themeSentimentCounts.negative || 0) / themeTotal; 8976 | 8977 | if (positivePercent > negativePercent && positivePercent > 0.5) { 8978 | return { background: '#d1f2d1', color: '#2d5a2d' }; 8979 | } 8980 | if (negativePercent > positivePercent && negativePercent > 0.5) { 8981 | return { background: '#f8d7da', color: '#721c24' }; 8982 | } 8983 | 8984 | return { background: '#fff3cd', color: '#856404' }; 8985 | } 8986 | 8987 | function copyMultiResultsToClipboard(results) { 8988 | const successfulResults = results.filter(r => r.success); 8989 | 8990 | // Format as Markdown 8991 | let markdown = `# Multi-Product Search Results\n\n`; 8992 | markdown += `**${successfulResults.length}** products found\n\n`; 8993 | markdown += `---\n\n`; 8994 | 8995 | // Add each product's results 8996 | successfulResults.forEach((result, index) => { 8997 | const data = result.data; 8998 | const query = result.query; 8999 | 9000 | markdown += `# ${index + 1}. ${query}\n\n`; 9001 | 9002 | // Stats 9003 | markdown += `**${data.summary.total_reviews}** reviews • **${data.summary.total_products}** products • **${data.summary.total_product_links}** citation links • **${data.summary.review_themes.length}** themes\n\n`; 9004 | 9005 | // Product Overview 9006 | if (data.rationale && data.rationale.trim()) { 9007 | markdown += `## Product Overview\n\n${data.rationale}\n\n`; 9008 | } 9009 | 9010 | // Review Summary 9011 | if (data.reviewSummary && data.reviewSummary.trim()) { 9012 | markdown += `## Review Summary\n\n${data.reviewSummary}\n\n`; 9013 | } 9014 | 9015 | // Citation Links 9016 | if (data.productLinks && data.productLinks.length > 0) { 9017 | markdown += `## Citation Links\n\n`; 9018 | data.productLinks.forEach(link => { 9019 | markdown += `### ${link.title}\n`; 9020 | markdown += `🔗 ${link.url}\n`; 9021 | if (link.snippet) { 9022 | markdown += `${link.snippet}\n`; 9023 | } 9024 | markdown += `\n`; 9025 | }); 9026 | } 9027 | 9028 | // Reviews with sentiment analysis 9029 | if (data.reviews.length > 0) { 9030 | markdown += `## Reviews\n\n`; 9031 | 9032 | // Calculate sentiment distribution 9033 | const sentimentCounts = data.reviews.reduce((acc, review) => { 9034 | acc[review.sentiment] = (acc[review.sentiment] || 0) + 1; 9035 | return acc; 9036 | }, {}); 9037 | 9038 | const totalReviews = data.reviews.length; 9039 | const positivePercent = Math.round(((sentimentCounts.positive || 0) / totalReviews) * 100); 9040 | const neutralPercent = Math.round(((sentimentCounts.neutral || 0) / totalReviews) * 100); 9041 | const negativePercent = Math.round(((sentimentCounts.negative || 0) / totalReviews) * 100); 9042 | 9043 | markdown += `**Sentiment Distribution:** `; 9044 | markdown += `✅ ${positivePercent}% Positive`; 9045 | if (neutralPercent > 0) markdown += ` • ⚠️ ${neutralPercent}% Neutral`; 9046 | if (negativePercent > 0) markdown += ` • ❌ ${negativePercent}% Negative`; 9047 | markdown += `\n\n`; 9048 | 9049 | data.reviews.forEach(review => { 9050 | const sentimentEmoji = review.sentiment === 'positive' ? '✅' : 9051 | review.sentiment === 'negative' ? '❌' : '⚠️'; 9052 | markdown += `### ${sentimentEmoji} ${review.theme}\n`; 9053 | markdown += `**Source:** ${review.source}`; 9054 | if (review.url) { 9055 | markdown += ` (${review.url})`; 9056 | } 9057 | markdown += `\n\n${review.summary}\n\n`; 9058 | }); 9059 | } 9060 | 9061 | // Themes 9062 | if (data.summary.review_themes.length > 0) { 9063 | markdown += `## Key Themes\n\n`; 9064 | markdown += data.summary.review_themes.map(theme => `- ${theme}`).join('\n'); 9065 | markdown += `\n\n`; 9066 | } 9067 | 9068 | // Separator between products 9069 | if (index < successfulResults.length - 1) { 9070 | markdown += `---\n\n`; 9071 | } 9072 | }); 9073 | 9074 | // Copy to clipboard 9075 | navigator.clipboard.writeText(markdown).then(() => { 9076 | const copyBtn = document.getElementById('copy-multi-results-btn'); 9077 | if (copyBtn) { 9078 | const originalHTML = copyBtn.innerHTML; 9079 | copyBtn.innerHTML = ` 9080 | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"> 9081 | <path d="M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z"/> 9082 | </svg> 9083 | Copied! 9084 | `; 9085 | copyBtn.style.background = '#d4edda'; 9086 | copyBtn.style.color = '#155724'; 9087 | copyBtn.style.borderColor = '#c3e6cb'; 9088 | 9089 | setTimeout(() => { 9090 | copyBtn.innerHTML = originalHTML; 9091 | copyBtn.style.background = '#fff'; 9092 | copyBtn.style.color = '#495057'; 9093 | copyBtn.style.borderColor = '#dee2e6'; 9094 | }, 2000); 9095 | } 9096 | }).catch(err => { 9097 | console.error('Failed to copy:', err); 9098 | alert('Failed to copy to clipboard'); 9099 | }); 9100 | } 9101 | 9102 | function copyResultsToClipboard(data, query) { 9103 | // Format data as Markdown 9104 | let markdown = `# Search Results: ${query}\n\n`; 9105 | 9106 | // Summary stats 9107 | markdown += `**${data.summary.total_reviews}** reviews • **${data.summary.total_products}** products • **${data.summary.total_product_links}** citation links • **${data.summary.review_themes.length}** themes\n\n`; 9108 | 9109 | // Product Overview 9110 | if (data.rationale && data.rationale.trim()) { 9111 | markdown += `## Product Overview\n\n${data.rationale}\n\n`; 9112 | } 9113 | 9114 | // Review Summary 9115 | if (data.reviewSummary && data.reviewSummary.trim()) { 9116 | markdown += `## Review Summary\n\n${data.reviewSummary}\n\n`; 9117 | } 9118 | 9119 | // Citation Links 9120 | if (data.productLinks && data.productLinks.length > 0) { 9121 | markdown += `## Citation Links\n\n`; 9122 | data.productLinks.forEach(link => { 9123 | markdown += `### ${link.title}\n`; 9124 | markdown += `🔗 ${link.url}\n`; 9125 | if (link.snippet) { 9126 | markdown += `${link.snippet}\n`; 9127 | } 9128 | markdown += `\n`; 9129 | }); 9130 | } 9131 | 9132 | // Reviews with sentiment analysis 9133 | if (data.reviews.length > 0) { 9134 | markdown += `## Reviews\n\n`; 9135 | 9136 | // Calculate sentiment distribution 9137 | const sentimentCounts = data.reviews.reduce((acc, review) => { 9138 | acc[review.sentiment] = (acc[review.sentiment] || 0) + 1; 9139 | return acc; 9140 | }, {}); 9141 | 9142 | const totalReviews = data.reviews.length; 9143 | const positivePercent = Math.round(((sentimentCounts.positive || 0) / totalReviews) * 100); 9144 | const neutralPercent = Math.round(((sentimentCounts.neutral || 0) / totalReviews) * 100); 9145 | const negativePercent = Math.round(((sentimentCounts.negative || 0) / totalReviews) * 100); 9146 | 9147 | markdown += `**Sentiment Distribution:** `; 9148 | markdown += `✅ ${positivePercent}% Positive`; 9149 | if (neutralPercent > 0) markdown += ` • ⚠️ ${neutralPercent}% Neutral`; 9150 | if (negativePercent > 0) markdown += ` • ❌ ${negativePercent}% Negative`; 9151 | markdown += `\n\n`; 9152 | 9153 | data.reviews.forEach(review => { 9154 | const sentimentEmoji = review.sentiment === 'positive' ? '✅' : 9155 | review.sentiment === 'negative' ? '❌' : '⚠️'; 9156 | markdown += `### ${sentimentEmoji} ${review.theme}\n`; 9157 | markdown += `**Source:** ${review.source}`; 9158 | if (review.url) { 9159 | markdown += ` (${review.url})`; 9160 | } 9161 | markdown += `\n\n${review.summary}\n\n`; 9162 | }); 9163 | } 9164 | 9165 | // Themes 9166 | if (data.summary.review_themes.length > 0) { 9167 | markdown += `## Key Themes\n\n`; 9168 | markdown += data.summary.review_themes.map(theme => `- ${theme}`).join('\n'); 9169 | markdown += `\n`; 9170 | } 9171 | 9172 | // Copy to clipboard 9173 | navigator.clipboard.writeText(markdown).then(() => { 9174 | const copyBtn = document.getElementById('copy-results-btn'); 9175 | if (copyBtn) { 9176 | const originalHTML = copyBtn.innerHTML; 9177 | copyBtn.innerHTML = ` 9178 | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"> 9179 | <path d="M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z"/> 9180 | </svg> 9181 | Copied! 9182 | `; 9183 | copyBtn.style.background = '#d4edda'; 9184 | copyBtn.style.color = '#155724'; 9185 | copyBtn.style.borderColor = '#c3e6cb'; 9186 | 9187 | setTimeout(() => { 9188 | copyBtn.innerHTML = originalHTML; 9189 | copyBtn.style.background = '#fff'; 9190 | copyBtn.style.color = '#495057'; 9191 | copyBtn.style.borderColor = '#dee2e6'; 9192 | }, 2000); 9193 | } 9194 | }).catch(err => { 9195 | console.error('Failed to copy:', err); 9196 | alert('Failed to copy to clipboard'); 9197 | }); 9198 | } 9199 | 9200 | function displayResults(data, query, targetContainer, options = {}) { 9201 | const defaultContainer = document.getElementById('results-container'); 9202 | let resultsContainer = null; 9203 | 9204 | if (targetContainer instanceof HTMLElement) { 9205 | resultsContainer = targetContainer; 9206 | } else if (typeof targetContainer === 'string') { 9207 | resultsContainer = document.getElementById(targetContainer); 9208 | } 9209 | 9210 | if (!resultsContainer) { 9211 | resultsContainer = defaultContainer; 9212 | } 9213 | 9214 | if (!resultsContainer) { 9215 | return; 9216 | } 9217 | 9218 | if (resultsContainer === defaultContainer) { 9219 | resultsContainer.style.display = 'block'; 9220 | } 9221 | 9222 | if (!data || (!data.reviews.length && !data.products.length && !data.productLinks.length)) { 9223 | resultsContainer.innerHTML = ` 9224 | <div style=" 9225 | background: #fef2f2; 9226 | color: #991b1b; 9227 | padding: 15px; 9228 | border-radius: 8px; 9229 | border-left: 4px solid #ef4444; 9230 | margin: 20px 0; 9231 | "> 9232 | <h3>No results found</h3> 9233 | <p>No products or reviews were found for "${query}". Try a different search term.</p> 9234 | </div> 9235 | `; 9236 | return; 9237 | } 9238 | 9239 | const suppressHeader = Boolean(options.suppressHeader); 9240 | 9241 | let html = ''; 9242 | 9243 | if (!suppressHeader) { 9244 | html += ` 9245 | <div style=" 9246 | background: #f8f9fa; 9247 | padding: 10px 14px; 9248 | margin-bottom: 16px; 9249 | border-left: 4px solid #5b8def; 9250 | display: flex; 9251 | flex-wrap: wrap; 9252 | justify-content: space-between; 9253 | align-items: center; 9254 | gap: 12px; 9255 | "> 9256 | <div style="display: flex; flex-direction: column; gap: 4px;"> 9257 | <h3 style="margin: 0; color: #495057;">Results for "${query}"</h3> 9258 | <div style="font-size: 12px; color: #6c757d;"> 9259 | ${data.summary.total_reviews} reviews • 9260 | ${data.summary.total_products} products • 9261 | ${data.summary.total_product_links} citation links • 9262 | ${data.summary.review_themes.length} themes 9263 | </div> 9264 | </div> 9265 | <button id="copy-results-btn" title="Copy results to clipboard in Markdown format" style=" 9266 | display: flex; 9267 | align-items: center; 9268 | gap: 6px; 9269 | padding: 8px 14px; 9270 | background: #fff; 9271 | border: 1px solid #dee2e6; 9272 | border-radius: 6px; 9273 | color: #495057; 9274 | font-size: 13px; 9275 | font-weight: 500; 9276 | cursor: pointer; 9277 | transition: all 0.2s ease; 9278 | "> 9279 | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"> 9280 | <rect width="256" height="256" fill="none"/> 9281 | <polyline points="168 168 216 168 216 40 88 40 88 88" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/> 9282 | <rect x="40" y="88" width="128" height="128" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/> 9283 | </svg> 9284 | Copy Results 9285 | </button> 9286 | </div> 9287 | `; 9288 | } 9289 | 9290 | if (data.rationale && data.rationale.trim()) { 9291 | html += ` 9292 | <div style="margin-bottom: 20px;"> 9293 | <div style=" 9294 | font-size: 14px; 9295 | font-weight: 600; 9296 | color: #495057; 9297 | margin-bottom: 8px; 9298 | padding: 0 12px; 9299 | ">Product Overview</div> 9300 | <div style=" 9301 | background: #e2f1ff; 9302 | padding: 6px 12px; 9303 | margin: 0 12px; 9304 | color: #000; 9305 | line-height: 1.4; 9306 | font-size: 13px; 9307 | border-left: 3px solid #3a6797; 9308 | ">${data.rationale}</div> 9309 | </div> 9310 | `; 9311 | } 9312 | 9313 | if (data.reviewSummary && data.reviewSummary.trim()) { 9314 | html += ` 9315 | <div style="margin-bottom: 20px;"> 9316 | <div style=" 9317 | font-size: 14px; 9318 | font-weight: 600; 9319 | color: #495057; 9320 | margin-bottom: 8px; 9321 | padding: 0 12px; 9322 | ">Review Summary</div> 9323 | <div style=" 9324 | background: #f5fee8; 9325 | padding: 6px 12px; 9326 | margin: 0 12px; 9327 | color: #000; 9328 | line-height: 1.4; 9329 | font-size: 13px; 9330 | border-left: 3px solid #93ac71; 9331 | ">${data.reviewSummary}</div> 9332 | </div> 9333 | `; 9334 | } 9335 | 9336 | if (data.productLinks && data.productLinks.length > 0) { 9337 | html += ` 9338 | <div style="margin-bottom: 20px;"> 9339 | <div style=" 9340 | font-size: 14px; 9341 | font-weight: 600; 9342 | color: #495057; 9343 | margin-bottom: 8px; 9344 | padding: 0 12px; 9345 | ">Citation Links</div> 9346 | ${data.productLinks.map(link => ` 9347 | <div style=" 9348 | border: 1px solid #e9ecef; 9349 | border-radius: 6px; 9350 | padding: 6px 12px; 9351 | margin: 0 12px 8px 12px; 9352 | background: #f8f9fa; 9353 | "> 9354 | <div style=" 9355 | display: flex; 9356 | align-items: center; 9357 | margin-bottom: 6px; 9358 | gap: 8px; 9359 | "> 9360 | <img src="${getFaviconUrl(link.url)}" 9361 | alt="Site favicon" 9362 | style=" 9363 | width: 16px; 9364 | height: 16px; 9365 | flex-shrink: 0; 9366 | " 9367 | onerror="this.style.display='none'" /> 9368 | <span style=" 9369 | font-weight: 600; 9370 | color: #5b8def; 9371 | font-size: 14px; 9372 | ">${link.title}</span> 9373 | <a href="${link.url}" target="_blank" style=" 9374 | color: #28a745; 9375 | text-decoration: none; 9376 | font-size: 14px; 9377 | margin-left: auto; 9378 | background: #d4edda; 9379 | padding: 4px 8px; 9380 | border-radius: 4px; 9381 | border: 1px solid #c3e6cb; 9382 | " title="Visit citation page">↗</a> 9383 | </div> 9384 | ${link.snippet ? `<div style=" 9385 | color: #6c757d; 9386 | font-size: 12px; 9387 | line-height: 1.3; 9388 | margin-top: 4px; 9389 | ">${link.snippet}</div>` : ''} 9390 | </div> 9391 | `).join('')} 9392 | </div> 9393 | `; 9394 | } 9395 | 9396 | if (data.reviews.length > 0) { 9397 | // Calculate sentiment distribution 9398 | const sentimentCounts = data.reviews.reduce((acc, review) => { 9399 | acc[review.sentiment] = (acc[review.sentiment] || 0) + 1; 9400 | return acc; 9401 | }, {}); 9402 | 9403 | const totalReviews = data.reviews.length; 9404 | const positiveCount = sentimentCounts.positive || 0; 9405 | const neutralCount = sentimentCounts.neutral || 0; 9406 | const negativeCount = sentimentCounts.negative || 0; 9407 | 9408 | const positivePercent = Math.round((positiveCount / totalReviews) * 100); 9409 | const neutralPercent = Math.round((neutralCount / totalReviews) * 100); 9410 | const negativePercent = Math.round((negativeCount / totalReviews) * 100); 9411 | 9412 | html += ` 9413 | <div> 9414 | <div style=" 9415 | display: flex; 9416 | align-items: center; 9417 | justify-content: space-between; 9418 | margin-bottom: 12px; 9419 | padding: 0 12px; 9420 | "> 9421 | <div style=" 9422 | font-size: 14px; 9423 | font-weight: 600; 9424 | color: #495057; 9425 | ">Reviews</div> 9426 | <div style="display: flex; align-items: center; gap: 12px;"> 9427 | <div style=" 9428 | display: flex; 9429 | background: #f8f9fa; 9430 | border-radius: 8px; 9431 | overflow: hidden; 9432 | width: 100px; 9433 | height: 6px; 9434 | border: 1px solid #e9ecef; 9435 | "> 9436 | ${positiveCount > 0 ? `<div style=" 9437 | background: #28a745; 9438 | width: ${positivePercent}%; 9439 | height: 100%; 9440 | " title="${positiveCount} positive (${positivePercent}%)"></div>` : ''} 9441 | ${neutralCount > 0 ? `<div style=" 9442 | background: #ffc107; 9443 | width: ${neutralPercent}%; 9444 | height: 100%; 9445 | " title="${neutralCount} neutral (${neutralPercent}%)"></div>` : ''} 9446 | ${negativeCount > 0 ? `<div style=" 9447 | background: #dc3545; 9448 | width: ${negativePercent}%; 9449 | height: 100%; 9450 | " title="${negativeCount} negative (${negativePercent}%)"></div>` : ''} 9451 | </div> 9452 | <div style=" 9453 | font-size: 12px; 9454 | color: #6c757d; 9455 | white-space: nowrap; 9456 | "> 9457 | <span style="color: #28a745;">●</span> ${positivePercent}% 9458 | ${neutralCount > 0 ? `<span style="color: #ffc107; margin-left: 6px;">●</span> ${neutralPercent}%` : ''} 9459 | ${negativeCount > 0 ? `<span style="color: #dc3545; margin-left: 6px;">●</span> ${negativePercent}%` : ''} 9460 | </div> 9461 | </div> 9462 | </div> 9463 | ${data.reviews.map(review => { 9464 | const sentimentColor = review.sentiment === 'positive' ? '#28a745' : 9465 | review.sentiment === 'negative' ? '#dc3545' : '#ffc107'; 9466 | 9467 | return ` 9468 | <div style=" 9469 | border-bottom: 1px solid #f8f9fa; 9470 | padding: 6px 12px; 9471 | margin-bottom: 1px; 9472 | "> 9473 | <div style=" 9474 | display: flex; 9475 | align-items: center; 9476 | margin-bottom: 8px; 9477 | gap: 8px; 9478 | "> 9479 | ${review.url ? `<img src="${getFaviconUrl(review.url)}" 9480 | alt="Site favicon" 9481 | style=" 9482 | width: 16px; 9483 | height: 16px; 9484 | flex-shrink: 0; 9485 | " 9486 | onerror="this.style.display='none'" />` : ''} 9487 | <span style="font-weight: 500; color: #495057; font-size: 14px;">${review.source}</span> 9488 | ${review.url ? `<a href="${review.url}" target="_blank" style=" 9489 | color: #6c757d; 9490 | text-decoration: none; 9491 | font-size: 12px; 9492 | margin-left: auto; 9493 | " title="Open source">↗</a>` : ''} 9494 | <span style=" 9495 | width: 8px; 9496 | height: 8px; 9497 | border-radius: 50%; 9498 | background: ${sentimentColor}; 9499 | display: inline-block; 9500 | margin-left: ${review.url ? '4px' : 'auto'}; 9501 | "></span> 9502 | </div> 9503 | <div style=" 9504 | font-size: 13px; 9505 | font-weight: 600; 9506 | color: #5b8def; 9507 | margin-bottom: 6px; 9508 | ">${review.theme}</div> 9509 | <div style="color: #6c757d; line-height: 1.4; font-size: 13px;">${review.summary}</div> 9510 | </div> 9511 | `; 9512 | }).join('')} 9513 | </div> 9514 | `; 9515 | } 9516 | 9517 | if (data.summary.review_themes.length > 0) { 9518 | html += ` 9519 | <div style="margin-bottom: 20px;"> 9520 | <div style=" 9521 | font-size: 14px; 9522 | font-weight: 600; 9523 | color: #495057; 9524 | margin-bottom: 8px; 9525 | padding: 0 12px; 9526 | ">Themes</div> 9527 | <div style="display: flex; flex-wrap: wrap; gap: 6px; padding: 0 12px;"> 9528 | ${data.summary.review_themes.map(theme => { 9529 | const colors = getThemeColor(theme, data.reviews); 9530 | return `<span style=" 9531 | background: ${colors.background}; 9532 | color: ${colors.color}; 9533 | padding: 4px 8px; 9534 | border-radius: 12px; 9535 | font-size: 12px; 9536 | border: 1px solid ${colors.background === '#d1f2d1' ? '#c3e6cb' : colors.background === '#f8d7da' ? '#f5c6cb' : '#ffeaa7'}; 9537 | ">${theme}</span>`; 9538 | }).join('')} 9539 | </div> 9540 | </div> 9541 | `; 9542 | } 9543 | 9544 | resultsContainer.innerHTML = html; 9545 | 9546 | // Attach copy button event listener 9547 | if (!suppressHeader) { 9548 | const copyBtn = document.getElementById('copy-results-btn'); 9549 | if (copyBtn) { 9550 | copyBtn.addEventListener('click', () => copyResultsToClipboard(data, query)); 9551 | // Add hover effects 9552 | copyBtn.addEventListener('mouseenter', () => { 9553 | copyBtn.style.background = '#f8f9fa'; 9554 | copyBtn.style.borderColor = '#5b8def'; 9555 | }); 9556 | copyBtn.addEventListener('mouseleave', () => { 9557 | copyBtn.style.background = '#fff'; 9558 | copyBtn.style.borderColor = '#dee2e6'; 9559 | }); 9560 | } 9561 | } 9562 | } 9563 | 9564 | function displayMultiResults(results, targetContainer, options = {}) { 9565 | const defaultContainer = document.getElementById('results-container'); 9566 | let resultsContainer = null; 9567 | 9568 | if (targetContainer instanceof HTMLElement) { 9569 | resultsContainer = targetContainer; 9570 | } else if (typeof targetContainer === 'string') { 9571 | resultsContainer = document.getElementById(targetContainer); 9572 | } 9573 | 9574 | if (!resultsContainer) { 9575 | resultsContainer = defaultContainer; 9576 | } 9577 | 9578 | if (!resultsContainer) { 9579 | return; 9580 | } 9581 | 9582 | if (resultsContainer === defaultContainer) { 9583 | resultsContainer.style.display = 'block'; 9584 | } 9585 | 9586 | const successfulResults = results.filter(r => r.success); 9587 | const failedResults = results.filter(r => !r.success); 9588 | 9589 | let html = ` 9590 | <div style=" 9591 | padding: 6px 12px; 9592 | margin-bottom: 16px; 9593 | border-bottom: 1px solid #e9ecef; 9594 | display: flex; 9595 | justify-content: space-between; 9596 | align-items: center; 9597 | gap: 12px; 9598 | flex-wrap: wrap; 9599 | "> 9600 | <div> 9601 | <div style=" 9602 | font-size: 16px; 9603 | font-weight: 600; 9604 | color: #495057; 9605 | margin-bottom: 8px; 9606 | ">Multi-Product Search Results</div> 9607 | <div style="display: flex; gap: 16px; font-size: 14px; color: #6c757d;"> 9608 | <span>${successfulResults.length}/${results.length} products found</span> 9609 | ${failedResults.length > 0 ? `<span style="color: #dc3545;">${failedResults.length} failed</span>` : ''} 9610 | </div> 9611 | </div> 9612 | ${successfulResults.length > 0 ? ` 9613 | <button id="copy-multi-results-btn" title="Copy all product results to clipboard in Markdown format" style=" 9614 | display: flex; 9615 | align-items: center; 9616 | gap: 6px; 9617 | padding: 8px 14px; 9618 | background: #fff; 9619 | border: 1px solid #dee2e6; 9620 | border-radius: 6px; 9621 | color: #495057; 9622 | font-size: 13px; 9623 | font-weight: 500; 9624 | cursor: pointer; 9625 | transition: all 0.2s ease; 9626 | "> 9627 | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"> 9628 | <rect width="256" height="256" fill="none"/> 9629 | <polyline points="168 168 216 168 216 40 88 40 88 88" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/> 9630 | <rect x="40" y="88" width="128" height="128" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/> 9631 | </svg> 9632 | Copy All Results 9633 | </button> 9634 | ` : ''} 9635 | </div> 9636 | `; 9637 | 9638 | if (failedResults.length > 0) { 9639 | html += ` 9640 | <div style="margin-bottom: 20px;"> 9641 | <div style=" 9642 | font-size: 14px; 9643 | font-weight: 600; 9644 | color: #dc3545; 9645 | margin-bottom: 8px; 9646 | padding: 0 12px; 9647 | ">Search Errors</div> 9648 | <div style=" 9649 | background: #f8d7da; 9650 | border: 1px solid #f5c6cb; 9651 | border-radius: 4px; 9652 | padding: 6px 12px; 9653 | margin: 0 12px; 9654 | color: #721c24; 9655 | font-size: 13px; 9656 | "> 9657 | ${failedResults.map(r => `<div><strong>${r.query}:</strong> ${r.error}</div>`).join('<br>')} 9658 | </div> 9659 | </div> 9660 | `; 9661 | } 9662 | 9663 | if (successfulResults.length === 0) { 9664 | html += ` 9665 | <div style=" 9666 | text-align: center; 9667 | padding: 60px 40px; 9668 | color: #6c757d; 9669 | "> 9670 | <span class="status-icon status-icon--large status-icon--error" aria-hidden="true" style="margin-bottom: 20px; color: #dc3545; opacity: 0.8;"></span> 9671 | <h3 style="margin: 0 0 12px 0; font-size: 20px; font-weight: 600; color: #495057;">No Results Found</h3> 9672 | <p style="margin: 0; font-size: 16px; line-height: 1.5;">None of the products could be found. Please try different search terms.</p> 9673 | </div> 9674 | `; 9675 | resultsContainer.innerHTML = html; 9676 | return; 9677 | } 9678 | 9679 | // Create comparison table 9680 | html += ` 9681 | <div style="margin-bottom: 20px;"> 9682 | <div style=" 9683 | font-size: 14px; 9684 | font-weight: 600; 9685 | color: #495057; 9686 | margin-bottom: 12px; 9687 | padding: 0 12px; 9688 | ">Product Comparison Table</div> 9689 | 9690 | <div style="overflow-x: auto; margin: 0 12px;"> 9691 | <table style=" 9692 | width: 100%; 9693 | border-collapse: collapse; 9694 | background: white; 9695 | border: 1px solid #e9ecef; 9696 | border-radius: 6px; 9697 | overflow: hidden; 9698 | font-size: 13px; 9699 | "> 9700 | <thead> 9701 | <tr style="background: #f8f9fa; border-bottom: 2px solid #e9ecef;"> 9702 | <th style=" 9703 | padding: 12px 8px; 9704 | text-align: left; 9705 | font-weight: 600; 9706 | color: #495057; 9707 | border-right: 1px solid #e9ecef; 9708 | min-width: 150px; 9709 | ">Product</th> 9710 | <th style=" 9711 | padding: 12px 8px; 9712 | text-align: center; 9713 | font-weight: 600; 9714 | color: #495057; 9715 | border-right: 1px solid #e9ecef; 9716 | min-width: 80px; 9717 | ">Reviews</th> 9718 | <th style=" 9719 | padding: 12px 8px; 9720 | text-align: center; 9721 | font-weight: 600; 9722 | color: #495057; 9723 | border-right: 1px solid #e9ecef; 9724 | min-width: 120px; 9725 | ">Sentiment</th> 9726 | <th style=" 9727 | padding: 12px 8px; 9728 | text-align: left; 9729 | font-weight: 600; 9730 | color: #495057; 9731 | border-right: 1px solid #e9ecef; 9732 | min-width: 200px; 9733 | ">Themes</th> 9734 | <th style=" 9735 | padding: 12px 8px; 9736 | text-align: center; 9737 | font-weight: 600; 9738 | color: #495057; 9739 | min-width: 80px; 9740 | ">Links</th> 9741 | </tr> 9742 | </thead> 9743 | <tbody> 9744 | `; 9745 | 9746 | successfulResults.forEach((result, index) => { 9747 | const data = result.data; 9748 | 9749 | // Calculate sentiment distribution 9750 | const sentimentCounts = data.reviews.reduce((acc, review) => { 9751 | acc[review.sentiment] = (acc[review.sentiment] || 0) + 1; 9752 | return acc; 9753 | }, {}); 9754 | 9755 | const totalReviews = data.reviews.length; 9756 | const positiveCount = sentimentCounts.positive || 0; 9757 | const neutralCount = sentimentCounts.neutral || 0; 9758 | const negativeCount = sentimentCounts.negative || 0; 9759 | 9760 | const positivePercent = totalReviews > 0 ? Math.round((positiveCount / totalReviews) * 100) : 0; 9761 | const neutralPercent = totalReviews > 0 ? Math.round((neutralCount / totalReviews) * 100) : 0; 9762 | const negativePercent = totalReviews > 0 ? Math.round((negativeCount / totalReviews) * 100) : 0; 9763 | 9764 | // Get all themes instead of just top 3 9765 | const topThemes = data.summary.review_themes; 9766 | 9767 | html += ` 9768 | <tr style=" 9769 | border-bottom: 1px solid #f8f9fa; 9770 | ${index % 2 === 0 ? 'background: #fdfdfd;' : 'background: white;'} 9771 | "> 9772 | <td style=" 9773 | padding: 12px 8px; 9774 | border-right: 1px solid #e9ecef; 9775 | vertical-align: top; 9776 | "> 9777 | <div style=" 9778 | font-weight: 600; 9779 | color: #5b8def; 9780 | margin-bottom: 4px; 9781 | cursor: pointer; 9782 | " data-product-index="${index}" class="product-name-link">${result.query}</div> 9783 | ${data.rationale ? `<div style=" 9784 | color: #6c757d; 9785 | font-size: 11px; 9786 | line-height: 1.3; 9787 | max-height: 60px; 9788 | overflow: hidden; 9789 | ">${data.rationale.substring(0, 120)}${data.rationale.length > 120 ? '...' : ''}</div>` : ''} 9790 | </td> 9791 | <td style=" 9792 | padding: 12px 8px; 9793 | text-align: center; 9794 | border-right: 1px solid #e9ecef; 9795 | vertical-align: top; 9796 | "> 9797 | <div style="font-weight: 600; color: #495057;">${totalReviews}</div> 9798 | <div style="font-size: 11px; color: #6c757d;">reviews</div> 9799 | </td> 9800 | <td style=" 9801 | padding: 12px 8px; 9802 | text-align: center; 9803 | border-right: 1px solid #e9ecef; 9804 | vertical-align: top; 9805 | "> 9806 | ${totalReviews > 0 ? ` 9807 | <div style=" 9808 | display: flex; 9809 | background: #f8f9fa; 9810 | border-radius: 4px; 9811 | overflow: hidden; 9812 | width: 60px; 9813 | height: 6px; 9814 | margin: 0 auto 4px auto; 9815 | border: 1px solid #e9ecef; 9816 | "> 9817 | ${positiveCount > 0 ? `<div style=" 9818 | background: #28a745; 9819 | width: ${positivePercent}%; 9820 | height: 100%; 9821 | " title="${positiveCount} positive"></div>` : ''} 9822 | ${neutralCount > 0 ? `<div style=" 9823 | background: #ffc107; 9824 | width: ${neutralPercent}%; 9825 | height: 100%; 9826 | " title="${neutralCount} neutral"></div>` : ''} 9827 | ${negativeCount > 0 ? `<div style=" 9828 | background: #dc3545; 9829 | width: ${negativePercent}%; 9830 | height: 100%; 9831 | " title="${negativeCount} negative"></div>` : ''} 9832 | </div> 9833 | <div style=" 9834 | font-size: 10px; 9835 | color: #6c757d; 9836 | line-height: 1.2; 9837 | "> 9838 | <span style="color: #28a745;">●</span>${positivePercent}% 9839 | ${neutralCount > 0 ? `<br><span style="color: #ffc107;">●</span>${neutralPercent}%` : ''} 9840 | ${negativeCount > 0 ? `<br><span style="color: #dc3545;">●</span>${negativePercent}%` : ''} 9841 | </div> 9842 | ` : '<span style="color: #6c757d; font-size: 11px;">No reviews</span>'} 9843 | </td> 9844 | <td style=" 9845 | padding: 12px 8px; 9846 | border-right: 1px solid #e9ecef; 9847 | vertical-align: top; 9848 | "> 9849 | ${topThemes.length > 0 ? topThemes.map(theme => { 9850 | const colors = getThemeColor(theme, data.reviews); 9851 | return ` 9852 | <span style=" 9853 | display: inline-block; 9854 | background: ${colors.background}; 9855 | color: ${colors.color}; 9856 | padding: 2px 6px; 9857 | border-radius: 8px; 9858 | font-size: 10px; 9859 | margin: 1px 2px 1px 0; 9860 | border: 1px solid ${colors.background === '#d1f2d1' ? '#c3e6cb' : colors.background === '#f8d7da' ? '#f5c6cb' : '#ffeaa7'}; 9861 | ">${theme}</span> 9862 | `; 9863 | }).join('') : '<span style="color: #6c757d; font-size: 11px;">No themes</span>'} 9864 | </td> 9865 | <td style=" 9866 | padding: 12px 8px; 9867 | text-align: center; 9868 | vertical-align: top; 9869 | "> 9870 | <div style="font-weight: 600; color: #495057;">${data.productLinks.length}</div> 9871 | <div style="font-size: 11px; color: #6c757d;">links</div> 9872 | ${(data.reviews.length > 0 || data.products.length > 0 || data.rationale || data.reviewSummary) ? ` 9873 | <button data-product-index="${index}" class="view-details-btn" style=" 9874 | background: #5b8def; 9875 | color: white; 9876 | border: none; 9877 | padding: 2px 6px; 9878 | border-radius: 3px; 9879 | font-size: 10px; 9880 | cursor: pointer; 9881 | margin-top: 4px; 9882 | ">View</button> 9883 | ` : data.productLinks.length > 0 ? ` 9884 | <button data-product-index="${index}" class="view-details-btn" style=" 9885 | background: #ffc107; 9886 | color: #212529; 9887 | border: none; 9888 | padding: 2px 6px; 9889 | border-radius: 3px; 9890 | font-size: 10px; 9891 | cursor: pointer; 9892 | margin-top: 4px; 9893 | ">Links</button> 9894 | ` : ''} 9895 | </td> 9896 | </tr> 9897 | `; 9898 | }); 9899 | 9900 | html += ` 9901 | </tbody> 9902 | </table> 9903 | </div> 9904 | </div> 9905 | `; 9906 | 9907 | // Add detailed results section (initially hidden) 9908 | html += ` 9909 | <div id="detailed-results" style="display: none;"> 9910 | <div id="detailed-content"></div> 9911 | </div> 9912 | `; 9913 | 9914 | resultsContainer.innerHTML = html; 9915 | 9916 | // Store results for detailed view 9917 | window.multiSearchResults = successfulResults; 9918 | 9919 | // Attach copy button event listener for multi-results 9920 | if (successfulResults.length > 0) { 9921 | const copyMultiBtn = document.getElementById('copy-multi-results-btn'); 9922 | if (copyMultiBtn) { 9923 | copyMultiBtn.addEventListener('click', () => copyMultiResultsToClipboard(results)); 9924 | // Add hover effects 9925 | copyMultiBtn.addEventListener('mouseenter', () => { 9926 | copyMultiBtn.style.background = '#f8f9fa'; 9927 | copyMultiBtn.style.borderColor = '#5b8def'; 9928 | }); 9929 | copyMultiBtn.addEventListener('mouseleave', () => { 9930 | copyMultiBtn.style.background = '#fff'; 9931 | copyMultiBtn.style.borderColor = '#dee2e6'; 9932 | }); 9933 | } 9934 | } 9935 | 9936 | // Add event listeners for product details links 9937 | const productNameLinks = document.querySelectorAll('.product-name-link'); 9938 | productNameLinks.forEach(link => { 9939 | link.addEventListener('click', function() { 9940 | const index = parseInt(this.getAttribute('data-product-index')); 9941 | showProductDetails(index); 9942 | }); 9943 | }); 9944 | 9945 | const viewDetailsBtns = document.querySelectorAll('.view-details-btn'); 9946 | viewDetailsBtns.forEach(btn => { 9947 | btn.addEventListener('click', function() { 9948 | const index = parseInt(this.getAttribute('data-product-index')); 9949 | showProductDetails(index); 9950 | }); 9951 | }); 9952 | } 9953 | 9954 | // Function to show detailed product information 9955 | window.showProductDetails = function(index) { 9956 | const detailedResults = document.getElementById('detailed-results'); 9957 | const detailedContent = document.getElementById('detailed-content'); 9958 | 9959 | if (!window.multiSearchResults || !detailedResults || !detailedContent) { 9960 | return; 9961 | } 9962 | 9963 | const result = window.multiSearchResults[index]; 9964 | if (!result) { 9965 | return; 9966 | } 9967 | 9968 | const productName = result.query; 9969 | 9970 | // Display the single product result using existing function 9971 | const tempContainer = document.createElement('div'); 9972 | tempContainer.innerHTML = ''; 9973 | 9974 | // Use the existing displayResults function but capture its output 9975 | const originalContainer = document.getElementById('results-container'); 9976 | const tempId = 'temp-results-container'; 9977 | tempContainer.id = tempId; 9978 | document.body.appendChild(tempContainer); 9979 | 9980 | // Temporarily replace the results container 9981 | const originalGetElementById = document.getElementById; 9982 | document.getElementById = function(id) { 9983 | if (id === 'results-container') { 9984 | return tempContainer; 9985 | } 9986 | return originalGetElementById.call(document, id); 9987 | }; 9988 | 9989 | displayResults(result.data, result.query, null, { suppressHeader: true }); 9990 | 9991 | // Restore original function 9992 | document.getElementById = originalGetElementById; 9993 | 9994 | // Move the content to detailed view 9995 | detailedContent.innerHTML = ` 9996 | <div style=" 9997 | background: #f8f9fa; 9998 | padding: 10px 14px; 9999 | margin-bottom: 16px; 10000 | border-left: 4px solid #5b8def; 10001 | display: flex; 10002 | justify-content: space-between; 10003 | align-items: center; 10004 | gap: 12px; 10005 | flex-wrap: wrap; 10006 | "> 10007 | <div style="display: flex; flex-direction: column; gap: 4px;"> 10008 | <h3 style="margin: 0; color: #495057;">Results for "${productName}"</h3> 10009 | <div style="font-size: 12px; color: #6c757d;"> 10010 | ${result.data?.reviews?.length || 0} reviews • 10011 | ${result.data?.products?.length || 0} products • 10012 | ${(result.data?.productLinks?.length || 0)} citation links 10013 | • ${result.data?.summary?.review_themes?.length || 0} themes 10014 | </div> 10015 | </div> 10016 | <button class="close-details-btn" style=" 10017 | background: #6c757d; 10018 | color: white; 10019 | border: none; 10020 | padding: 4px 10px; 10021 | border-radius: 3px; 10022 | font-size: 12px; 10023 | cursor: pointer; 10024 | ">Close</button> 10025 | </div> 10026 | ${tempContainer.innerHTML} 10027 | `; 10028 | 10029 | // Clean up 10030 | document.body.removeChild(tempContainer); 10031 | 10032 | // Add event listener for close button 10033 | const closeBtn = detailedContent.querySelector('.close-details-btn'); 10034 | if (closeBtn) { 10035 | closeBtn.addEventListener('click', function() { 10036 | detailedResults.style.display = 'none'; 10037 | }); 10038 | } 10039 | 10040 | // Show detailed results 10041 | detailedResults.style.display = 'block'; 10042 | detailedResults.scrollIntoView({ behavior: 'smooth' }); 10043 | }; 10044 | 10045 | function displayError(message) { 10046 | const resultsContainer = document.getElementById('results-container'); 10047 | if (!resultsContainer) { 10048 | alert(`Search Error: ${message}`); 10049 | return; 10050 | } 10051 | 10052 | resultsContainer.innerHTML = ` 10053 | <div style=" 10054 | background: #fef2f2; 10055 | color: #991b1b; 10056 | padding: 15px; 10057 | border-radius: 8px; 10058 | border-left: 4px solid #ef4444; 10059 | margin: 20px 0; 10060 | "> 10061 | <h3>Search Error</h3> 10062 | <p>${message}</p> 10063 | <p>Please check your token and try again.</p> 10064 | </div> 10065 | `; 10066 | } 10067 | 10068 | // --- Patched, context-aware sidebar filters (last definition wins) --- 10069 | function _activeTab() { 10070 | const reportsContainer = document.getElementById('reports-container'); 10071 | if (reportsContainer && reportsContainer.style.display !== 'none') { 10072 | return 'reports'; 10073 | } 10074 | const historyContainer = document.getElementById('history-container'); 10075 | if (historyContainer && historyContainer.style.display !== 'none') { 10076 | return 'history'; 10077 | } 10078 | return 'search'; 10079 | } 10080 | 10081 | function _applyToAnalysis({ projectId = '', tagId = '' }) { 10082 | const projectSelects = document.querySelectorAll('select#analysis-project-filter'); 10083 | if (projectSelects.length > 0) { 10084 | projectSelects.forEach(select => { 10085 | select.value = projectId || ''; 10086 | }); 10087 | } 10088 | 10089 | if (tagId) { 10090 | const tagChecks = document.querySelectorAll('.analysis-tag-checkbox'); 10091 | const targetCheckbox = Array.from(tagChecks).find(cb => cb.value === tagId); 10092 | if (targetCheckbox) { 10093 | // Toggle the tag - if it's checked, uncheck it; if unchecked, check it 10094 | targetCheckbox.checked = !targetCheckbox.checked; 10095 | } 10096 | } 10097 | 10098 | // Apply the filters to actually trigger the filtering and update global state 10099 | if (typeof applyAnalysisFilters === 'function') { 10100 | applyAnalysisFilters(); 10101 | } else { 10102 | if (typeof updateAnalysisFilterSummary === 'function') { 10103 | updateAnalysisFilterSummary(); 10104 | } 10105 | if (typeof updateAnalysisFilterChips === 'function') { 10106 | updateAnalysisFilterChips(); 10107 | } 10108 | } 10109 | 10110 | if (projectId || tagId) { 10111 | const syncOptions = { shouldSwitch: false }; 10112 | if (projectId) syncOptions.projectId = projectId; 10113 | if (tagId) { 10114 | syncOptions.tagId = tagId; 10115 | syncOptions.tags = [tagId]; 10116 | } 10117 | _applyToHistory(syncOptions); 10118 | } 10119 | } 10120 | 10121 | function _applyToHistory({ projectId, tagId, tags, market, shouldSwitch = true } = {}) { 10122 | const previousFilters = currentFilters || { text: '', rawText: '', project: '', tags: [], market: 'all', isActive: false }; 10123 | 10124 | const filterTextInput = document.getElementById('filter-text'); 10125 | const rawFromDom = filterTextInput ? filterTextInput.value.trim() : ''; 10126 | const effectiveRawText = rawFromDom || previousFilters.rawText || ''; 10127 | 10128 | let nextProject = previousFilters.project || ''; 10129 | if (typeof projectId === 'string') { 10130 | nextProject = projectId; 10131 | } 10132 | 10133 | let nextTags = Array.isArray(previousFilters.tags) ? [...previousFilters.tags] : []; 10134 | if (Array.isArray(tags)) { 10135 | nextTags = Array.from(new Set( 10136 | tags.filter(tag => typeof tag === 'string' && tag.length > 0) 10137 | )); 10138 | } 10139 | 10140 | if (typeof tagId === 'string' && tagId.length > 0) { 10141 | // For sidebar tag selection, preserve existing tags and add/toggle the new one 10142 | if (nextTags.includes(tagId)) { 10143 | // If tag is already selected, remove it (toggle off) 10144 | nextTags = nextTags.filter(tag => tag !== tagId); 10145 | } else { 10146 | // Add the tag to existing selection 10147 | nextTags = [...nextTags, tagId]; 10148 | } 10149 | // Don't clear project when selecting a tag from sidebar 10150 | } 10151 | 10152 | // Only clear tags when explicitly setting a project with empty tags array 10153 | if (typeof projectId === 'string' && Array.isArray(tags) && tags.length === 0) { 10154 | nextTags = []; 10155 | } 10156 | 10157 | const filterMarketSelect = document.getElementById('filter-market'); 10158 | let nextMarket = previousFilters.market || 'all'; 10159 | const domMarket = filterMarketSelect ? filterMarketSelect.value : ''; 10160 | 10161 | if (typeof market === 'string') { 10162 | nextMarket = market; 10163 | } else if (domMarket) { 10164 | nextMarket = domMarket; 10165 | } 10166 | 10167 | const normalizedText = effectiveRawText.toLowerCase(); 10168 | 10169 | currentFilters = { 10170 | text: normalizedText, 10171 | rawText: effectiveRawText, 10172 | project: nextProject, 10173 | tags: nextTags, 10174 | market: nextMarket, 10175 | isActive: Boolean(normalizedText || nextProject || nextTags.length || (nextMarket && nextMarket !== 'all')) 10176 | }; 10177 | 10178 | if (filterMarketSelect) { 10179 | filterMarketSelect.value = nextMarket || 'all'; 10180 | } 10181 | 10182 | const historyIsActive = _activeTab() === 'history'; 10183 | const shouldRender = shouldSwitch || historyIsActive; 10184 | 10185 | if (!shouldRender) { 10186 | return; 10187 | } 10188 | 10189 | if (shouldSwitch && !historyIsActive) { 10190 | switchTab('history'); 10191 | } 10192 | 10193 | if (typeof loadHistory === 'function') { 10194 | loadHistory(); 10195 | } 10196 | 10197 | // Sync filters to Analysis tab 10198 | if (typeof syncAnalysisFiltersWithHistoryFilters === 'function') { 10199 | syncAnalysisFiltersWithHistoryFilters(); 10200 | } 10201 | 10202 | ['history-content', 'history-welcome-state', 'history-list'].forEach(id => { 10203 | const el = document.getElementById(id); 10204 | if (el) { 10205 | el.style.visibility = 'visible'; 10206 | } 10207 | }); 10208 | 10209 | const projectSel = document.getElementById('filter-project'); 10210 | if (projectSel) { 10211 | projectSel.value = currentFilters.project || ''; 10212 | } 10213 | 10214 | const tagChecks = document.querySelectorAll('#filter-tags input[type="checkbox"]'); 10215 | if (tagChecks.length > 0) { 10216 | const activeTags = new Set(currentFilters.tags || []); 10217 | tagChecks.forEach(cb => { 10218 | cb.checked = activeTags.has(cb.value); 10219 | }); 10220 | } 10221 | 10222 | const updatedFilterText = document.getElementById('filter-text'); 10223 | if (updatedFilterText) { 10224 | updatedFilterText.value = currentFilters.rawText || ''; 10225 | } 10226 | 10227 | if (typeof updateFilterSummary === 'function') { 10228 | updateFilterSummary(); 10229 | } 10230 | if (typeof updateFilterChips === 'function') { 10231 | updateFilterChips(); 10232 | } 10233 | 10234 | if (typeof loadSearchHistory === 'function' && typeof applyAdvancedFilters === 'function') { 10235 | const history = loadSearchHistory(); 10236 | const filtered = applyAdvancedFilters(history); 10237 | if (typeof renderHistoryList === 'function') { 10238 | renderHistoryList(filtered); 10239 | } 10240 | } 10241 | } 10242 | 10243 | window.filterByProject = filterByProject; 10244 | window.filterByTag = filterByTag; 10245 | 10246 | // Listen for messages from popup 10247 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 10248 | if (message.action === 'ping') { 10249 | sendResponse({ status: 'ready' }); 10250 | return true; 10251 | } 10252 | 10253 | if (message.action === 'openSearch') { 10254 | createModal(); 10255 | sendResponse({ status: 'opened' }); 10256 | return true; 10257 | } 10258 | }); 10259 | } 10260 | 10261 | })(); 10262 | ```