function safeInlineJSON(data: unknown): string { return JSON.stringify(data) .replace(//g, "\\u003e") .replace(/&/g, "\\u0026") .replace(/\u2028/g, "\\u2028") .replace(/\u2029/g, "\\u2029"); } function buildProviderButtons( available: { perplexity: boolean; exa: boolean; gemini: boolean }, selected: string, hasInitialQueries: boolean, ): string { const providers = [ { value: "perplexity", label: "Perplexity", available: available.perplexity }, { value: "exa", label: "Exa", available: available.exa }, { value: "gemini", label: "Gemini", available: available.gemini }, ]; return providers .filter(p => p.available) .map((p) => { const isDefault = p.value === selected; const state = isDefault && hasInitialQueries ? "loading" : "idle"; const classes = ["provider-btn", state, isDefault ? "is-default" : ""].filter(Boolean).join(" "); const disabled = state === "loading" ? " disabled" : ""; return ``; }) .join(""); } export function generateCuratorPage( queries: string[], sessionToken: string, timeout: number, availableProviders: { perplexity: boolean; exa: boolean; gemini: boolean }, defaultProvider: string, summaryModels: Array<{ value: string; label: string }>, defaultSummaryModel: string | null, ): string { const providerButtonsHtml = buildProviderButtons(availableProviders, defaultProvider, queries.length > 0); const inlineData = safeInlineJSON({ queries, sessionToken, timeout, defaultProvider, summaryModels, defaultSummaryModel, availableProviders }); return ` Curate Search Results `; } const CSS = ` *,*::before,*::after{box-sizing:border-box;margin:0;padding:0} :root { --bg: #18181e; --bg-card: #1e1e24; --bg-elevated: #252530; --bg-hover: #2b2b37; --fg: #e0e0e0; --fg-muted: #909098; --fg-dim: #606068; --accent: #8abeb7; --accent-hover: #9dcec7; --accent-muted: rgba(138, 190, 183, 0.15); --accent-subtle: rgba(138, 190, 183, 0.08); --border: #2a2a34; --border-muted: #353540; --border-checked: #8abeb7; --check-bg: #8abeb7; --btn-primary: #8abeb7; --btn-primary-hover: #9dcec7; --btn-primary-fg: #18181e; --btn-secondary: #252530; --btn-secondary-hover: #2b2b37; --timer-bg: #252530; --timer-fg: #909098; --timer-warn-bg: rgba(240, 198, 116, 0.15); --timer-warn-fg: #f0c674; --timer-urgent-bg: rgba(204, 102, 102, 0.15); --timer-urgent-fg: #cc6666; --overlay-bg: rgba(24, 24, 30, 0.92); --success: #b5bd68; --warning: #f0c674; --font: 'Outfit', system-ui, -apple-system, sans-serif; --font-display: 'Instrument Serif', Georgia, 'Times New Roman', serif; --font-mono: 'SF Mono', Consolas, monospace; --radius: 10px; --radius-sm: 6px; } @media (prefers-color-scheme: light) { :root { --bg: #f5f5f7; --bg-card: #ffffff; --bg-elevated: #eeeef0; --bg-hover: #e4e4e8; --fg: #1a1a1e; --fg-muted: #6c6c74; --fg-dim: #9a9aa2; --accent: #5f8787; --accent-hover: #4a7272; --accent-muted: rgba(95, 135, 135, 0.12); --accent-subtle: rgba(95, 135, 135, 0.06); --border: #dcdce0; --border-muted: #c8c8d0; --border-checked: #5f8787; --check-bg: #5f8787; --btn-primary: #5f8787; --btn-primary-hover: #4a7272; --btn-primary-fg: #ffffff; --btn-secondary: #e4e4e8; --btn-secondary-hover: #d4d4d8; --timer-bg: #e4e4e8; --timer-fg: #6c6c74; --timer-warn-bg: rgba(217, 119, 6, 0.10); --timer-warn-fg: #92400e; --timer-urgent-bg: rgba(175, 95, 95, 0.10); --timer-urgent-fg: #991b1b; --overlay-bg: rgba(255, 255, 255, 0.92); --success: #4d7c0f; --warning: #b45309; } } body { font-family: var(--font); background: var(--bg); background-image: radial-gradient(ellipse at 50% 0%, var(--accent-muted) 0%, transparent 60%); color: var(--fg); line-height: 1.5; min-height: 100dvh; padding-bottom: 72px; } .timer-badge { position: fixed; top: 20px; right: 24px; z-index: 50; font-family: var(--font); font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; padding: 5px 14px; border-radius: 999px; background: var(--bg-elevated); color: var(--timer-fg); border: 1px solid var(--border); transition: background 0.3s, color 0.3s, border-color 0.3s, opacity 0.3s; box-shadow: 0 2px 8px rgba(0,0,0,0.2); cursor: pointer; user-select: none; opacity: 0.5; } .timer-badge:hover { opacity: 1; } .timer-badge.active { opacity: 1; } .timer-badge.warn { opacity: 1; background: var(--timer-warn-bg); color: var(--timer-warn-fg); border-color: color-mix(in srgb, var(--timer-warn-fg) 30%, transparent); } .timer-badge.urgent { opacity: 1; background: var(--timer-urgent-bg); color: var(--timer-urgent-fg); border-color: color-mix(in srgb, var(--timer-urgent-fg) 30%, transparent); } .timer-adjust { position: fixed; top: 20px; right: 24px; z-index: 51; display: none; align-items: center; gap: 6px; padding: 4px 6px 4px 12px; background: var(--bg-elevated); border: 1px solid var(--accent); border-radius: 999px; box-shadow: 0 2px 12px rgba(0,0,0,0.3); } .timer-adjust.visible { display: flex; } .timer-adjust input { width: 48px; background: transparent; border: none; outline: none; color: var(--fg); font-family: var(--font); font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; text-align: center; } .timer-adjust-label { font-size: 11px; color: var(--fg-dim); } .timer-adjust-btn { font-family: var(--font); font-size: 11px; font-weight: 600; padding: 3px 10px; border-radius: 999px; border: none; background: var(--accent); color: var(--btn-primary-fg); cursor: pointer; } .timer-adjust-btn:hover { background: var(--accent-hover); } main { max-width: 640px; margin: 0 auto; padding: 56px 24px 16px; } .hero { margin-bottom: 28px; } .hero-kicker { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.1em; color: var(--accent); margin-bottom: 8px; } .hero-title { font-family: var(--font-display); font-size: 40px; font-weight: 400; font-style: italic; letter-spacing: -0.01em; line-height: 1.1; color: var(--fg); margin-bottom: 10px; text-wrap: balance; } .hero-desc { font-size: 14px; color: var(--fg-muted); line-height: 1.5; margin-bottom: 12px; max-width: 480px; } .hero-meta { display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--fg-dim); } .hero-meta-sep { width: 3px; height: 3px; border-radius: 50%; background: var(--fg-dim); flex-shrink: 0; } #hero-status:empty + .hero-meta-sep { display: none; } .provider-buttons { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; } .summary-model-controls { display: flex; align-items: center; gap: 6px; min-width: 0; flex-shrink: 0; } .summary-model-dropdown { font-family: var(--font); font-size: 12px; font-weight: 600; color: var(--fg); background: var(--bg-elevated); border: 1px solid var(--border-muted); border-radius: var(--radius-sm); padding: 4px 8px; max-width: 220px; } .summary-model-dropdown:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent); } .summary-model-dropdown:disabled { opacity: 0.65; cursor: default; } .provider-btn { font-family: var(--font); font-size: 12px; font-weight: 600; padding: 3px 10px; border-radius: 999px; border: 1px solid var(--border-muted); background: transparent; color: var(--fg-muted); cursor: pointer; transition: border-color 0.12s, background 0.12s, color 0.12s, opacity 0.12s; } .provider-btn.idle:hover { color: var(--fg); border-color: var(--accent); } .provider-btn.loading { background: var(--accent-subtle); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 35%, var(--border-muted)); cursor: default; pointer-events: none; opacity: 0.85; } .provider-btn.loading::after { content: " …"; animation: provider-pulse 1.2s ease-in-out infinite; } .provider-btn.searched { background: var(--btn-secondary); color: var(--fg); border-color: var(--border-muted); } .provider-btn.searched::after { content: " ✓"; color: var(--success); } .provider-btn.is-default { box-shadow: inset 0 -2px 0 0 var(--accent); border-color: var(--accent); } .provider-btn:disabled { cursor: default; opacity: 0.5; } @keyframes provider-pulse { 0%, 100% { opacity: 0.4; } 50% { opacity: 1; } } #result-cards { display: flex; flex-direction: column; gap: 8px; } .send-raw-row { display: flex; justify-content: flex-end; padding: 4px 0; } .send-raw-row.hidden { display: none; } .result-loading { border: 1px solid var(--border); border-radius: var(--radius); background: color-mix(in srgb, var(--bg-card) 86%, var(--accent-subtle)); overflow: hidden; box-shadow: 0 1px 2px rgba(0,0,0,0.06); } .result-loading-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 12px 14px 10px; border-bottom: 1px solid var(--border); } .result-loading-title { font-size: 12px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--accent); } .result-loading-sub { font-size: 12px; color: var(--fg-dim); font-variant-numeric: tabular-nums; } .result-loading-grid { display: grid; gap: 10px; padding: 12px 14px 14px; } .loading-card { border: 1px solid color-mix(in srgb, var(--border-muted) 80%, var(--accent-subtle)); border-radius: var(--radius-sm); background: var(--bg-card); overflow: hidden; position: relative; } .loading-card::after { content: ""; position: absolute; inset: 0; background: linear-gradient(105deg, transparent 10%, color-mix(in srgb, var(--accent) 18%, transparent) 45%, transparent 75%); transform: translateX(-130%); animation: loading-sweep 2s ease-in-out infinite; pointer-events: none; } .loading-card-row { height: 10px; border-radius: 999px; margin: 10px 12px; background: color-mix(in srgb, var(--fg-dim) 35%, transparent); } .loading-card-row.short { width: 35%; } .loading-card-row.mid { width: 58%; } .loading-card-row.long { width: 78%; } .result-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; transition: border-color 0.12s; box-shadow: 0 1px 2px rgba(0,0,0,0.06); } .result-card.checked { border-color: var(--border-checked); } .result-card.searching { opacity: 1; border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); background: linear-gradient(180deg, color-mix(in srgb, var(--accent-subtle) 70%, var(--bg-card)) 0%, var(--bg-card) 100%); position: relative; } .result-card.searching::after { content: ""; position: absolute; inset: 0; background: linear-gradient(110deg, transparent 20%, color-mix(in srgb, var(--accent) 14%, transparent) 50%, transparent 80%); transform: translateX(-130%); animation: loading-sweep 2.2s ease-in-out infinite; pointer-events: none; } .result-card.searching .result-card-header { cursor: default; } .result-card.searching .result-card-header:hover { background: transparent; } .result-card.error { border-color: var(--timer-urgent-fg); } .result-card-header { display: flex; align-items: flex-start; gap: 12px; padding: 14px 16px; cursor: pointer; user-select: none; transition: background 0.12s; } .result-card-header:hover { background: var(--bg-hover); } .result-card-header input[type="checkbox"] { appearance: none; width: 16px; height: 16px; min-width: 16px; border: 1.5px solid var(--border-muted); border-radius: 4px; margin-top: 2px; cursor: pointer; transition: background 0.12s, border-color 0.12s; display: grid; place-content: center; } .result-card-header input[type="checkbox"]:checked { background: var(--check-bg); border-color: var(--check-bg); } .result-card-header input[type="checkbox"]:checked::after { content: ""; width: 9px; height: 6px; border-left: 2px solid var(--btn-primary-fg); border-bottom: 2px solid var(--btn-primary-fg); transform: rotate(-45deg); margin-top: -1px; } .result-card-info { flex: 1; min-width: 0; } .result-card-query-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 2px; } .result-card-query { font-size: 14px; font-weight: 600; color: var(--fg); } .provider-tag { display: inline-flex; align-items: center; padding: 1px 7px; border-radius: 999px; font-size: 10px; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase; border: 1px solid transparent; } .provider-tag.provider-exa { color: #8dd3ff; background: rgba(141, 211, 255, 0.14); border-color: rgba(141, 211, 255, 0.3); } .provider-tag.provider-perplexity { color: #cba6f7; background: rgba(203, 166, 247, 0.14); border-color: rgba(203, 166, 247, 0.3); } .provider-tag.provider-gemini { color: #f5c27b; background: rgba(245, 194, 123, 0.14); border-color: rgba(245, 194, 123, 0.3); } .provider-tag.provider-unknown { color: var(--fg-muted); background: var(--bg-elevated); border-color: var(--border-muted); } .result-card-meta { font-size: 12px; color: var(--fg-dim); } .result-card-preview { font-size: 12.5px; color: var(--fg-muted); margin-top: 6px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.45; } .result-card-expand { color: var(--fg-dim); font-size: 11px; margin-top: 2px; flex-shrink: 0; padding-top: 3px; transition: color 0.12s; } .result-card-header:hover .result-card-expand { color: var(--fg-muted); } .result-card-body { display: none; border-top: 1px solid var(--border); } .result-card-body.open { display: block; } .result-card-answer { padding: 14px 16px; font-size: 13.5px; color: var(--fg-muted); line-height: 1.6; max-height: 400px; overflow-y: auto; } .result-card-answer h1, .result-card-answer h2, .result-card-answer h3, .result-card-answer h4 { color: var(--fg); font-family: var(--font); font-weight: 600; margin: 16px 0 6px; line-height: 1.3; } .result-card-answer h1 { font-size: 16px; } .result-card-answer h2 { font-size: 14.5px; } .result-card-answer h3 { font-size: 13.5px; } .result-card-answer h4 { font-size: 13px; color: var(--fg-muted); } .result-card-answer p { margin: 0 0 10px; } .result-card-answer p:last-child { margin-bottom: 0; } .result-card-answer strong { color: var(--fg); font-weight: 600; } .result-card-answer a { color: var(--accent); text-decoration: none; } .result-card-answer a:hover { text-decoration: underline; } .result-card-answer ul, .result-card-answer ol { margin: 6px 0 10px; padding-left: 20px; } .result-card-answer li { margin-bottom: 4px; } .result-card-answer li::marker { color: var(--fg-dim); } .result-card-answer code { font-family: var(--font-mono); font-size: 12px; padding: 1px 5px; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 3px; color: var(--fg); } .result-card-answer pre { margin: 8px 0 12px; padding: 12px 14px; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-sm); overflow-x: auto; line-height: 1.45; } .result-card-answer pre code { padding: 0; background: none; border: none; font-size: 12px; color: var(--fg-muted); } .result-card-answer blockquote { margin: 8px 0; padding: 8px 14px; border-left: 3px solid var(--accent); color: var(--fg-dim); background: var(--accent-subtle); border-radius: 0 var(--radius-sm) var(--radius-sm) 0; } .result-card-answer table { width: 100%; border-collapse: collapse; margin: 8px 0 12px; font-size: 12.5px; } .result-card-answer th, .result-card-answer td { padding: 6px 10px; border: 1px solid var(--border); text-align: left; } .result-card-answer th { background: var(--bg-elevated); color: var(--fg); font-weight: 600; font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.03em; } .result-card-answer hr { border: none; border-top: 1px solid var(--border); margin: 14px 0; } .result-card-sources { padding: 10px 16px 14px; border-top: 1px solid var(--border); } .result-card-sources-title { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--fg-dim); margin-bottom: 6px; } .source-link { display: block; padding: 4px 0; font-size: 12.5px; color: var(--fg-muted); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; transition: color 0.12s; } .source-link:hover { color: var(--accent); } .source-domain { color: var(--fg-dim); margin-left: 6px; } .result-card-error-msg { padding: 12px 16px; font-size: 13px; color: var(--timer-urgent-fg); } .card-alt-providers { display: flex; align-items: center; gap: 6px; padding: 4px 16px 8px 42px; font-size: 11px; color: var(--fg-dim); } .card-alt-chip { font-family: var(--font); font-size: 10px; font-weight: 600; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--border-muted); background: transparent; color: var(--fg-muted); cursor: pointer; transition: border-color 0.12s, color 0.12s, background 0.12s; } .card-alt-chip:hover:not(:disabled) { color: var(--accent); border-color: var(--accent); } .card-alt-chip:disabled { opacity: 0.4; cursor: default; } .card-alt-chip.loading { opacity: 0.6; pointer-events: none; } .card-alt-chip.loading::after { content: " …"; } .searching-dots::after { content: ""; animation: dots 1.5s steps(4, end) infinite; } @keyframes dots { 0% { content: ""; } 25% { content: "."; } 50% { content: ".."; } 75% { content: "..."; } } @keyframes loading-sweep { 0% { transform: translateX(-130%); } 100% { transform: translateX(130%); } } @keyframes summary-pulse { 0%, 100% { transform: scale(0.9); box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 35%, transparent); } 50% { transform: scale(1.15); box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent) 0%, transparent); } } @keyframes summary-sweep { 0% { transform: translateX(-100%); } 100% { transform: translateX(120%); } } @keyframes summary-panel-sweep { 0% { transform: translateX(-115%); } 100% { transform: translateX(115%); } } .add-search { display: flex; align-items: center; gap: 10px; margin-top: 12px; padding: 11px 14px; border: 1px dashed var(--border); border-radius: var(--radius); cursor: text; transition: border-color 0.15s, background 0.15s; } .add-search:hover { border-color: var(--border-muted); background: var(--accent-subtle); } .add-search:focus-within { border-color: var(--accent); border-style: solid; background: var(--accent-subtle); } .add-search-icon { color: var(--fg-dim); font-size: 16px; font-weight: 300; line-height: 1; flex-shrink: 0; transition: color 0.15s; } .add-search:focus-within .add-search-icon { color: var(--accent); } .add-search input { flex: 1; background: transparent; border: none; outline: none; color: var(--fg); font-family: var(--font); font-size: 13.5px; font-weight: 500; } .add-search input::placeholder { color: var(--fg-dim); font-weight: 400; } .add-search-wand { flex-shrink: 0; width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; border: 1px solid var(--border-muted); border-radius: 6px; background: transparent; color: var(--fg-dim); font-size: 14px; cursor: pointer; transition: color 0.12s, border-color 0.12s, background 0.12s; } .add-search-wand:hover:not(:disabled) { color: var(--accent); border-color: var(--accent); background: var(--accent-subtle); } .add-search-wand:disabled { opacity: 0.3; cursor: default; } .add-search-wand.rewriting { pointer-events: none; animation: wand-spin 0.8s linear infinite; } @keyframes wand-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .summary-panel { margin-top: 14px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-card); padding: 14px; display: flex; flex-direction: column; gap: 10px; } .summary-panel.hidden { display: none; } .summary-header { display: flex; flex-direction: column; gap: 2px; } .summary-header-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; } .summary-title { font-size: 14px; font-weight: 600; color: var(--fg); } .summary-subtitle { font-size: 12px; color: var(--fg-dim); } .summary-generating { position: relative; isolation: isolate; overflow: hidden; border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--border)); border-radius: var(--radius-sm); background: linear-gradient(130deg, color-mix(in srgb, var(--accent-subtle) 78%, transparent) 0%, var(--bg-elevated) 70%); padding: 12px; display: flex; flex-direction: column; gap: 10px; } .summary-generating::before { content: ""; position: absolute; inset: 0; background: linear-gradient(110deg, transparent 0%, color-mix(in srgb, var(--accent) 16%, transparent) 50%, transparent 100%); transform: translateX(-115%); animation: summary-panel-sweep 2.4s ease-in-out infinite; pointer-events: none; } .summary-generating > * { position: relative; z-index: 1; } .summary-generating.hidden { display: none; } .summary-generating-head { display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 600; color: var(--accent-hover); } .summary-generating-orb { width: 10px; height: 10px; border-radius: 999px; background: var(--accent); box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 35%, transparent); animation: summary-pulse 1.1s ease-in-out infinite; } .summary-generating-bars { display: grid; gap: 6px; } .summary-generating-bar { position: relative; display: block; height: 8px; border-radius: 999px; background: color-mix(in srgb, var(--bg) 65%, var(--bg-elevated)); overflow: hidden; transition: width 220ms ease; } .summary-generating-bar::after { content: ""; position: absolute; inset: 0; transform: translateX(-100%); background: linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--accent) 45%, transparent) 50%, transparent 100%); animation: summary-sweep 1.6s ease-in-out infinite; } .summary-generating-bar.b1 { width: 86%; } .summary-generating-bar.b2 { width: 68%; } .summary-generating-bar.b3 { width: 74%; } .summary-generating[data-phase="1"] .summary-generating-bar.b1 { width: 72%; } .summary-generating[data-phase="1"] .summary-generating-bar.b2 { width: 82%; } .summary-generating[data-phase="1"] .summary-generating-bar.b3 { width: 60%; } .summary-generating[data-phase="2"] .summary-generating-bar.b1 { width: 64%; } .summary-generating[data-phase="2"] .summary-generating-bar.b2 { width: 71%; } .summary-generating[data-phase="2"] .summary-generating-bar.b3 { width: 90%; } .summary-generating-bar.b2::after { animation-delay: 0.15s; } .summary-generating-bar.b3::after { animation-delay: 0.3s; } .summary-input { width: 100%; min-height: 180px; resize: vertical; border: 1px solid var(--border-muted); border-radius: var(--radius-sm); padding: 10px 12px; font-family: var(--font); font-size: 13px; line-height: 1.5; color: var(--fg); background: var(--bg-elevated); outline: none; } .summary-input.hidden { display: none; } .summary-input:focus { border-color: var(--accent); } .summary-feedback-row { display: flex; align-items: center; gap: 8px; margin-top: 6px; } .summary-feedback { flex: 1; height: 32px; border: 1px solid var(--border-muted); border-radius: var(--radius-sm); padding: 4px 10px; font-family: var(--font); font-size: 12px; color: var(--fg); background: var(--bg-elevated); outline: none; } .summary-feedback:focus { border-color: var(--accent); } .summary-feedback::placeholder { color: var(--fg-muted); } .summary-actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; } .action-bar { position: fixed; bottom: 0; left: 0; right: 0; z-index: 10; display: flex; align-items: center; justify-content: space-between; padding: 12px 24px; background: color-mix(in srgb, var(--bg) 90%, transparent); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border-top: 1px solid var(--border); } .action-shortcuts { display: flex; align-items: center; gap: 16px; } .shortcut { display: flex; align-items: center; gap: 5px; font-size: 11px; color: var(--fg-dim); } .shortcut kbd { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px; padding: 0 4px; font-family: var(--font-mono); font-size: 10px; font-weight: 500; background: var(--bg-elevated); border: 1px solid var(--border-muted); border-radius: 3px; color: var(--fg-muted); } .action-buttons { display: flex; gap: 8px; } .btn { font-family: var(--font); font-size: 13px; font-weight: 500; padding: 7px 16px; border: none; border-radius: var(--radius-sm); cursor: pointer; transition: background 0.12s, opacity 0.12s; } .btn:disabled { opacity: 0.35; cursor: default; } .btn-submit { background: var(--btn-primary); color: var(--btn-primary-fg); } .btn-submit:hover:not(:disabled) { background: var(--btn-primary-hover); } .btn-secondary { background: var(--btn-secondary); color: var(--fg-muted); border: 1px solid var(--border); } .btn-secondary:hover:not(:disabled) { background: var(--btn-secondary-hover); color: var(--fg); } .success-overlay { position: fixed; inset: 0; z-index: 200; background: var(--overlay-bg); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; transition: opacity 200ms; } .success-overlay.hidden { display: flex !important; opacity: 0; pointer-events: none; } .success-icon { width: 56px; height: 56px; border-radius: 50%; border: 2px solid var(--success); display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: 700; color: var(--success); } .success-overlay p { margin: 0; font-size: 13px; font-weight: 600; color: var(--success); letter-spacing: 0.06em; text-transform: uppercase; } .expired-overlay { position: fixed; inset: 0; background: var(--overlay-bg); display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity 400ms; pointer-events: none; z-index: 200; } .expired-overlay.visible { opacity: 1; pointer-events: auto; } .expired-overlay.hidden { display: flex !important; opacity: 0; pointer-events: none; } .expired-content { text-align: center; max-width: 480px; padding: 48px 56px; background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; } .expired-overlay.visible .expired-content { animation: slide-up 400ms ease-out; } @keyframes slide-up { from { transform: translateY(20px); } to { transform: translateY(0); } } .expired-icon { width: 72px; height: 72px; border-radius: 50%; border: 2px solid var(--warning); display: flex; align-items: center; justify-content: center; font-size: 32px; font-weight: bold; color: var(--warning); margin: 0 auto 24px; } .expired-content h2 { color: var(--fg); margin: 0 0 16px; font-size: 22px; font-weight: 600; } .expired-content p { color: var(--fg-muted); margin: 0 0 24px; font-size: 14px; line-height: 1.6; } .expired-countdown { font-size: 13px; color: var(--fg-dim); font-variant-numeric: tabular-nums; } .expired-countdown span { color: var(--warning); font-weight: 600; } .preview-modal { position: fixed; inset: 0; z-index: 250; background: var(--overlay-bg); display: flex; align-items: center; justify-content: center; animation: fade-in 150ms ease-out; } .preview-modal.hidden { display: none; } @keyframes fade-in { from { opacity: 0; } to { opacity: 1; } } .preview-modal-inner { width: min(720px, calc(100% - 48px)); max-height: calc(100vh - 80px); background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; display: flex; flex-direction: column; animation: slide-up 200ms ease-out; } .preview-modal-header { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; border-bottom: 1px solid var(--border); flex-shrink: 0; } .preview-modal-title { font-size: 14px; font-weight: 600; color: var(--fg); margin: 0; } .preview-modal-close { background: none; border: none; cursor: pointer; font-size: 22px; line-height: 1; color: var(--fg-muted); padding: 0 4px; transition: color 0.12s; } .preview-modal-close:hover { color: var(--fg); } .preview-modal-body { position: relative; padding: 24px 28px; overflow-y: auto; font-size: 14px; line-height: 1.7; color: var(--fg); } .preview-modal-body h1 { font-size: 20px; font-weight: 600; margin: 1.2em 0 0.5em; color: var(--fg); } .preview-modal-body h2 { font-size: 16px; font-weight: 600; margin: 1.2em 0 0.4em; color: var(--fg); } .preview-modal-body h3 { font-size: 14px; font-weight: 600; margin: 1em 0 0.3em; color: var(--fg); } .preview-modal-body p { margin: 0.6em 0; } .preview-modal-body a { color: var(--accent); } .preview-modal-body pre { background: var(--bg-elevated); padding: 14px; border-radius: var(--radius-sm); overflow-x: auto; } .preview-modal-body code { font-size: 0.9em; } .preview-modal-body blockquote { border-left: 3px solid var(--border); padding-left: 14px; color: var(--fg-muted); margin: 0.6em 0; } .preview-modal-body hr { border: none; border-top: 1px solid var(--border); margin: 1.5em 0; } .preview-modal-body ul, .preview-modal-body ol { padding-left: 1.4em; } .preview-modal-body li + li { margin-top: 0.25em; } .preview-modal-body strong { color: var(--fg); } .preview-modal-footer { padding: 12px 20px; border-top: 1px solid var(--border); display: flex; align-items: center; gap: 8px; flex-shrink: 0; } .preview-modal-model { margin-right: auto; font-family: var(--font); font-size: 11px; color: var(--fg-muted); background: var(--bg-elevated); border: 1px solid var(--border-muted); border-radius: var(--radius-sm); padding: 4px 8px; max-width: 220px; outline: none; } .preview-modal-model:focus { border-color: var(--accent); } .preview-popover { position: absolute; z-index: 260; width: min(340px, calc(100% - 40px)); background: var(--bg-elevated); border: 1px solid var(--accent); border-radius: var(--radius); padding: 10px 12px; display: flex; flex-direction: column; gap: 8px; box-shadow: 0 8px 24px rgba(0,0,0,0.35); animation: fade-in 100ms ease-out; } .preview-popover.hidden { display: none; } .preview-popover-quote { font-size: 12px; color: var(--fg-muted); font-style: italic; border-left: 2px solid var(--accent); padding-left: 8px; max-height: 48px; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } .preview-popover-input { font-family: var(--font); font-size: 13px; line-height: 1.4; color: var(--fg); background: var(--bg-card); border: 1px solid var(--border-muted); border-radius: var(--radius-sm); padding: 6px 10px; outline: none; width: 100%; resize: vertical; } .preview-popover-input:focus { border-color: var(--accent); } .preview-popover-btn { align-self: flex-end; font-size: 12px; padding: 5px 14px; } .error-banner { position: fixed; bottom: 64px; left: 50%; transform: translateX(-50%); z-index: 50; padding: 10px 20px; background: var(--timer-urgent-bg); color: var(--timer-urgent-fg); border-radius: var(--radius); font-size: 13px; font-weight: 500; } .summary-panel.updating { border-color: color-mix(in srgb, var(--accent) 35%, var(--border)); position: relative; overflow: hidden; } .summary-panel.updating::after { content: ""; position: absolute; top: 0; left: 0; width: 30%; height: 2px; border-radius: var(--radius) var(--radius) 0 0; background: linear-gradient(90deg, transparent, var(--accent), transparent); animation: updating-bar 1.8s ease-in-out infinite; pointer-events: none; } .summary-panel.updating .summary-input, .summary-panel.updating .summary-feedback-row { opacity: 0.45; pointer-events: none; } .summary-panel.updating .summary-actions { opacity: 0.72; } @keyframes updating-bar { 0% { transform: translateX(-50%); } 100% { transform: translateX(430%); } } @media (prefers-reduced-motion: reduce) { .loading-card::after, .result-card.searching::after, .provider-btn.loading::after, .searching-dots::after, .summary-generating::before, .summary-generating-orb, .summary-generating-bar::after, .summary-panel.updating::after { animation: none !important; } } @media (max-width: 500px) { main { padding: 32px 16px 16px; } .hero-title { font-size: 28px; } .hero-desc { font-size: 13px; } .summary-header-top { flex-direction: column; } .summary-model-controls { flex-wrap: wrap; } .summary-model-dropdown { max-width: 100%; } .action-bar { padding: 10px 14px; } .action-shortcuts { display: none; } .result-card-header { padding: 12px 14px; } .expired-content { padding: 32px 24px; } .timer-badge { top: 12px; right: 16px; } } `; const SCRIPT = `(function() { var DATA = __INLINE_DATA__; var token = DATA.sessionToken; var timeoutSec = DATA.timeout; var queries = Array.isArray(DATA.queries) ? DATA.queries : []; var providers = ["perplexity", "exa", "gemini"]; var availProviders = DATA.availableProviders && typeof DATA.availableProviders === "object" ? DATA.availableProviders : {}; var workflow = "summary-review"; var initialDefaultProvider = typeof DATA.defaultProvider === "string" ? DATA.defaultProvider : "exa"; if (providers.indexOf(initialDefaultProvider) === -1) initialDefaultProvider = "exa"; var summaryModels = Array.isArray(DATA.summaryModels) ? DATA.summaryModels.filter(function(model) { return model && typeof model === "object" && typeof model.value === "string"; }) : []; var defaultSummaryModel = typeof DATA.defaultSummaryModel === "string" ? DATA.defaultSummaryModel.trim() : ""; var submitted = false; var timerExpired = false; var submitInFlight = false; var searchesDone = false; var stage = "results"; var summaryMeta = null; var summaryRequestSeq = 0; var lastAutoSummarySignature = ""; var lastInteraction = Date.now(); var completedCount = 0; var es = null; var allQueries = queries.map(function(query, slotId) { return { slotId: slotId, query: query }; }); var nextSlotId = queries.length; var queryIndexToSlot = new Map(); var providerCoverage = new Map(); var currentProvider = initialDefaultProvider; var initialStreamDone = queries.length === 0; var providerBatchInFlight = false; var batchLoadingProvider = null; var addSearchInFlight = 0; var isRegenerating = false; var timerEl = document.getElementById("timer"); var timerAdjustEl = document.getElementById("timer-adjust"); var timerInput = document.getElementById("timer-input"); var timerSetBtn = document.getElementById("timer-set"); var heroTitle = document.querySelector(".hero-title"); var heroDesc = document.querySelector(".hero-desc"); var resultCardsEl = document.getElementById("result-cards"); var btnSend = document.getElementById("btn-send"); var btnSendRaw = document.getElementById("btn-send-raw"); var sendRawRow = document.getElementById("send-raw-row"); var summaryPanel = document.getElementById("summary-panel"); var summarySubtitle = document.getElementById("summary-subtitle"); var summaryGeneratingEl = document.getElementById("summary-generating"); var summaryGeneratingCopy = document.getElementById("summary-generating-copy"); var summaryInput = document.getElementById("summary-input"); var summaryFeedback = document.getElementById("summary-feedback"); var btnSummaryBack = document.getElementById("btn-summary-back"); var btnSummaryRegenerate = document.getElementById("btn-summary-regenerate"); var btnSummaryPreview = document.getElementById("btn-summary-preview"); var btnSummaryApprove = document.getElementById("btn-summary-approve"); var successOverlay = document.getElementById("success-overlay"); var successText = document.getElementById("success-text"); var expiredOverlay = document.getElementById("expired-overlay"); var expiredText = document.getElementById("expired-text"); var closeCountdown = document.getElementById("close-countdown"); var errorBanner = document.getElementById("error-banner"); var addSearchInput = document.getElementById("add-search-input"); var addSearchEl = document.getElementById("add-search"); var addSearchWand = document.getElementById("add-search-wand"); var heroStatus = document.getElementById("hero-status"); var summaryProviderSelect = document.getElementById("summary-provider-select"); var summaryModelSelect = document.getElementById("summary-model-select"); var previewModal = document.getElementById("preview-modal"); var previewModalBody = document.getElementById("preview-modal-body"); var previewModalClose = document.getElementById("preview-modal-close"); var previewModalModel = document.getElementById("preview-modal-model"); var previewModalRegenerate = document.getElementById("preview-modal-regenerate"); var previewModalApprove = document.getElementById("preview-modal-approve"); var previewPopover = document.getElementById("preview-popover"); var previewPopoverQuote = document.getElementById("preview-popover-quote"); var previewPopoverInput = document.getElementById("preview-popover-input"); var previewPopoverRegen = document.getElementById("preview-popover-regen"); var providerButtons = Array.prototype.slice.call(document.querySelectorAll(".provider-btn")); var loadingPanelEl = null; var summaryModelsByProvider = Object.create(null); var summaryProviders = []; var currentSummaryProvider = ""; var currentSummaryModel = ""; var summaryPendingModel = ""; var summaryGeneratingStartedAt = 0; var summaryGeneratingPhase = -1; var rewriteInFlight = false; function escHtml(s) { return String(s) .replace(/&/g, "&") .replace(//g, ">") .replace(/\"/g, """); } function sanitizeHref(url) { var value = typeof url === "string" ? url.trim() : ""; return /^https?:\/\//i.test(value) ? value : "#"; } function sanitizeMarkdownHtml(html) { var container = document.createElement("div"); container.innerHTML = html; container.querySelectorAll("script, iframe, object, embed, form, style, link, meta, base") .forEach(function(el) { el.remove(); }); var nodes = container.querySelectorAll("*"); nodes.forEach(function(node) { for (var i = node.attributes.length - 1; i >= 0; i--) { var attr = node.attributes[i]; if (/^on/i.test(attr.name)) node.removeAttribute(attr.name); } }); var anchors = container.querySelectorAll("a[href]"); anchors.forEach(function(anchor) { var safe = sanitizeHref(anchor.getAttribute("href") || ""); anchor.setAttribute("href", safe); anchor.setAttribute("rel", "noopener noreferrer"); anchor.setAttribute("target", "_blank"); }); var images = container.querySelectorAll("img[src]"); images.forEach(function(img) { var safe = sanitizeHref(img.getAttribute("src") || ""); if (safe === "#") { img.remove(); } else { img.setAttribute("src", safe); } }); return container.innerHTML; } function post(path, body) { return fetch(path, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(Object.assign({ token: token }, body)), }); } function extractServerError(data) { if (!data || typeof data !== "object") return ""; if (typeof data.error === "string" && data.error.trim()) return data.error.trim(); return ""; } function postJson(path, body) { return post(path, body).then(function(res) { return res.text().then(function(raw) { var data = null; if (raw) { try { data = JSON.parse(raw); } catch (err) { var parseMessage = err instanceof Error ? err.message : String(err); throw new Error("Invalid JSON response from " + path + ": " + parseMessage); } } if (!res.ok) { throw new Error(extractServerError(data) || ("HTTP " + res.status)); } return data; }); }); } function formatTime(sec) { var m = Math.floor(sec / 60); var s = sec % 60; return m + ":" + (s < 10 ? "0" : "") + s; } function normalizeProvider(provider, fallback) { if (typeof provider === "string") { var normalized = provider.toLowerCase(); if (providers.indexOf(normalized) !== -1) return normalized; } if (typeof fallback === "string") { var fallbackNormalized = fallback.toLowerCase(); if (providers.indexOf(fallbackNormalized) !== -1) return fallbackNormalized; } return ""; } function providerLabel(provider) { if (provider === "perplexity") return "Perplexity"; if (provider === "exa") return "Exa"; if (provider === "gemini") return "Gemini"; return "Unknown"; } function providerTagHtml(provider) { var normalized = normalizeProvider(provider, ""); if (!normalized) return ""; return '' + escHtml(providerLabel(normalized)) + ""; } function buildAltChipsHtml(provider, queryText) { var normalizedProv = normalizeProvider(provider, ""); if (!normalizedProv) return ""; var altProviders = providers.filter(function(p) { return p !== normalizedProv && availProviders[p] === true; }); if (altProviders.length === 0) return ""; var html = '
Also try'; for (var ap = 0; ap < altProviders.length; ap++) { html += ''; } html += "
"; return html; } function getSummaryProvider(modelValue) { if (typeof modelValue !== "string") return ""; var trimmed = modelValue.trim(); var slash = trimmed.indexOf("/"); if (slash <= 0) return ""; return trimmed.slice(0, slash); } function summaryProviderLabel(provider) { if (!provider) return ""; if (provider === "openai") return "OpenAI"; if (provider === "google") return "Google"; if (provider === "anthropic") return "Anthropic"; return provider.charAt(0).toUpperCase() + provider.slice(1); } function buildSummaryModelState() { summaryModelsByProvider = Object.create(null); summaryProviders = []; var seenValues = {}; for (var i = 0; i < summaryModels.length; i++) { var model = summaryModels[i]; if (!model || typeof model.value !== "string") continue; var value = model.value.trim(); if (!value || seenValues[value]) continue; var provider = getSummaryProvider(value); if (!provider) continue; seenValues[value] = true; if (!summaryModelsByProvider[provider]) { summaryModelsByProvider[provider] = []; summaryProviders.push(provider); } var label = typeof model.label === "string" && model.label.trim().length > 0 ? model.label.trim() : value; summaryModelsByProvider[provider].push({ value: value, label: label }); } } function renderSummaryProviderSelect() { if (!summaryProviderSelect) return; summaryProviderSelect.innerHTML = ""; for (var i = 0; i < summaryProviders.length; i++) { var provider = summaryProviders[i]; var option = document.createElement("option"); option.value = provider; option.textContent = summaryProviderLabel(provider); summaryProviderSelect.appendChild(option); } } function populateSummaryModelSelect(provider, preferredModel) { if (!summaryModelSelect) return; summaryModelSelect.innerHTML = ""; var autoOption = document.createElement("option"); autoOption.value = ""; autoOption.textContent = "Auto"; summaryModelSelect.appendChild(autoOption); var models = summaryModelsByProvider[provider] || []; for (var i = 0; i < models.length; i++) { var option = document.createElement("option"); option.value = models[i].value; var shortLabel = models[i].value; var labelSlash = shortLabel.indexOf("/"); if (labelSlash > 0) shortLabel = shortLabel.slice(labelSlash + 1); option.textContent = shortLabel; summaryModelSelect.appendChild(option); } var hasPreferred = false; if (preferredModel) { for (var j = 0; j < models.length; j++) { if (models[j].value === preferredModel) { hasPreferred = true; break; } } } if (hasPreferred) { summaryModelSelect.value = preferredModel; } else if (models.length > 0) { summaryModelSelect.value = models[0].value; } else { summaryModelSelect.value = ""; } currentSummaryModel = typeof summaryModelSelect.value === "string" ? summaryModelSelect.value.trim() : ""; } function setSummaryProvider(provider, preferredModel) { if (summaryProviders.indexOf(provider) === -1) return; currentSummaryProvider = provider; if (summaryProviderSelect) { summaryProviderSelect.value = provider; } populateSummaryModelSelect(provider, preferredModel); } function initializeSummaryModelControls() { buildSummaryModelState(); renderSummaryProviderSelect(); if (summaryProviders.length === 0) { currentSummaryProvider = ""; currentSummaryModel = ""; if (summaryProviderSelect) summaryProviderSelect.innerHTML = ""; if (summaryModelSelect) { summaryModelSelect.innerHTML = ''; summaryModelSelect.value = ""; } return; } var defaultProvider = getSummaryProvider(defaultSummaryModel); if (defaultProvider && summaryProviders.indexOf(defaultProvider) !== -1) { setSummaryProvider(defaultProvider, defaultSummaryModel); return; } setSummaryProvider(summaryProviders[0], ""); } function getSelectedSummaryModel() { if (!summaryModelSelect) return currentSummaryModel; if (typeof summaryModelSelect.value !== "string") return currentSummaryModel; currentSummaryModel = summaryModelSelect.value.trim(); return currentSummaryModel; } function getFeedbackText() { if (!summaryFeedback || typeof summaryFeedback.value !== "string") return ""; return summaryFeedback.value; } function getCoverageSet(provider) { var set = providerCoverage.get(provider); if (set) return set; set = new Set(); providerCoverage.set(provider, set); return set; } function markCoverage(provider, slotId) { if (typeof slotId !== "number") return; var normalized = normalizeProvider(provider, ""); if (!normalized) return; getCoverageSet(normalized).add(slotId); } function removeSlot(slotId) { allQueries = allQueries.filter(function(slot) { return slot.slotId !== slotId; }); providerCoverage.forEach(function(coveredSlots) { coveredSlots.delete(slotId); }); queryIndexToSlot.forEach(function(mappedSlotId, qi) { if (mappedSlotId === slotId) queryIndexToSlot.delete(qi); }); syncLoadingPanel(); } function isResultMutationLocked() { return submitted || timerExpired || submitInFlight; } function applyProviderInterlocks() { var disableProviders = isResultMutationLocked() || providerBatchInFlight || addSearchInFlight; for (var i = 0; i < providerButtons.length; i++) { var btn = providerButtons[i]; var state = btn.dataset.state || "idle"; btn.disabled = disableProviders || state === "loading"; } var disableAddSearch = isResultMutationLocked(); if (addSearchInput) { addSearchInput.disabled = disableAddSearch; } if (addSearchEl) { addSearchEl.style.opacity = disableAddSearch ? "0.6" : ""; addSearchEl.style.pointerEvents = disableAddSearch ? "none" : ""; } var cards = resultCardsEl ? resultCardsEl.querySelectorAll(".result-card") : []; cards.forEach(function(card) { var cb = card.querySelector("input[type=checkbox]"); if (!cb) return; var searching = card.classList.contains("searching"); var error = card.classList.contains("error"); cb.disabled = searching || error || isResultMutationLocked(); }); } function recomputeProviderStates() { for (var i = 0; i < providerButtons.length; i++) { var btn = providerButtons[i]; var provider = normalizeProvider(btn.dataset.provider, ""); if (!provider) continue; var state = "idle"; if (providerBatchInFlight && batchLoadingProvider === provider) { state = "loading"; } else if (!initialStreamDone && queries.length > 0 && provider === initialDefaultProvider) { state = "loading"; } else if (allQueries.length > 0) { var coveredSlots = providerCoverage.get(provider); if (coveredSlots && coveredSlots.size >= allQueries.length) { state = "searched"; } } btn.dataset.state = state; btn.classList.remove("idle", "loading", "searched"); btn.classList.add(state); btn.classList.toggle("is-default", provider === currentProvider); } applyProviderInterlocks(); } function updateSummaryText() { if (completedCount <= 0) return; var totalCards = resultCardsEl.querySelectorAll(".result-card").length; var searchingCount = totalCards - completedCount; if (searchingCount > 0) { heroTitle.textContent = completedCount + " of " + totalCards + " Searches Complete"; } else { heroTitle.textContent = completedCount + " Search" + (completedCount !== 1 ? "es" : "") + " Complete"; } heroDesc.textContent = "Check the results to include, then generate and approve a summary."; if (heroStatus) heroStatus.textContent = completedCount + " completed" + (searchingCount > 0 ? ", " + searchingCount + " searching" : ""); } function getSummaryDraftText() { if (!summaryInput || typeof summaryInput.value !== "string") return ""; return summaryInput.value.trim(); } function clearError() { if (!errorBanner) return; errorBanner.hidden = true; errorBanner.textContent = ""; } function setError(text) { if (!errorBanner) return; errorBanner.textContent = text; errorBanner.hidden = false; } function updateSummaryGeneratingIndicator() { if (!summaryGeneratingCopy) return; if (stage !== "generating-summary") { summaryGeneratingCopy.textContent = "Generating summary draft…"; summaryGeneratingPhase = -1; if (summaryGeneratingEl) { summaryGeneratingEl.removeAttribute("data-phase"); } return; } if (summaryGeneratingStartedAt <= 0) { summaryGeneratingStartedAt = Date.now(); } var elapsedMs = Date.now() - summaryGeneratingStartedAt; var nextPhase = Math.min(2, Math.floor(elapsedMs / 1800)); if (nextPhase === summaryGeneratingPhase) return; summaryGeneratingPhase = nextPhase; var phaseLabel = "Planning summary"; if (nextPhase === 1) phaseLabel = "Drafting summary"; if (nextPhase === 2) phaseLabel = "Polishing summary"; summaryGeneratingCopy.textContent = summaryPendingModel ? phaseLabel + " with " + summaryPendingModel + "…" : phaseLabel + "…"; if (summaryGeneratingEl) { summaryGeneratingEl.dataset.phase = String(nextPhase); } } function updateStageUI() { var showSummary = stage === "summary-review" || stage === "generating-summary" || isRegenerating; if (summaryPanel) { summaryPanel.classList.toggle("hidden", !showSummary); summaryPanel.classList.toggle("updating", isRegenerating); } if (summarySubtitle) { var selCount = getSelectedIndices().length; var selLabel = selCount + " selected result" + (selCount !== 1 ? "s" : ""); if (isRegenerating && stage === "generating-summary") { summarySubtitle.textContent = "Selection changed — regenerating summary…"; } else if (isRegenerating) { summarySubtitle.textContent = "Selection changed — summary will regenerate shortly…"; } else if (stage === "generating-summary") { summarySubtitle.textContent = summaryPendingModel ? "Summarizing " + selLabel + " with " + summaryPendingModel + "…" : "Summarizing " + selLabel + "…"; } else if (summaryMeta && summaryMeta.fallbackUsed) { summarySubtitle.textContent = "Fallback summary of " + selLabel + "."; } else { summarySubtitle.textContent = "Summary of " + selLabel + ". Edit directly, regenerate with feedback, or approve."; } } if (summaryGeneratingEl) { var showGenerating = stage === "generating-summary" && !isRegenerating; summaryGeneratingEl.classList.toggle("hidden", !showGenerating); } updateSummaryGeneratingIndicator(); if (summaryInput) { summaryInput.classList.toggle("hidden", stage === "generating-summary" && !isRegenerating); summaryInput.disabled = submitted || timerExpired || stage === "generating-summary" || submitInFlight || isRegenerating; } if (summaryFeedback) { summaryFeedback.disabled = submitted || timerExpired || submitInFlight || stage === "generating-summary" || isRegenerating; } var disableSummaryModelControls = submitted || timerExpired || stage === "generating-summary" || submitInFlight || summaryProviders.length === 0; if (summaryProviderSelect) { summaryProviderSelect.disabled = disableSummaryModelControls; } if (summaryModelSelect) { summaryModelSelect.disabled = disableSummaryModelControls; } var inResults = stage === "results"; var hasSelection = getSelectedIndices().length > 0; var hasCompleted = getCompletedSelectableIndices().length > 0; var canGenerate = inResults && !submitted && !timerExpired && !submitInFlight && hasCompleted; if (btnSend) { if (stage === "generating-summary") { btnSend.textContent = "Generating summary…"; btnSend.disabled = true; } else if (!inResults) { btnSend.textContent = "Summary ready"; btnSend.disabled = true; } else if (!hasCompleted) { btnSend.textContent = searchesDone ? "No results yet" : "Waiting for results…"; btnSend.disabled = true; } else { btnSend.textContent = hasSelection ? "Generate summary" : "Select results to summarize"; btnSend.disabled = !canGenerate || !hasSelection; } } if (sendRawRow) { sendRawRow.classList.toggle("hidden", !hasSelection || submitted || timerExpired); } if (btnSendRaw) { btnSendRaw.disabled = !hasSelection || submitted || timerExpired || submitInFlight; } if (btnSummaryBack) btnSummaryBack.disabled = submitted || timerExpired || submitInFlight || (stage === "generating-summary" && !isRegenerating); if (btnSummaryRegenerate) btnSummaryRegenerate.disabled = submitted || timerExpired || submitInFlight || stage === "generating-summary" || isRegenerating; var hasDraft = getSummaryDraftText().length > 0; if (btnSummaryPreview) btnSummaryPreview.disabled = !hasDraft || stage === "generating-summary"; if (btnSummaryApprove) { btnSummaryApprove.disabled = submitted || timerExpired || submitInFlight || stage === "generating-summary" || isRegenerating || !hasSelection || !hasDraft; } applyProviderInterlocks(); } function shouldShowLoadingPanel() { if (submitted || timerExpired || searchesDone) return false; if (completedCount > 0) return false; return allQueries.length > 0; } function ensureLoadingPanel() { if (loadingPanelEl) return loadingPanelEl; if (!resultCardsEl) return null; var panel = document.createElement("div"); panel.className = "result-loading"; panel.innerHTML = '
' + '
Searching sources
' + '
Searching\u2026
' + '
' + '
' + '
' + '
' + '
'; resultCardsEl.prepend(panel); loadingPanelEl = panel; return panel; } function updateLoadingPanelSummary() { if (!loadingPanelEl) return; var sub = loadingPanelEl.querySelector(".result-loading-sub"); if (!sub) return; var total = allQueries.length; if (total <= 0) { sub.textContent = "Searching\u2026"; return; } var done = Math.min(completedCount, total); var noun = total === 1 ? "query" : "queries"; sub.textContent = "Searching " + done + "/" + total + " " + noun + "\u2026"; } function syncLoadingPanel() { if (shouldShowLoadingPanel()) { if (!ensureLoadingPanel()) return; updateLoadingPanelSummary(); return; } if (loadingPanelEl) { loadingPanelEl.remove(); loadingPanelEl = null; } } function renderErrorCard(card, queryText, errorText, provider) { var tag = providerTagHtml(provider); card.innerHTML = '
' + '' + '
' + '
' + '
' + escHtml(queryText) + "
" + tag + "
" + '
Failed
' + "
" + "
" + '
' + escHtml(errorText || "Search failed") + "
"; } function populateResultCard(card, data, queryText, provider) { var sourceCount = data.results ? data.results.length : 0; var domains = []; if (data.results) { for (var i = 0; i < Math.min(data.results.length, 3); i++) { domains.push(data.results[i].domain); } } var metaText = sourceCount + " source" + (sourceCount !== 1 ? "s" : ""); if (domains.length > 0) metaText += " \u00B7 " + domains.join(", "); if (sourceCount > 3) metaText += ", +" + (sourceCount - 3); var preview = ""; if (data.answer) { preview = data.answer.substring(0, 200).replace(/\\n+/g, " ").replace(/[#*_\\[\\]]/g, ""); } var bodyHtml = ""; if (data.answer) { var rendered = typeof marked !== "undefined" && marked.parse ? marked.parse(data.answer, { breaks: true }) : "

" + escHtml(data.answer) + "

"; bodyHtml += '
' + sanitizeMarkdownHtml(rendered) + "
"; } if (data.results && data.results.length > 0) { bodyHtml += '
Sources
'; for (var k = 0; k < data.results.length; k++) { var r = data.results[k]; var label = r.title && r.title.indexOf("Source ") !== 0 ? r.title : r.url; var href = sanitizeHref(r.url); bodyHtml += '' + escHtml(label) + '' + escHtml(r.domain) + ""; } bodyHtml += "
"; } var altChipsHtml = buildAltChipsHtml(provider, queryText); card.innerHTML = '
' + '' + '
' + '
' + '
' + escHtml(queryText) + "
" + providerTagHtml(provider) + "
" + '
' + escHtml(metaText) + "
" + (preview ? '
' + escHtml(preview) + "
" : "") + "
" + '
\u25BC
' + "
" + altChipsHtml + '
' + bodyHtml + "
"; } function applyResponseToCard(card, data, queryText, providerHint, slotHint) { if (!card || !data) return; if (submitted || timerExpired) return; var queryIndex = typeof data.queryIndex === "number" ? data.queryIndex : null; if (queryIndex !== null) { card.dataset.qi = String(queryIndex); } var slotId = typeof slotHint === "number" ? slotHint : (queryIndex !== null ? queryIndexToSlot.get(queryIndex) : undefined); if (typeof slotId !== "number" && queryIndex !== null) { slotId = queryIndex; } if (queryIndex !== null && typeof slotId === "number") { queryIndexToSlot.set(queryIndex, slotId); } var provider = normalizeProvider(data.provider, providerHint); card.classList.remove("searching", "checked", "error"); if (data.error) { card.classList.add("error"); renderErrorCard(card, queryText, data.error, provider); } else { card.classList.add("checked"); populateResultCard(card, data, queryText, provider); setupCardInteraction(card); } if (card.dataset.completed !== "true") { completedCount++; card.dataset.completed = "true"; } markCoverage(provider, slotId); updateSummaryText(); syncLoadingPanel(); recomputeProviderStates(); updateStageUI(); maybeAutoGenerateSummary(); resetTimer(); } function resetTimer() { lastInteraction = Date.now(); } function updateTimer() { var idleSec = Math.floor((Date.now() - lastInteraction) / 1000); var remaining = Math.max(0, timeoutSec - idleSec); timerEl.textContent = formatTime(remaining); timerEl.classList.remove("warn", "urgent", "active"); if (remaining <= 15) timerEl.classList.add("urgent"); else if (remaining <= 30) timerEl.classList.add("warn"); else if (remaining < timeoutSec) timerEl.classList.add("active"); updateSummaryGeneratingIndicator(); if (remaining <= 0 && !submitted && !timerExpired) onTimeout(); } setInterval(updateTimer, 1000); updateTimer(); ["click", "keydown", "input", "change"].forEach(function(evt) { document.addEventListener(evt, resetTimer, { passive: true }); }); document.addEventListener("scroll", resetTimer, { passive: true }); document.addEventListener("mousemove", resetTimer, { passive: true }); timerEl.addEventListener("click", function(e) { e.stopPropagation(); timerInput.value = timeoutSec; timerAdjustEl.classList.add("visible"); timerEl.style.display = "none"; timerInput.focus(); timerInput.select(); }); function applyTimerAdjust() { var val = parseInt(timerInput.value, 10); if (val && val > 0) timeoutSec = Math.min(val, 600); timerAdjustEl.classList.remove("visible"); timerEl.style.display = ""; resetTimer(); } timerSetBtn.addEventListener("click", function(e) { e.stopPropagation(); applyTimerAdjust(); }); timerInput.addEventListener("keydown", function(e) { if (e.key === "Enter") { e.preventDefault(); applyTimerAdjust(); } if (e.key === "Escape") { timerAdjustEl.classList.remove("visible"); timerEl.style.display = ""; } e.stopPropagation(); }); document.addEventListener("click", function() { if (timerAdjustEl.classList.contains("visible")) { timerAdjustEl.classList.remove("visible"); timerEl.style.display = ""; } }); function setDefaultProvider(provider, persist) { var normalized = normalizeProvider(provider, currentProvider); if (!normalized) return; currentProvider = normalized; recomputeProviderStates(); if (persist) { postJson("/provider", { provider: normalized }).then(function(data) { if (data && data.ok === false) { throw new Error(extractServerError(data) || "request rejected"); } }).catch(function(err) { var message = err instanceof Error ? err.message : String(err); setError("Failed to save provider preference: " + (message || "unknown error")); }); } } providerButtons.forEach(function(btn) { btn.addEventListener("click", function() { if (isResultMutationLocked()) return; if (providerBatchInFlight || addSearchInFlight) return; var provider = normalizeProvider(btn.dataset.provider, ""); if (!provider) return; var state = btn.dataset.state || "idle"; if (state === "loading") return; if (state === "searched") { if (provider === currentProvider) return; setDefaultProvider(provider, true); resetTimer(); return; } setDefaultProvider(provider, true); if (allQueries.length === 0) { resetTimer(); return; } interruptSummaryIfNeeded(); providerBatchInFlight = true; batchLoadingProvider = provider; recomputeProviderStates(); var batchQueries = allQueries.slice(); var inflight = batchQueries.length; if (inflight === 0) { providerBatchInFlight = false; batchLoadingProvider = null; recomputeProviderStates(); return; } var batchCards = []; for (var bi = 0; bi < batchQueries.length; bi++) { var bq = batchQueries[bi]; var card = document.createElement("div"); card.className = "result-card searching"; card.innerHTML = '
' + '' + '
' + '
' + '
' + escHtml(bq.query) + "
" + providerTagHtml(provider) + "
" + '
Searching
' + "
" + "
" + buildAltChipsHtml(provider, bq.query); resultCardsEl.appendChild(card); batchCards.push(card); } updateSummaryText(); batchQueries.forEach(function(slot, si) { var searchingCard = batchCards[si]; postJson("/search", { query: slot.query, provider: provider }) .then(function(data) { if (submitted || timerExpired) return; if (!data || data.ok === false) { applyResponseToCard(searchingCard, { answer: "", results: [], error: extractServerError(data) || "Search failed", provider: provider, }, slot.query, provider, slot.slotId); return; } applyResponseToCard(searchingCard, data, slot.query, provider, slot.slotId); }) .catch(function(err) { if (submitted || timerExpired) return; var message = err instanceof Error ? err.message : String(err); applyResponseToCard(searchingCard, { answer: "", results: [], error: message || "Search failed", provider: provider, }, slot.query, provider, slot.slotId); }) .finally(function() { inflight -= 1; if (inflight <= 0) { providerBatchInFlight = false; batchLoadingProvider = null; recomputeProviderStates(); updateStageUI(); maybeAutoGenerateSummary(); } }); }); resetTimer(); }); }); if (resultCardsEl) { resultCardsEl.addEventListener("click", function(e) { if (!(e.target instanceof Element)) return; var chip = e.target.closest(".card-alt-chip"); if (!chip) return; if (isResultMutationLocked()) return; var altProvider = chip.dataset.altProvider; var altQuery = chip.dataset.altQuery; if (!altProvider || !altQuery) return; interruptSummaryIfNeeded(); chip.classList.add("loading"); chip.disabled = true; resetTimer(); var slotId = nextSlotId++; allQueries.push({ slotId: slotId, query: altQuery }); var parentCard = chip.closest(".result-card"); var newCard = document.createElement("div"); newCard.className = "result-card searching"; newCard.innerHTML = '
' + '' + '
' + '
' + '
' + escHtml(altQuery) + "
" + providerTagHtml(altProvider) + "
" + '
Searching
' + "
" + "
" + buildAltChipsHtml(altProvider, altQuery); if (parentCard && parentCard.nextSibling) { resultCardsEl.insertBefore(newCard, parentCard.nextSibling); } else { resultCardsEl.appendChild(newCard); } updateSummaryText(); postJson("/search", { query: altQuery, provider: altProvider }) .then(function(data) { if (submitted || timerExpired) return; if (!data || data.ok === false) { applyResponseToCard(newCard, { answer: "", results: [], error: extractServerError(data) || "Search failed", provider: altProvider, }, altQuery, altProvider, slotId); return; } applyResponseToCard(newCard, data, altQuery, altProvider, slotId); }) .catch(function(err) { removeSlot(slotId); newCard.remove(); var message = err instanceof Error ? err.message : String(err); setError("Re-search failed: " + (message || "Search failed")); updateSummaryText(); }) .finally(function() { chip.classList.remove("loading"); chip.disabled = false; recomputeProviderStates(); updateStageUI(); maybeAutoGenerateSummary(); }); }); } if (addSearchInput && addSearchWand) { addSearchInput.addEventListener("input", function() { addSearchWand.disabled = rewriteInFlight || !addSearchInput.value.trim() || isResultMutationLocked(); }); addSearchWand.addEventListener("click", function() { var text = addSearchInput.value.trim(); if (!text || rewriteInFlight || isResultMutationLocked()) return; rewriteInFlight = true; addSearchWand.disabled = true; addSearchWand.classList.add("rewriting"); resetTimer(); postJson("/rewrite", { query: text }) .then(function(data) { if (!data || data.ok === false) { throw new Error(extractServerError(data) || "Rewrite failed"); } var rewritten = typeof data.query === "string" ? data.query.trim() : ""; if (rewritten) { addSearchInput.value = rewritten; addSearchInput.focus(); } }) .catch(function(err) { var message = err instanceof Error ? err.message : String(err); setError("Rewrite failed: " + (message || "unknown error")); }) .finally(function() { rewriteInFlight = false; addSearchWand.classList.remove("rewriting"); addSearchWand.disabled = !addSearchInput.value.trim() || isResultMutationLocked(); }); }); } addSearchInput.addEventListener("keydown", function(e) { if (e.key !== "Enter") return; var text = addSearchInput.value.trim(); if (!text || isResultMutationLocked()) return; interruptSummaryIfNeeded(); e.preventDefault(); e.stopPropagation(); addSearchInFlight++; applyProviderInterlocks(); addSearchInput.value = ""; var slotId = nextSlotId++; allQueries.push({ slotId: slotId, query: text }); syncLoadingPanel(); recomputeProviderStates(); var requestedProvider = currentProvider; var card = document.createElement("div"); card.className = "result-card searching"; card.innerHTML = '
' + '' + '
' + '
' + '
' + escHtml(text) + "
" + providerTagHtml(requestedProvider) + "
" + '
Searching
' + "
" + "
" + buildAltChipsHtml(requestedProvider, text); resultCardsEl.appendChild(card); updateSummaryText(); resetTimer(); postJson("/search", { query: text, provider: requestedProvider }) .then(function(data) { if (!data || data.ok === false) { removeSlot(slotId); card.remove(); setError("Failed to add search: " + (extractServerError(data) || "Search failed")); recomputeProviderStates(); updateSummaryText(); return; } if (submitted || timerExpired) return; applyResponseToCard(card, data, text, requestedProvider, slotId); }) .catch(function(err) { removeSlot(slotId); card.remove(); var message = err instanceof Error ? err.message : String(err); setError("Failed to add search: " + (message || "Search failed")); recomputeProviderStates(); updateSummaryText(); }) .finally(function() { addSearchInFlight--; recomputeProviderStates(); updateStageUI(); maybeAutoGenerateSummary(); }); }); function showSuccess(text) { if (es) { es.close(); es = null; } closePreviewModal(); successText.textContent = text; successOverlay.classList.remove("hidden"); setTimeout(function() { window.close(); }, 800); } function showExpired(text) { if (es) { es.close(); es = null; } closePreviewModal(); expiredText.textContent = text; expiredOverlay.classList.remove("hidden"); requestAnimationFrame(function() { expiredOverlay.classList.add("visible"); }); } function startOverlayCloseCountdown(seconds) { var count = seconds; closeCountdown.textContent = count; var iv = setInterval(function() { count--; closeCountdown.textContent = count; if (count <= 0) { clearInterval(iv); window.close(); } }, 1000); } function submitPayload(payload, successText) { if (submitInFlight) return Promise.reject(new Error("Submit already in progress")); submitInFlight = true; submitted = true; syncLoadingPanel(); updateStageUI(); clearError(); return postJson("/submit", payload) .then(function(data) { if (data && data.ok === false) { throw new Error(extractServerError(data) || "submit rejected"); } showSuccess(successText); }) .catch(function(err) { submitInFlight = false; submitted = false; syncLoadingPanel(); updateStageUI(); throw err; }); } function submitWithTimeoutFallback(payload) { if (submitInFlight) return; submitInFlight = true; submitted = true; timerExpired = true; syncLoadingPanel(); updateStageUI(); clearError(); showExpired("Time\u2019s up \u2014 submitting current summary state."); function finalizeClose() { submitInFlight = false; startOverlayCloseCountdown(5); } function toErrorMessage(err) { return err instanceof Error ? err.message : String(err); } function attemptCancelFallback(submitErrorMessage) { return postJson("/cancel", { reason: "timeout" }) .catch(function(cancelErr) { console.error("Timeout finalize failed after submit errors:", submitErrorMessage, "| cancel:", toErrorMessage(cancelErr)); }) .finally(finalizeClose); } postJson("/submit", payload) .then(function(data) { if (data && data.ok === false) { throw new Error(extractServerError(data) || "submit rejected"); } finalizeClose(); }) .catch(function(firstErr) { var firstMessage = toErrorMessage(firstErr); setTimeout(function() { postJson("/submit", payload) .then(function(data) { if (data && data.ok === false) { throw new Error(extractServerError(data) || "submit rejected"); } finalizeClose(); }) .catch(function(secondErr) { var secondMessage = toErrorMessage(secondErr); attemptCancelFallback(firstMessage + " | " + secondMessage); }); }, 250); }); } function onTimeout() { if (submitted || timerExpired) return; var timeoutSelected = getTimeoutSelectedIndices(); var payload = { selected: timeoutSelected }; var draft = getSummaryDraftText(); if (stage === "summary-review" && draft.length > 0) { payload.summary = draft; if (summaryMeta) payload.summaryMeta = summaryMeta; } submitWithTimeoutFallback(payload); } if (queries.length === 0) { heroTitle.textContent = "What do you need?"; heroDesc.textContent = "Search for anything below, then generate and approve a summary."; if (heroStatus) heroStatus.textContent = ""; btnSend.textContent = "No results yet"; } else { for (var i = 0; i < queries.length; i++) { queryIndexToSlot.set(i, i); var card = document.createElement("div"); card.className = "result-card searching"; card.dataset.qi = i; card.innerHTML = '
' + '' + '
' + '
' + '
' + escHtml(queries[i]) + "
" + providerTagHtml(initialDefaultProvider) + "
" + '
Searching
' + "
" + "
" + buildAltChipsHtml(initialDefaultProvider, queries[i]); resultCardsEl.appendChild(card); } } initializeSummaryModelControls(); syncLoadingPanel(); recomputeProviderStates(); updateStageUI(); es = new EventSource("/events?session=" + encodeURIComponent(token)); function parseSseEventData(eventName, e) { try { return JSON.parse(e.data); } catch (err) { var message = err instanceof Error ? err.message : String(err); setError("Invalid " + eventName + " event payload: " + (message || "unknown parse error")); return null; } } es.addEventListener("result", function(e) { var data = parseSseEventData("result", e); if (!data) return; var card = resultCardsEl.querySelector('.result-card[data-qi="' + data.queryIndex + '"]'); if (!card) return; var slotId = queryIndexToSlot.get(data.queryIndex); if (typeof slotId !== "number") slotId = data.queryIndex; applyResponseToCard(card, data, data.query || queries[data.queryIndex], data.provider, slotId); }); es.addEventListener("search-error", function(e) { var data = parseSseEventData("search-error", e); if (!data) return; var card = resultCardsEl.querySelector('.result-card[data-qi="' + data.queryIndex + '"]'); if (!card) return; var slotId = queryIndexToSlot.get(data.queryIndex); if (typeof slotId !== "number") slotId = data.queryIndex; applyResponseToCard(card, { queryIndex: data.queryIndex, answer: "", results: [], error: data.error || "Search failed", provider: data.provider, }, data.query || queries[data.queryIndex], data.provider, slotId); }); es.addEventListener("done", function() { searchesDone = true; initialStreamDone = true; if (completedCount > 0) { updateSummaryText(); } syncLoadingPanel(); recomputeProviderStates(); updateStageUI(); maybeAutoGenerateSummary(); resetTimer(); }); es.onerror = function() { // EventSource reconnects automatically. }; function setupCardInteraction(card) { var header = card.querySelector(".result-card-header"); var body = card.querySelector(".result-card-body"); var cb = card.querySelector("input[type=checkbox]"); var expandEl = card.querySelector(".result-card-expand"); if (!header || !cb) return; header.addEventListener("click", function(e) { if (e.target.tagName === "A") return; if (e.target === cb) { if (isResultMutationLocked()) { e.preventDefault(); return; } card.classList.toggle("checked", cb.checked); if (stage === "summary-review" || stage === "generating-summary") { interruptSummaryIfNeeded(); } updateStageUI(); maybeAutoGenerateSummary(); return; } var isExpanded = body && body.classList.contains("open"); if (body) body.classList.toggle("open"); if (expandEl) expandEl.textContent = isExpanded ? "\u25BC" : "\u25B2"; }); if (body) { body.addEventListener("click", function(e) { e.stopPropagation(); }); } } function getSelectedIndices() { var indices = []; var cards = resultCardsEl.querySelectorAll(".result-card"); cards.forEach(function(card) { if (card.dataset.completed !== "true") return; if (card.classList.contains("error")) return; var cb = card.querySelector("input[type=checkbox]"); if (!cb || !cb.checked) return; var qi = parseInt(card.dataset.qi, 10); if (!Number.isNaN(qi)) indices.push(qi); }); return indices; } function getCompletedSelectableIndices() { var indices = []; var cards = resultCardsEl.querySelectorAll(".result-card"); cards.forEach(function(card) { if (card.dataset.completed !== "true") return; if (card.classList.contains("error")) return; var qi = parseInt(card.dataset.qi, 10); if (!Number.isNaN(qi)) indices.push(qi); }); return indices; } function hasPendingSearchCards() { var cards = resultCardsEl.querySelectorAll(".result-card"); for (var i = 0; i < cards.length; i++) { var card = cards[i]; if (card.dataset.completed !== "true") return true; } return addSearchInFlight || providerBatchInFlight; } function getTimeoutSelectedIndices() { var selected = getSelectedIndices(); if (selected.length > 0) return selected; return getCompletedSelectableIndices(); } function normalizeSummaryMeta(meta, edited) { if (!meta || typeof meta !== "object") { return { model: null, durationMs: 0, tokenEstimate: 0, fallbackUsed: false, edited: !!edited, }; } return { model: typeof meta.model === "string" || meta.model === null ? meta.model : null, durationMs: typeof meta.durationMs === "number" && Number.isFinite(meta.durationMs) && meta.durationMs >= 0 ? meta.durationMs : 0, tokenEstimate: typeof meta.tokenEstimate === "number" && Number.isFinite(meta.tokenEstimate) && meta.tokenEstimate >= 0 ? meta.tokenEstimate : 0, fallbackUsed: meta.fallbackUsed === true, fallbackReason: typeof meta.fallbackReason === "string" ? meta.fallbackReason : undefined, edited: !!edited, }; } function isSummaryModelSelectionError(message) { if (typeof message !== "string") return false; return message.indexOf("Invalid summary model") !== -1 || message.indexOf("Summary model not found") !== -1 || message.indexOf("No API key available for summary model") !== -1 || message.indexOf("Invalid provider") !== -1; } function resetSummaryGeneratingState() { summaryPendingModel = ""; summaryGeneratingStartedAt = 0; summaryGeneratingPhase = -1; } function cancelInFlightSummaryRequest() { summaryRequestSeq += 1; resetSummaryGeneratingState(); } function interruptSummaryIfNeeded() { if (stage !== "generating-summary" && stage !== "summary-review") return; if (stage === "generating-summary") { cancelInFlightSummaryRequest(); } clearError(); isRegenerating = getSummaryDraftText().length > 0; stage = "results"; updateStageUI(); } function exitRegeneratingState() { if (!isRegenerating) return false; if (stage === "generating-summary") { cancelInFlightSummaryRequest(); } isRegenerating = false; clearError(); stage = "results"; updateStageUI(); return true; } function requestSummary(indices, feedback) { if (submitted || timerExpired || submitInFlight) return; if (!Array.isArray(indices) || indices.length === 0) { setError("Select at least one result to summarize"); stage = "results"; updateStageUI(); return; } if (hasPendingSearchCards()) { setError("Wait for running searches to finish before generating summary"); stage = "results"; updateStageUI(); return; } clearError(); var previousStage = stage; var wasRegenerating = isRegenerating; var selectedSummaryModel = getSelectedSummaryModel(); summaryPendingModel = selectedSummaryModel; summaryGeneratingStartedAt = Date.now(); summaryGeneratingPhase = -1; stage = "generating-summary"; updateStageUI(); var requestId = ++summaryRequestSeq; var feedbackText = typeof feedback === "string" ? feedback.trim() : ""; var summarizePayload = { selected: indices }; if (selectedSummaryModel.length > 0) { summarizePayload.model = selectedSummaryModel; } if (feedbackText.length > 0) { summarizePayload.feedback = feedbackText; } postJson("/summarize", summarizePayload) .then(function(data) { if (requestId !== summaryRequestSeq) return data; if (!data || data.ok === false) { throw new Error(extractServerError(data) || "summary request rejected"); } return data; }) .catch(function(err) { if (requestId !== summaryRequestSeq) throw err; var firstMessage = err instanceof Error ? err.message : String(err); if (selectedSummaryModel.length === 0 || !isSummaryModelSelectionError(firstMessage)) { throw err; } summaryPendingModel = ""; updateStageUI(); var retryPayload = { selected: indices }; if (feedbackText.length > 0) { retryPayload.feedback = feedbackText; } return postJson("/summarize", retryPayload).then(function(retryData) { if (!retryData || retryData.ok === false) { throw new Error(extractServerError(retryData) || "summary request rejected"); } return retryData; }).catch(function(retryErr) { var retryMessage = retryErr instanceof Error ? retryErr.message : String(retryErr); throw new Error(firstMessage + " (auto retry failed: " + (retryMessage || "unknown error") + ")"); }); }) .then(function(data) { if (requestId !== summaryRequestSeq) return; var summaryText = typeof data.summary === "string" ? data.summary.trim() : ""; if (!summaryText) { throw new Error("Summary response was empty"); } if (summaryInput) { summaryInput.value = summaryText; } if (summaryFeedback) { summaryFeedback.value = ""; } summaryMeta = normalizeSummaryMeta(data.meta || null, false); lastAutoSummarySignature = selectionSignature(indices); resetSummaryGeneratingState(); isRegenerating = false; stage = "summary-review"; updateStageUI(); }) .catch(function(err) { if (requestId !== summaryRequestSeq) return; var message = err instanceof Error ? err.message : String(err); setError("Failed to generate summary — " + (message || "unknown error")); resetSummaryGeneratingState(); isRegenerating = false; if (wasRegenerating && getSummaryDraftText().length > 0) { stage = "summary-review"; } else { stage = previousStage === "summary-review" ? "summary-review" : "results"; } updateStageUI(); }); } function selectionSignature(indices) { return indices.slice().sort(function(a, b) { return a - b; }).join(","); } function maybeAutoGenerateSummary() { if (workflow !== "summary-review") return; if (!searchesDone) return; if (stage !== "results") return; if (submitted || timerExpired || submitInFlight) return; if (hasPendingSearchCards()) return; var selected = getSelectedIndices(); if (selected.length === 0) { if (isRegenerating) { isRegenerating = false; updateStageUI(); } return; } var signature = selectionSignature(selected); if (signature === lastAutoSummarySignature) { if (isRegenerating) { isRegenerating = false; if (getSummaryDraftText().length > 0) { stage = "summary-review"; } updateStageUI(); } return; } lastAutoSummarySignature = signature; requestSummary(selected); } function doApprove() { if (submitted || timerExpired || submitInFlight || stage !== "summary-review") return; var selected = getSelectedIndices(); if (selected.length === 0) { setError("Select at least one result before approving"); updateStageUI(); return; } var draft = getSummaryDraftText(); var payload = { selected: selected }; if (draft.length > 0) { payload.summary = draft; payload.summaryMeta = normalizeSummaryMeta(summaryMeta, summaryMeta && summaryMeta.edited === true); } submitPayload(payload, "Summary approved") .catch(function(err) { var message = err instanceof Error ? err.message : String(err); setError("Failed to approve summary — " + (message || "the agent may have moved on")); }); } function doCancel() { if (submitted || timerExpired || submitInFlight) return; submitted = true; submitInFlight = true; syncLoadingPanel(); updateStageUI(); clearError(); postJson("/cancel", { reason: "user" }) .then(function(data) { if (data && data.ok === false) { throw new Error(extractServerError(data) || "cancel rejected"); } showSuccess("Skipped"); }) .catch(function(err) { submitted = false; submitInFlight = false; syncLoadingPanel(); updateStageUI(); var message = err instanceof Error ? err.message : String(err); setError("Failed to cancel — " + (message || "the agent may have moved on")); }); } btnSend.addEventListener("click", function() { if (stage !== "results") return; requestSummary(getSelectedIndices()); }); if (btnSendRaw) { btnSendRaw.addEventListener("click", function() { var selected = getSelectedIndices(); if (selected.length === 0) return; submitPayload({ selected: selected, rawResults: true }, "Results sent") .catch(function(err) { var message = err instanceof Error ? err.message : String(err); setError("Failed to send results — " + (message || "the agent may have moved on")); }); }); } if (btnSummaryBack) { btnSummaryBack.addEventListener("click", function() { if (exitRegeneratingState()) { resetTimer(); return; } if (stage !== "summary-review") return; clearError(); stage = "results"; updateStageUI(); resetTimer(); }); } if (btnSummaryRegenerate) { btnSummaryRegenerate.addEventListener("click", function() { requestSummary(getSelectedIndices(), getFeedbackText()); resetTimer(); }); } function openPreviewModal() { var draft = getSummaryDraftText(); if (!draft || !previewModal || !previewModalBody) return; var rendered = typeof marked !== "undefined" && marked.parse ? marked.parse(draft, { breaks: true }) : "
" + escHtml(draft) + "
"; previewModalBody.innerHTML = sanitizeMarkdownHtml(rendered); if (previewModalModel) { previewModalModel.innerHTML = ''; for (var i = 0; i < summaryModels.length; i++) { var m = summaryModels[i]; var opt = document.createElement("option"); opt.value = m.value; opt.textContent = m.label; previewModalModel.appendChild(opt); } previewModalModel.value = getSelectedSummaryModel() || ""; } previewModal.classList.remove("hidden"); resetTimer(); } function closePreviewModal() { if (previewModal) previewModal.classList.add("hidden"); if (previewModalBody) previewModalBody.innerHTML = ""; hidePreviewPopover(); } var popoverSelectedText = ""; function hidePreviewPopover() { if (previewPopover) previewPopover.classList.add("hidden"); if (previewPopoverInput) previewPopoverInput.value = ""; popoverSelectedText = ""; } function showPreviewPopover(text, rect) { if (!previewPopover || !previewPopoverQuote || !previewModalBody) return; popoverSelectedText = text; var display = text.length > 120 ? text.slice(0, 117) + "\u2026" : text; previewPopoverQuote.textContent = "\u201c" + display + "\u201d"; if (previewPopoverInput) previewPopoverInput.value = ""; previewPopover.classList.remove("hidden"); var bodyRect = previewModalBody.getBoundingClientRect(); var popH = previewPopover.offsetHeight; var top = rect.bottom - bodyRect.top + previewModalBody.scrollTop + 6; if (rect.bottom + popH + 20 > bodyRect.bottom) { top = rect.top - bodyRect.top + previewModalBody.scrollTop - popH - 6; } var left = Math.max(8, Math.min(rect.left - bodyRect.left, bodyRect.width - previewPopover.offsetWidth - 8)); previewPopover.style.top = top + "px"; previewPopover.style.left = left + "px"; if (previewPopoverInput) previewPopoverInput.focus(); } if (btnSummaryPreview) { btnSummaryPreview.addEventListener("click", openPreviewModal); } if (previewModalClose) { previewModalClose.addEventListener("click", closePreviewModal); } if (previewModalRegenerate) { previewModalRegenerate.addEventListener("click", function() { var selectedModel = previewModalModel ? previewModalModel.value.trim() : ""; closePreviewModal(); var modelProvider = getSummaryProvider(selectedModel); if (modelProvider && modelProvider !== currentSummaryProvider) { setSummaryProvider(modelProvider, selectedModel); } else if (summaryModelSelect) { summaryModelSelect.value = selectedModel; currentSummaryModel = selectedModel; } requestSummary(getSelectedIndices(), getFeedbackText()); resetTimer(); }); } if (previewModalApprove) { previewModalApprove.addEventListener("click", function() { closePreviewModal(); doApprove(); }); } if (previewModalBody) { previewModalBody.addEventListener("mouseup", function() { var sel = window.getSelection(); if (!sel || sel.isCollapsed) return; var text = sel.toString().trim(); if (!text) return; var range = sel.getRangeAt(0); showPreviewPopover(text, range.getBoundingClientRect()); }); previewModalBody.addEventListener("mousedown", function(e) { if (previewPopover && !previewPopover.contains(e.target)) { hidePreviewPopover(); } }); } if (previewPopoverRegen) { previewPopoverRegen.addEventListener("click", function() { var note = previewPopoverInput ? previewPopoverInput.value.trim() : ""; var quoted = popoverSelectedText; hidePreviewPopover(); var feedback = 'Regarding: "' + quoted + '"'; if (note) feedback += " \u2014 " + note; var selectedModel = previewModalModel ? previewModalModel.value.trim() : ""; closePreviewModal(); var modelProvider = getSummaryProvider(selectedModel); if (modelProvider && modelProvider !== currentSummaryProvider) { setSummaryProvider(modelProvider, selectedModel); } else if (summaryModelSelect) { summaryModelSelect.value = selectedModel; currentSummaryModel = selectedModel; } requestSummary(getSelectedIndices(), feedback); resetTimer(); }); } if (previewPopoverInput) { previewPopoverInput.addEventListener("keydown", function(e) { if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); if (previewPopoverRegen) previewPopoverRegen.click(); } if (e.key === "Escape") { e.preventDefault(); e.stopImmediatePropagation(); hidePreviewPopover(); } }); } if (previewModal) { previewModal.addEventListener("click", function(e) { if (e.target === previewModal) closePreviewModal(); }); document.addEventListener("keydown", function(e) { if (e.key === "Escape" && !previewModal.classList.contains("hidden")) { if (previewPopover && !previewPopover.classList.contains("hidden")) { e.preventDefault(); e.stopImmediatePropagation(); hidePreviewPopover(); return; } e.preventDefault(); e.stopImmediatePropagation(); closePreviewModal(); } }); } if (btnSummaryApprove) { btnSummaryApprove.addEventListener("click", function() { doApprove(); resetTimer(); }); } if (summaryInput) { summaryInput.addEventListener("input", function() { if (!summaryMeta || typeof summaryMeta !== "object") { summaryMeta = normalizeSummaryMeta(null, true); } summaryMeta.edited = true; clearError(); updateStageUI(); resetTimer(); }); } if (summaryProviderSelect) { summaryProviderSelect.addEventListener("change", function() { var provider = typeof summaryProviderSelect.value === "string" ? summaryProviderSelect.value : ""; if (!provider || provider === currentSummaryProvider) return; setSummaryProvider(provider, ""); clearError(); updateStageUI(); resetTimer(); }); } if (summaryModelSelect) { summaryModelSelect.addEventListener("change", function() { currentSummaryModel = typeof summaryModelSelect.value === "string" ? summaryModelSelect.value.trim() : ""; clearError(); resetTimer(); }); } function isInteractiveTarget(target) { if (!target || !target.tagName) return false; var tag = target.tagName; if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || tag === "BUTTON" || tag === "A") return true; if (typeof target.isContentEditable === "boolean" && target.isContentEditable) return true; if (typeof target.closest === "function") { return !!target.closest('[contenteditable=""], [contenteditable="true"]'); } return false; } document.addEventListener("keydown", function(e) { if (submitted || timerExpired || submitInFlight) return; var isSummaryInput = summaryInput && e.target === summaryInput; if (isSummaryInput && (e.metaKey || e.ctrlKey) && e.key === "Enter") { e.preventDefault(); if (stage === "summary-review") doApprove(); return; } if (e.key === "Escape") { e.preventDefault(); if (exitRegeneratingState()) { return; } if (stage === "summary-review") { stage = "results"; clearError(); updateStageUI(); } else if (stage === "results") { doCancel(); } return; } if (isInteractiveTarget(e.target)) return; if (e.key === "Enter" && !e.metaKey && !e.ctrlKey) { if (stage !== "results") return; e.preventDefault(); requestSummary(getSelectedIndices()); return; } if ((e.metaKey || e.ctrlKey) && e.key === "Enter") { if (stage !== "summary-review") return; e.preventDefault(); doApprove(); return; } if (e.key.toLowerCase() === "a" && !e.metaKey && !e.ctrlKey) { e.preventDefault(); if (stage !== "results") return; var boxes = resultCardsEl.querySelectorAll(".result-card input[type=checkbox]"); var selectable = []; boxes.forEach(function(cb) { if (cb.disabled) return; selectable.push(cb); }); if (selectable.length === 0) return; var allChecked = true; selectable.forEach(function(cb) { if (!cb.checked) allChecked = false; }); selectable.forEach(function(cb) { cb.checked = !allChecked; var parentCard = typeof cb.closest === "function" ? cb.closest(".result-card") : null; if (parentCard) parentCard.classList.toggle("checked", cb.checked); }); updateStageUI(); maybeAutoGenerateSummary(); resetTimer(); } }); setInterval(function() { if (submitted) return; postJson("/heartbeat", {}).catch(function() { // Heartbeat is best-effort. }); }, 10000); var lastResizeHeight = 0; function checkContentHeight() { if (!window.glimpse || typeof window.glimpse.send !== "function") return; var h = document.documentElement.scrollHeight || document.body.scrollHeight; if (h > 0 && Math.abs(h - lastResizeHeight) > 30) { lastResizeHeight = h; window.glimpse.send({ type: "resize", height: h }); } } setInterval(checkContentHeight, 500); if (queries.length === 0 && addSearchInput) { addSearchInput.focus(); } })();`;