@undefineds.co/linx 0.3.5 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -23
- package/dist/generated/version.js +1 -1
- package/dist/generated/version.js.map +1 -1
- package/dist/index.js +336 -162
- package/dist/index.js.map +1 -1
- package/dist/lib/account-session.js +4 -8
- package/dist/lib/account-session.js.map +1 -1
- package/dist/lib/ai-command.js +228 -178
- package/dist/lib/ai-command.js.map +1 -1
- package/dist/lib/auto-mode/archive.js +38 -7
- package/dist/lib/auto-mode/archive.js.map +1 -1
- package/dist/lib/auto-mode/auth.js.map +1 -1
- package/dist/lib/auto-mode/display.js +71 -45
- package/dist/lib/auto-mode/display.js.map +1 -1
- package/dist/lib/auto-mode/format.js +9 -7
- package/dist/lib/auto-mode/format.js.map +1 -1
- package/dist/lib/auto-mode/hooks/claude.js +12 -2
- package/dist/lib/auto-mode/hooks/claude.js.map +1 -1
- package/dist/lib/auto-mode/hooks/codex.js +17 -7
- package/dist/lib/auto-mode/hooks/codex.js.map +1 -1
- package/dist/lib/auto-mode/hooks/index.js +28 -8
- package/dist/lib/auto-mode/hooks/index.js.map +1 -1
- package/dist/lib/auto-mode/pod-ai.js +20 -37
- package/dist/lib/auto-mode/pod-ai.js.map +1 -1
- package/dist/lib/auto-mode/pod-approval.js +124 -195
- package/dist/lib/auto-mode/pod-approval.js.map +1 -1
- package/dist/lib/auto-mode/pod-persistence.js +169 -90
- package/dist/lib/auto-mode/pod-persistence.js.map +1 -1
- package/dist/lib/auto-mode/runner.js +683 -81
- package/dist/lib/auto-mode/runner.js.map +1 -1
- package/dist/lib/auto-mode/secretary.js +186 -41
- package/dist/lib/auto-mode/secretary.js.map +1 -1
- package/dist/lib/auto-mode-command.js +32 -32
- package/dist/lib/auto-mode-command.js.map +1 -1
- package/dist/lib/chat-api.js +242 -50
- package/dist/lib/chat-api.js.map +1 -1
- package/dist/lib/codex-plugin/bridge.js +164 -17
- package/dist/lib/codex-plugin/bridge.js.map +1 -1
- package/dist/lib/codex-plugin/codex-native-proxy.js +370 -34
- package/dist/lib/codex-plugin/codex-native-proxy.js.map +1 -1
- package/dist/lib/credentials-store.js +33 -42
- package/dist/lib/credentials-store.js.map +1 -1
- package/dist/lib/linx-cloud-errors.js +61 -0
- package/dist/lib/linx-cloud-errors.js.map +1 -0
- package/dist/lib/linx-tui-contract.js +8 -5
- package/dist/lib/linx-tui-contract.js.map +1 -1
- package/dist/lib/login-command.js +9 -2
- package/dist/lib/login-command.js.map +1 -1
- package/dist/lib/models.js +3 -20
- package/dist/lib/models.js.map +1 -1
- package/dist/lib/oidc-auth.js +143 -17
- package/dist/lib/oidc-auth.js.map +1 -1
- package/dist/lib/oidc-session-storage.js +2 -6
- package/dist/lib/oidc-session-storage.js.map +1 -1
- package/dist/lib/pi-adapter/auto-input-controller.js +988 -0
- package/dist/lib/pi-adapter/auto-input-controller.js.map +1 -0
- package/dist/lib/pi-adapter/backend-command.js +2 -0
- package/dist/lib/pi-adapter/backend-command.js.map +1 -0
- package/dist/lib/pi-adapter/backend-credentials.js +80 -0
- package/dist/lib/pi-adapter/backend-credentials.js.map +1 -0
- package/dist/lib/pi-adapter/branding.js +246 -108
- package/dist/lib/pi-adapter/branding.js.map +1 -1
- package/dist/lib/pi-adapter/control-state.js +72 -0
- package/dist/lib/pi-adapter/control-state.js.map +1 -0
- package/dist/lib/pi-adapter/interactive.js +2634 -30
- package/dist/lib/pi-adapter/interactive.js.map +1 -1
- package/dist/lib/pi-adapter/pod-approval.js +382 -210
- package/dist/lib/pi-adapter/pod-approval.js.map +1 -1
- package/dist/lib/pi-adapter/pod-mirror-mapping.js +71 -17
- package/dist/lib/pi-adapter/pod-mirror-mapping.js.map +1 -1
- package/dist/lib/pi-adapter/pod-mirror.js +531 -64
- package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
- package/dist/lib/pi-adapter/pod-native.js +81 -85
- package/dist/lib/pi-adapter/pod-native.js.map +1 -1
- package/dist/lib/pi-adapter/pod-status-output.js +54 -0
- package/dist/lib/pi-adapter/pod-status-output.js.map +1 -0
- package/dist/lib/pi-adapter/runtime.js +458 -228
- package/dist/lib/pi-adapter/runtime.js.map +1 -1
- package/dist/lib/pi-adapter/session-control.js +509 -0
- package/dist/lib/pi-adapter/session-control.js.map +1 -0
- package/dist/lib/pi-adapter/session.js +35 -22
- package/dist/lib/pi-adapter/session.js.map +1 -1
- package/dist/lib/pi-adapter/stream.js +89 -32
- package/dist/lib/pi-adapter/stream.js.map +1 -1
- package/dist/lib/pi-adapter/sync-recovery.js +89 -0
- package/dist/lib/pi-adapter/sync-recovery.js.map +1 -0
- package/dist/lib/pi-adapter/web-fetch.js +13 -14
- package/dist/lib/pi-adapter/web-fetch.js.map +1 -1
- package/dist/lib/pod-chat-store.js +254 -78
- package/dist/lib/pod-chat-store.js.map +1 -1
- package/dist/lib/pod-data-session.js +156 -35
- package/dist/lib/pod-data-session.js.map +1 -1
- package/dist/lib/solid-auth-store.js +27 -0
- package/dist/lib/solid-auth-store.js.map +1 -0
- package/dist/lib/solid-auth.js +2 -4
- package/dist/lib/solid-auth.js.map +1 -1
- package/dist/lib/solid-client-credentials-login.js +100 -0
- package/dist/lib/solid-client-credentials-login.js.map +1 -0
- package/dist/lib/solid-local-store.js +31 -0
- package/dist/lib/solid-local-store.js.map +1 -0
- package/dist/lib/symphony/archive.js +328 -18
- package/dist/lib/symphony/archive.js.map +1 -1
- package/dist/lib/symphony/pod-projection.js +2222 -0
- package/dist/lib/symphony/pod-projection.js.map +1 -0
- package/dist/lib/symphony-command.js +602 -178
- package/dist/lib/symphony-command.js.map +1 -1
- package/dist/lib/sync-checkpoint-store.js +74 -0
- package/dist/lib/sync-checkpoint-store.js.map +1 -0
- package/dist/skills/symphony/SKILL.md +665 -0
- package/package.json +15 -9
- package/vendor/agent-runtime/dist/agent-runtime.d.ts +137 -0
- package/vendor/agent-runtime/dist/agent-runtime.js +211 -0
- package/vendor/agent-runtime/dist/auto-mode.d.ts +78 -13
- package/vendor/agent-runtime/dist/auto-mode.js +288 -31
- package/vendor/agent-runtime/dist/control-plane.d.ts +28 -0
- package/vendor/agent-runtime/dist/control-plane.js +79 -0
- package/vendor/agent-runtime/dist/file-sync.d.ts +157 -0
- package/vendor/agent-runtime/dist/file-sync.js +314 -0
- package/vendor/agent-runtime/dist/index.d.ts +7 -0
- package/vendor/agent-runtime/dist/index.js +7 -0
- package/vendor/agent-runtime/dist/reconciler.d.ts +117 -0
- package/vendor/agent-runtime/dist/reconciler.js +361 -0
- package/vendor/agent-runtime/dist/symphony.d.ts +128 -8
- package/vendor/agent-runtime/dist/symphony.js +362 -57
- package/vendor/agent-runtime/dist/sync.d.ts +271 -0
- package/vendor/agent-runtime/dist/sync.js +550 -0
- package/vendor/agent-runtime/dist/thread-reconciler-controller.d.ts +58 -0
- package/vendor/agent-runtime/dist/thread-reconciler-controller.js +137 -0
- package/vendor/agent-runtime/dist/turn-controller.js +2 -2
- package/vendor/agent-runtime/dist/wake-scheduler.d.ts +67 -0
- package/vendor/agent-runtime/dist/wake-scheduler.js +194 -0
- package/vendor/agent-runtime/package.json +8 -1
- package/vendor/pi-web-access/CHANGELOG.md +387 -0
- package/vendor/pi-web-access/LICENSE +21 -0
- package/vendor/pi-web-access/README.md +352 -0
- package/vendor/pi-web-access/activity.ts +101 -0
- package/vendor/pi-web-access/banner.png +0 -0
- package/vendor/pi-web-access/chrome-cookies.ts +322 -0
- package/vendor/pi-web-access/code-search.ts +107 -0
- package/vendor/pi-web-access/curator-page.ts +3359 -0
- package/vendor/pi-web-access/curator-server.ts +605 -0
- package/vendor/pi-web-access/exa.ts +520 -0
- package/vendor/pi-web-access/extract.ts +641 -0
- package/vendor/pi-web-access/gemini-api.ts +112 -0
- package/vendor/pi-web-access/gemini-search.ts +361 -0
- package/vendor/pi-web-access/gemini-url-context.ts +126 -0
- package/vendor/pi-web-access/gemini-web-config.ts +52 -0
- package/vendor/pi-web-access/gemini-web.ts +396 -0
- package/vendor/pi-web-access/github-api.ts +196 -0
- package/vendor/pi-web-access/github-extract.ts +634 -0
- package/vendor/pi-web-access/index.ts +2346 -0
- package/vendor/pi-web-access/package.json +45 -0
- package/vendor/pi-web-access/pdf-extract.ts +192 -0
- package/vendor/pi-web-access/perplexity.ts +195 -0
- package/vendor/pi-web-access/pi-web-fetch-demo.mp4 +0 -0
- package/vendor/pi-web-access/rsc-extract.ts +338 -0
- package/vendor/pi-web-access/skills/librarian/SKILL.md +195 -0
- package/vendor/pi-web-access/storage.ts +72 -0
- package/vendor/pi-web-access/summary-review.ts +276 -0
- package/vendor/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +41 -0
- package/vendor/pi-web-access/test/pdf-extract.test.mjs +95 -0
- package/vendor/pi-web-access/utils.ts +44 -0
- package/vendor/pi-web-access/video-extract.ts +378 -0
- package/vendor/pi-web-access/youtube-extract.ts +310 -0
- package/dist/lib/pi-adapter/auth.js +0 -68
- package/dist/lib/pi-adapter/auth.js.map +0 -1
- package/dist/lib/pi-adapter/pod-tools.js +0 -140
- package/dist/lib/pi-adapter/pod-tools.js.map +0 -1
- package/dist/skills/drizzle-solid/SKILL.md +0 -340
- package/dist/skills/pod-storage/SKILL.md +0 -100
- package/dist/skills/solid-modeling/SKILL.md +0 -274
- package/dist/skills/xpod-componentsjs/SKILL.md +0 -284
|
@@ -0,0 +1,3359 @@
|
|
|
1
|
+
function safeInlineJSON(data: unknown): string {
|
|
2
|
+
return JSON.stringify(data)
|
|
3
|
+
.replace(/</g, "\\u003c")
|
|
4
|
+
.replace(/>/g, "\\u003e")
|
|
5
|
+
.replace(/&/g, "\\u0026")
|
|
6
|
+
.replace(/\u2028/g, "\\u2028")
|
|
7
|
+
.replace(/\u2029/g, "\\u2029");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function buildProviderButtons(
|
|
11
|
+
available: { perplexity: boolean; exa: boolean; gemini: boolean },
|
|
12
|
+
selected: string,
|
|
13
|
+
hasInitialQueries: boolean,
|
|
14
|
+
): string {
|
|
15
|
+
const providers = [
|
|
16
|
+
{ value: "perplexity", label: "Perplexity", available: available.perplexity },
|
|
17
|
+
{ value: "exa", label: "Exa", available: available.exa },
|
|
18
|
+
{ value: "gemini", label: "Gemini", available: available.gemini },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
return providers
|
|
22
|
+
.filter(p => p.available)
|
|
23
|
+
.map((p) => {
|
|
24
|
+
const isDefault = p.value === selected;
|
|
25
|
+
const state = isDefault && hasInitialQueries ? "loading" : "idle";
|
|
26
|
+
const classes = ["provider-btn", state, isDefault ? "is-default" : ""].filter(Boolean).join(" ");
|
|
27
|
+
const disabled = state === "loading" ? " disabled" : "";
|
|
28
|
+
return `<button type="button" class="${classes}" data-provider="${p.value}" data-state="${state}"${disabled}>${p.label}</button>`;
|
|
29
|
+
})
|
|
30
|
+
.join("");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function generateCuratorPage(
|
|
34
|
+
queries: string[],
|
|
35
|
+
sessionToken: string,
|
|
36
|
+
timeout: number,
|
|
37
|
+
availableProviders: { perplexity: boolean; exa: boolean; gemini: boolean },
|
|
38
|
+
defaultProvider: string,
|
|
39
|
+
summaryModels: Array<{ value: string; label: string }>,
|
|
40
|
+
defaultSummaryModel: string | null,
|
|
41
|
+
): string {
|
|
42
|
+
const providerButtonsHtml = buildProviderButtons(availableProviders, defaultProvider, queries.length > 0);
|
|
43
|
+
const inlineData = safeInlineJSON({ queries, sessionToken, timeout, defaultProvider, summaryModels, defaultSummaryModel, availableProviders });
|
|
44
|
+
|
|
45
|
+
return `<!DOCTYPE html>
|
|
46
|
+
<html lang="en">
|
|
47
|
+
<head>
|
|
48
|
+
<meta charset="UTF-8">
|
|
49
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
50
|
+
<title>Curate Search Results</title>
|
|
51
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
52
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
53
|
+
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif&family=Outfit:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
54
|
+
<script src="https://cdn.jsdelivr.net/npm/marked@15/marked.min.js"><\/script>
|
|
55
|
+
<style>
|
|
56
|
+
${CSS}
|
|
57
|
+
</style>
|
|
58
|
+
</head>
|
|
59
|
+
<body>
|
|
60
|
+
|
|
61
|
+
<div class="timer-badge" id="timer" title="Click to adjust">--:--</div>
|
|
62
|
+
<div class="timer-adjust" id="timer-adjust">
|
|
63
|
+
<input type="text" id="timer-input" value="${timeout}">
|
|
64
|
+
<span class="timer-adjust-label">sec</span>
|
|
65
|
+
<button class="timer-adjust-btn" id="timer-set">Set</button>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<main>
|
|
69
|
+
<div class="hero" id="hero">
|
|
70
|
+
<div class="hero-kicker">Web Search</div>
|
|
71
|
+
<h1 class="hero-title">Searching\u2026</h1>
|
|
72
|
+
<p class="hero-desc">Results will appear below as they complete.</p>
|
|
73
|
+
<div class="hero-meta">
|
|
74
|
+
<span id="hero-status">Searching\u2026</span>
|
|
75
|
+
<span class="hero-meta-sep"></span>
|
|
76
|
+
<div class="provider-buttons" id="provider-buttons">${providerButtonsHtml}</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
<div id="result-cards"></div>
|
|
80
|
+
<div class="send-raw-row hidden" id="send-raw-row">
|
|
81
|
+
<button class="btn btn-secondary" id="btn-send-raw" disabled>Send selected results without summary</button>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="add-search" id="add-search">
|
|
84
|
+
<span class="add-search-icon">+</span>
|
|
85
|
+
<input type="text" placeholder="Add a search\u2026" id="add-search-input">
|
|
86
|
+
<button type="button" class="add-search-wand" id="add-search-wand" disabled title="Rewrite query with AI">\u2728</button>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<section class="summary-panel hidden" id="summary-panel" aria-label="Summary review">
|
|
90
|
+
<div class="summary-header">
|
|
91
|
+
<div class="summary-header-top">
|
|
92
|
+
<div>
|
|
93
|
+
<h2 class="summary-title">Review summary draft</h2>
|
|
94
|
+
<p class="summary-subtitle" id="summary-subtitle">Edit the summary before approving.</p>
|
|
95
|
+
</div>
|
|
96
|
+
<div class="summary-model-controls">
|
|
97
|
+
<select id="summary-provider-select" class="summary-model-dropdown" aria-label="Summary provider"></select>
|
|
98
|
+
<select id="summary-model-select" class="summary-model-dropdown" aria-label="Summary model"></select>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
<div class="summary-generating hidden" id="summary-generating" aria-live="polite">
|
|
103
|
+
<div class="summary-generating-head">
|
|
104
|
+
<span class="summary-generating-orb" aria-hidden="true"></span>
|
|
105
|
+
<span id="summary-generating-copy">Generating summary draft…</span>
|
|
106
|
+
</div>
|
|
107
|
+
<div class="summary-generating-bars" aria-hidden="true">
|
|
108
|
+
<span class="summary-generating-bar b1"></span>
|
|
109
|
+
<span class="summary-generating-bar b2"></span>
|
|
110
|
+
<span class="summary-generating-bar b3"></span>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
<textarea id="summary-input" class="summary-input" placeholder="Summary draft will appear here\u2026"></textarea>
|
|
114
|
+
<div class="summary-feedback-row">
|
|
115
|
+
<input type="text" id="summary-feedback" class="summary-feedback" placeholder="Optional feedback for regeneration\u2026" />
|
|
116
|
+
</div>
|
|
117
|
+
<div class="summary-actions">
|
|
118
|
+
<button class="btn btn-secondary" id="btn-summary-back">Back</button>
|
|
119
|
+
<button class="btn btn-secondary" id="btn-summary-regenerate">Regenerate</button>
|
|
120
|
+
<button class="btn btn-secondary" id="btn-summary-preview" title="Preview rendered summary">Preview</button>
|
|
121
|
+
<button class="btn btn-submit" id="btn-summary-approve">Approve</button>
|
|
122
|
+
</div>
|
|
123
|
+
</section>
|
|
124
|
+
</main>
|
|
125
|
+
|
|
126
|
+
<footer class="action-bar">
|
|
127
|
+
<div class="action-shortcuts">
|
|
128
|
+
<span class="shortcut"><kbd>A</kbd> <span>Toggle all</span></span>
|
|
129
|
+
<span class="shortcut"><kbd>Enter</kbd> <span>Generate</span></span>
|
|
130
|
+
<span class="shortcut"><kbd>Esc</kbd> <span>Cancel</span></span>
|
|
131
|
+
</div>
|
|
132
|
+
<div class="action-buttons">
|
|
133
|
+
<button class="btn btn-submit" id="btn-send" disabled>Waiting for results\u2026</button>
|
|
134
|
+
</div>
|
|
135
|
+
</footer>
|
|
136
|
+
|
|
137
|
+
<div id="success-overlay" class="success-overlay hidden" aria-live="polite">
|
|
138
|
+
<div class="success-icon">OK</div>
|
|
139
|
+
<p id="success-text">Results sent</p>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<div id="expired-overlay" class="expired-overlay hidden" aria-live="polite">
|
|
143
|
+
<div class="expired-content">
|
|
144
|
+
<div class="expired-icon">!</div>
|
|
145
|
+
<h2>Session Ended</h2>
|
|
146
|
+
<p id="expired-text">Time\u2019s up \u2014 sending all results to your agent.</p>
|
|
147
|
+
<div class="expired-countdown">Closing in <span id="close-countdown">5</span>s</div>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<div id="preview-modal" class="preview-modal hidden">
|
|
152
|
+
<div class="preview-modal-inner">
|
|
153
|
+
<div class="preview-modal-header">
|
|
154
|
+
<h2 class="preview-modal-title">Summary Preview</h2>
|
|
155
|
+
<button class="preview-modal-close" id="preview-modal-close" title="Close">\u00d7</button>
|
|
156
|
+
</div>
|
|
157
|
+
<div class="preview-modal-body" id="preview-modal-body"></div>
|
|
158
|
+
<div class="preview-popover hidden" id="preview-popover">
|
|
159
|
+
<div class="preview-popover-quote" id="preview-popover-quote"></div>
|
|
160
|
+
<textarea class="preview-popover-input" id="preview-popover-input" placeholder="Feedback\u2026" rows="3"></textarea>
|
|
161
|
+
<button class="btn btn-submit preview-popover-btn" id="preview-popover-regen">Regenerate</button>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="preview-modal-footer">
|
|
164
|
+
<select id="preview-modal-model" class="preview-modal-model" aria-label="Summary model"></select>
|
|
165
|
+
<button class="btn btn-secondary" id="preview-modal-regenerate">Regenerate</button>
|
|
166
|
+
<button class="btn btn-submit" id="preview-modal-approve">Approve</button>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<div id="error-banner" class="error-banner" hidden></div>
|
|
172
|
+
|
|
173
|
+
<script>
|
|
174
|
+
${SCRIPT.replace("__INLINE_DATA__", () => inlineData)}
|
|
175
|
+
</script>
|
|
176
|
+
</body>
|
|
177
|
+
</html>`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const CSS = `
|
|
181
|
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
182
|
+
|
|
183
|
+
:root {
|
|
184
|
+
--bg: #18181e;
|
|
185
|
+
--bg-card: #1e1e24;
|
|
186
|
+
--bg-elevated: #252530;
|
|
187
|
+
--bg-hover: #2b2b37;
|
|
188
|
+
--fg: #e0e0e0;
|
|
189
|
+
--fg-muted: #909098;
|
|
190
|
+
--fg-dim: #606068;
|
|
191
|
+
--accent: #8abeb7;
|
|
192
|
+
--accent-hover: #9dcec7;
|
|
193
|
+
--accent-muted: rgba(138, 190, 183, 0.15);
|
|
194
|
+
--accent-subtle: rgba(138, 190, 183, 0.08);
|
|
195
|
+
--border: #2a2a34;
|
|
196
|
+
--border-muted: #353540;
|
|
197
|
+
--border-checked: #8abeb7;
|
|
198
|
+
--check-bg: #8abeb7;
|
|
199
|
+
--btn-primary: #8abeb7;
|
|
200
|
+
--btn-primary-hover: #9dcec7;
|
|
201
|
+
--btn-primary-fg: #18181e;
|
|
202
|
+
--btn-secondary: #252530;
|
|
203
|
+
--btn-secondary-hover: #2b2b37;
|
|
204
|
+
--timer-bg: #252530;
|
|
205
|
+
--timer-fg: #909098;
|
|
206
|
+
--timer-warn-bg: rgba(240, 198, 116, 0.15);
|
|
207
|
+
--timer-warn-fg: #f0c674;
|
|
208
|
+
--timer-urgent-bg: rgba(204, 102, 102, 0.15);
|
|
209
|
+
--timer-urgent-fg: #cc6666;
|
|
210
|
+
--overlay-bg: rgba(24, 24, 30, 0.92);
|
|
211
|
+
--success: #b5bd68;
|
|
212
|
+
--warning: #f0c674;
|
|
213
|
+
--font: 'Outfit', system-ui, -apple-system, sans-serif;
|
|
214
|
+
--font-display: 'Instrument Serif', Georgia, 'Times New Roman', serif;
|
|
215
|
+
--font-mono: 'SF Mono', Consolas, monospace;
|
|
216
|
+
--radius: 10px;
|
|
217
|
+
--radius-sm: 6px;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
@media (prefers-color-scheme: light) {
|
|
221
|
+
:root {
|
|
222
|
+
--bg: #f5f5f7;
|
|
223
|
+
--bg-card: #ffffff;
|
|
224
|
+
--bg-elevated: #eeeef0;
|
|
225
|
+
--bg-hover: #e4e4e8;
|
|
226
|
+
--fg: #1a1a1e;
|
|
227
|
+
--fg-muted: #6c6c74;
|
|
228
|
+
--fg-dim: #9a9aa2;
|
|
229
|
+
--accent: #5f8787;
|
|
230
|
+
--accent-hover: #4a7272;
|
|
231
|
+
--accent-muted: rgba(95, 135, 135, 0.12);
|
|
232
|
+
--accent-subtle: rgba(95, 135, 135, 0.06);
|
|
233
|
+
--border: #dcdce0;
|
|
234
|
+
--border-muted: #c8c8d0;
|
|
235
|
+
--border-checked: #5f8787;
|
|
236
|
+
--check-bg: #5f8787;
|
|
237
|
+
--btn-primary: #5f8787;
|
|
238
|
+
--btn-primary-hover: #4a7272;
|
|
239
|
+
--btn-primary-fg: #ffffff;
|
|
240
|
+
--btn-secondary: #e4e4e8;
|
|
241
|
+
--btn-secondary-hover: #d4d4d8;
|
|
242
|
+
--timer-bg: #e4e4e8;
|
|
243
|
+
--timer-fg: #6c6c74;
|
|
244
|
+
--timer-warn-bg: rgba(217, 119, 6, 0.10);
|
|
245
|
+
--timer-warn-fg: #92400e;
|
|
246
|
+
--timer-urgent-bg: rgba(175, 95, 95, 0.10);
|
|
247
|
+
--timer-urgent-fg: #991b1b;
|
|
248
|
+
--overlay-bg: rgba(255, 255, 255, 0.92);
|
|
249
|
+
--success: #4d7c0f;
|
|
250
|
+
--warning: #b45309;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
body {
|
|
255
|
+
font-family: var(--font);
|
|
256
|
+
background: var(--bg);
|
|
257
|
+
background-image: radial-gradient(ellipse at 50% 0%, var(--accent-muted) 0%, transparent 60%);
|
|
258
|
+
color: var(--fg);
|
|
259
|
+
line-height: 1.5;
|
|
260
|
+
min-height: 100dvh;
|
|
261
|
+
padding-bottom: 72px;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.timer-badge {
|
|
265
|
+
position: fixed;
|
|
266
|
+
top: 20px;
|
|
267
|
+
right: 24px;
|
|
268
|
+
z-index: 50;
|
|
269
|
+
font-family: var(--font);
|
|
270
|
+
font-size: 12px;
|
|
271
|
+
font-weight: 600;
|
|
272
|
+
font-variant-numeric: tabular-nums;
|
|
273
|
+
padding: 5px 14px;
|
|
274
|
+
border-radius: 999px;
|
|
275
|
+
background: var(--bg-elevated);
|
|
276
|
+
color: var(--timer-fg);
|
|
277
|
+
border: 1px solid var(--border);
|
|
278
|
+
transition: background 0.3s, color 0.3s, border-color 0.3s, opacity 0.3s;
|
|
279
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
280
|
+
cursor: pointer;
|
|
281
|
+
user-select: none;
|
|
282
|
+
opacity: 0.5;
|
|
283
|
+
}
|
|
284
|
+
.timer-badge:hover { opacity: 1; }
|
|
285
|
+
.timer-badge.active { opacity: 1; }
|
|
286
|
+
.timer-badge.warn {
|
|
287
|
+
opacity: 1;
|
|
288
|
+
background: var(--timer-warn-bg);
|
|
289
|
+
color: var(--timer-warn-fg);
|
|
290
|
+
border-color: color-mix(in srgb, var(--timer-warn-fg) 30%, transparent);
|
|
291
|
+
}
|
|
292
|
+
.timer-badge.urgent {
|
|
293
|
+
opacity: 1;
|
|
294
|
+
background: var(--timer-urgent-bg);
|
|
295
|
+
color: var(--timer-urgent-fg);
|
|
296
|
+
border-color: color-mix(in srgb, var(--timer-urgent-fg) 30%, transparent);
|
|
297
|
+
}
|
|
298
|
+
.timer-adjust {
|
|
299
|
+
position: fixed;
|
|
300
|
+
top: 20px;
|
|
301
|
+
right: 24px;
|
|
302
|
+
z-index: 51;
|
|
303
|
+
display: none;
|
|
304
|
+
align-items: center;
|
|
305
|
+
gap: 6px;
|
|
306
|
+
padding: 4px 6px 4px 12px;
|
|
307
|
+
background: var(--bg-elevated);
|
|
308
|
+
border: 1px solid var(--accent);
|
|
309
|
+
border-radius: 999px;
|
|
310
|
+
box-shadow: 0 2px 12px rgba(0,0,0,0.3);
|
|
311
|
+
}
|
|
312
|
+
.timer-adjust.visible { display: flex; }
|
|
313
|
+
.timer-adjust input {
|
|
314
|
+
width: 48px;
|
|
315
|
+
background: transparent;
|
|
316
|
+
border: none;
|
|
317
|
+
outline: none;
|
|
318
|
+
color: var(--fg);
|
|
319
|
+
font-family: var(--font);
|
|
320
|
+
font-size: 13px;
|
|
321
|
+
font-weight: 600;
|
|
322
|
+
font-variant-numeric: tabular-nums;
|
|
323
|
+
text-align: center;
|
|
324
|
+
}
|
|
325
|
+
.timer-adjust-label { font-size: 11px; color: var(--fg-dim); }
|
|
326
|
+
.timer-adjust-btn {
|
|
327
|
+
font-family: var(--font);
|
|
328
|
+
font-size: 11px;
|
|
329
|
+
font-weight: 600;
|
|
330
|
+
padding: 3px 10px;
|
|
331
|
+
border-radius: 999px;
|
|
332
|
+
border: none;
|
|
333
|
+
background: var(--accent);
|
|
334
|
+
color: var(--btn-primary-fg);
|
|
335
|
+
cursor: pointer;
|
|
336
|
+
}
|
|
337
|
+
.timer-adjust-btn:hover { background: var(--accent-hover); }
|
|
338
|
+
|
|
339
|
+
main {
|
|
340
|
+
max-width: 640px;
|
|
341
|
+
margin: 0 auto;
|
|
342
|
+
padding: 56px 24px 16px;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.hero { margin-bottom: 28px; }
|
|
346
|
+
.hero-kicker {
|
|
347
|
+
font-size: 11px;
|
|
348
|
+
font-weight: 600;
|
|
349
|
+
text-transform: uppercase;
|
|
350
|
+
letter-spacing: 0.1em;
|
|
351
|
+
color: var(--accent);
|
|
352
|
+
margin-bottom: 8px;
|
|
353
|
+
}
|
|
354
|
+
.hero-title {
|
|
355
|
+
font-family: var(--font-display);
|
|
356
|
+
font-size: 40px;
|
|
357
|
+
font-weight: 400;
|
|
358
|
+
font-style: italic;
|
|
359
|
+
letter-spacing: -0.01em;
|
|
360
|
+
line-height: 1.1;
|
|
361
|
+
color: var(--fg);
|
|
362
|
+
margin-bottom: 10px;
|
|
363
|
+
text-wrap: balance;
|
|
364
|
+
}
|
|
365
|
+
.hero-desc {
|
|
366
|
+
font-size: 14px;
|
|
367
|
+
color: var(--fg-muted);
|
|
368
|
+
line-height: 1.5;
|
|
369
|
+
margin-bottom: 12px;
|
|
370
|
+
max-width: 480px;
|
|
371
|
+
}
|
|
372
|
+
.hero-meta {
|
|
373
|
+
display: flex;
|
|
374
|
+
align-items: center;
|
|
375
|
+
gap: 10px;
|
|
376
|
+
font-size: 13px;
|
|
377
|
+
color: var(--fg-dim);
|
|
378
|
+
}
|
|
379
|
+
.hero-meta-sep {
|
|
380
|
+
width: 3px;
|
|
381
|
+
height: 3px;
|
|
382
|
+
border-radius: 50%;
|
|
383
|
+
background: var(--fg-dim);
|
|
384
|
+
flex-shrink: 0;
|
|
385
|
+
}
|
|
386
|
+
#hero-status:empty + .hero-meta-sep { display: none; }
|
|
387
|
+
.provider-buttons {
|
|
388
|
+
display: flex;
|
|
389
|
+
align-items: center;
|
|
390
|
+
flex-wrap: wrap;
|
|
391
|
+
gap: 6px;
|
|
392
|
+
}
|
|
393
|
+
.summary-model-controls {
|
|
394
|
+
display: flex;
|
|
395
|
+
align-items: center;
|
|
396
|
+
gap: 6px;
|
|
397
|
+
min-width: 0;
|
|
398
|
+
flex-shrink: 0;
|
|
399
|
+
}
|
|
400
|
+
.summary-model-dropdown {
|
|
401
|
+
font-family: var(--font);
|
|
402
|
+
font-size: 12px;
|
|
403
|
+
font-weight: 600;
|
|
404
|
+
color: var(--fg);
|
|
405
|
+
background: var(--bg-elevated);
|
|
406
|
+
border: 1px solid var(--border-muted);
|
|
407
|
+
border-radius: var(--radius-sm);
|
|
408
|
+
padding: 4px 8px;
|
|
409
|
+
max-width: 220px;
|
|
410
|
+
}
|
|
411
|
+
.summary-model-dropdown:focus {
|
|
412
|
+
outline: none;
|
|
413
|
+
border-color: var(--accent);
|
|
414
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent);
|
|
415
|
+
}
|
|
416
|
+
.summary-model-dropdown:disabled {
|
|
417
|
+
opacity: 0.65;
|
|
418
|
+
cursor: default;
|
|
419
|
+
}
|
|
420
|
+
.provider-btn {
|
|
421
|
+
font-family: var(--font);
|
|
422
|
+
font-size: 12px;
|
|
423
|
+
font-weight: 600;
|
|
424
|
+
padding: 3px 10px;
|
|
425
|
+
border-radius: 999px;
|
|
426
|
+
border: 1px solid var(--border-muted);
|
|
427
|
+
background: transparent;
|
|
428
|
+
color: var(--fg-muted);
|
|
429
|
+
cursor: pointer;
|
|
430
|
+
transition: border-color 0.12s, background 0.12s, color 0.12s, opacity 0.12s;
|
|
431
|
+
}
|
|
432
|
+
.provider-btn.idle:hover {
|
|
433
|
+
color: var(--fg);
|
|
434
|
+
border-color: var(--accent);
|
|
435
|
+
}
|
|
436
|
+
.provider-btn.loading {
|
|
437
|
+
background: var(--accent-subtle);
|
|
438
|
+
color: var(--accent);
|
|
439
|
+
border-color: color-mix(in srgb, var(--accent) 35%, var(--border-muted));
|
|
440
|
+
cursor: default;
|
|
441
|
+
pointer-events: none;
|
|
442
|
+
opacity: 0.85;
|
|
443
|
+
}
|
|
444
|
+
.provider-btn.loading::after {
|
|
445
|
+
content: " …";
|
|
446
|
+
animation: provider-pulse 1.2s ease-in-out infinite;
|
|
447
|
+
}
|
|
448
|
+
.provider-btn.searched {
|
|
449
|
+
background: var(--btn-secondary);
|
|
450
|
+
color: var(--fg);
|
|
451
|
+
border-color: var(--border-muted);
|
|
452
|
+
}
|
|
453
|
+
.provider-btn.searched::after {
|
|
454
|
+
content: " ✓";
|
|
455
|
+
color: var(--success);
|
|
456
|
+
}
|
|
457
|
+
.provider-btn.is-default {
|
|
458
|
+
box-shadow: inset 0 -2px 0 0 var(--accent);
|
|
459
|
+
border-color: var(--accent);
|
|
460
|
+
}
|
|
461
|
+
.provider-btn:disabled {
|
|
462
|
+
cursor: default;
|
|
463
|
+
opacity: 0.5;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
@keyframes provider-pulse {
|
|
467
|
+
0%, 100% { opacity: 0.4; }
|
|
468
|
+
50% { opacity: 1; }
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
#result-cards { display: flex; flex-direction: column; gap: 8px; }
|
|
472
|
+
|
|
473
|
+
.send-raw-row {
|
|
474
|
+
display: flex;
|
|
475
|
+
justify-content: flex-end;
|
|
476
|
+
padding: 4px 0;
|
|
477
|
+
}
|
|
478
|
+
.send-raw-row.hidden { display: none; }
|
|
479
|
+
|
|
480
|
+
.result-loading {
|
|
481
|
+
border: 1px solid var(--border);
|
|
482
|
+
border-radius: var(--radius);
|
|
483
|
+
background: color-mix(in srgb, var(--bg-card) 86%, var(--accent-subtle));
|
|
484
|
+
overflow: hidden;
|
|
485
|
+
box-shadow: 0 1px 2px rgba(0,0,0,0.06);
|
|
486
|
+
}
|
|
487
|
+
.result-loading-header {
|
|
488
|
+
display: flex;
|
|
489
|
+
align-items: center;
|
|
490
|
+
justify-content: space-between;
|
|
491
|
+
gap: 10px;
|
|
492
|
+
padding: 12px 14px 10px;
|
|
493
|
+
border-bottom: 1px solid var(--border);
|
|
494
|
+
}
|
|
495
|
+
.result-loading-title {
|
|
496
|
+
font-size: 12px;
|
|
497
|
+
font-weight: 600;
|
|
498
|
+
letter-spacing: 0.04em;
|
|
499
|
+
text-transform: uppercase;
|
|
500
|
+
color: var(--accent);
|
|
501
|
+
}
|
|
502
|
+
.result-loading-sub {
|
|
503
|
+
font-size: 12px;
|
|
504
|
+
color: var(--fg-dim);
|
|
505
|
+
font-variant-numeric: tabular-nums;
|
|
506
|
+
}
|
|
507
|
+
.result-loading-grid {
|
|
508
|
+
display: grid;
|
|
509
|
+
gap: 10px;
|
|
510
|
+
padding: 12px 14px 14px;
|
|
511
|
+
}
|
|
512
|
+
.loading-card {
|
|
513
|
+
border: 1px solid color-mix(in srgb, var(--border-muted) 80%, var(--accent-subtle));
|
|
514
|
+
border-radius: var(--radius-sm);
|
|
515
|
+
background: var(--bg-card);
|
|
516
|
+
overflow: hidden;
|
|
517
|
+
position: relative;
|
|
518
|
+
}
|
|
519
|
+
.loading-card::after {
|
|
520
|
+
content: "";
|
|
521
|
+
position: absolute;
|
|
522
|
+
inset: 0;
|
|
523
|
+
background: linear-gradient(105deg, transparent 10%, color-mix(in srgb, var(--accent) 18%, transparent) 45%, transparent 75%);
|
|
524
|
+
transform: translateX(-130%);
|
|
525
|
+
animation: loading-sweep 2s ease-in-out infinite;
|
|
526
|
+
pointer-events: none;
|
|
527
|
+
}
|
|
528
|
+
.loading-card-row {
|
|
529
|
+
height: 10px;
|
|
530
|
+
border-radius: 999px;
|
|
531
|
+
margin: 10px 12px;
|
|
532
|
+
background: color-mix(in srgb, var(--fg-dim) 35%, transparent);
|
|
533
|
+
}
|
|
534
|
+
.loading-card-row.short { width: 35%; }
|
|
535
|
+
.loading-card-row.mid { width: 58%; }
|
|
536
|
+
.loading-card-row.long { width: 78%; }
|
|
537
|
+
|
|
538
|
+
.result-card {
|
|
539
|
+
background: var(--bg-card);
|
|
540
|
+
border: 1px solid var(--border);
|
|
541
|
+
border-radius: var(--radius);
|
|
542
|
+
overflow: hidden;
|
|
543
|
+
transition: border-color 0.12s;
|
|
544
|
+
box-shadow: 0 1px 2px rgba(0,0,0,0.06);
|
|
545
|
+
}
|
|
546
|
+
.result-card.checked { border-color: var(--border-checked); }
|
|
547
|
+
.result-card.searching {
|
|
548
|
+
opacity: 1;
|
|
549
|
+
border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
|
|
550
|
+
background: linear-gradient(180deg, color-mix(in srgb, var(--accent-subtle) 70%, var(--bg-card)) 0%, var(--bg-card) 100%);
|
|
551
|
+
position: relative;
|
|
552
|
+
}
|
|
553
|
+
.result-card.searching::after {
|
|
554
|
+
content: "";
|
|
555
|
+
position: absolute;
|
|
556
|
+
inset: 0;
|
|
557
|
+
background: linear-gradient(110deg, transparent 20%, color-mix(in srgb, var(--accent) 14%, transparent) 50%, transparent 80%);
|
|
558
|
+
transform: translateX(-130%);
|
|
559
|
+
animation: loading-sweep 2.2s ease-in-out infinite;
|
|
560
|
+
pointer-events: none;
|
|
561
|
+
}
|
|
562
|
+
.result-card.searching .result-card-header { cursor: default; }
|
|
563
|
+
.result-card.searching .result-card-header:hover { background: transparent; }
|
|
564
|
+
.result-card.error { border-color: var(--timer-urgent-fg); }
|
|
565
|
+
|
|
566
|
+
.result-card-header {
|
|
567
|
+
display: flex;
|
|
568
|
+
align-items: flex-start;
|
|
569
|
+
gap: 12px;
|
|
570
|
+
padding: 14px 16px;
|
|
571
|
+
cursor: pointer;
|
|
572
|
+
user-select: none;
|
|
573
|
+
transition: background 0.12s;
|
|
574
|
+
}
|
|
575
|
+
.result-card-header:hover { background: var(--bg-hover); }
|
|
576
|
+
|
|
577
|
+
.result-card-header input[type="checkbox"] {
|
|
578
|
+
appearance: none;
|
|
579
|
+
width: 16px;
|
|
580
|
+
height: 16px;
|
|
581
|
+
min-width: 16px;
|
|
582
|
+
border: 1.5px solid var(--border-muted);
|
|
583
|
+
border-radius: 4px;
|
|
584
|
+
margin-top: 2px;
|
|
585
|
+
cursor: pointer;
|
|
586
|
+
transition: background 0.12s, border-color 0.12s;
|
|
587
|
+
display: grid;
|
|
588
|
+
place-content: center;
|
|
589
|
+
}
|
|
590
|
+
.result-card-header input[type="checkbox"]:checked {
|
|
591
|
+
background: var(--check-bg);
|
|
592
|
+
border-color: var(--check-bg);
|
|
593
|
+
}
|
|
594
|
+
.result-card-header input[type="checkbox"]:checked::after {
|
|
595
|
+
content: "";
|
|
596
|
+
width: 9px;
|
|
597
|
+
height: 6px;
|
|
598
|
+
border-left: 2px solid var(--btn-primary-fg);
|
|
599
|
+
border-bottom: 2px solid var(--btn-primary-fg);
|
|
600
|
+
transform: rotate(-45deg);
|
|
601
|
+
margin-top: -1px;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.result-card-info { flex: 1; min-width: 0; }
|
|
605
|
+
|
|
606
|
+
.result-card-query-row {
|
|
607
|
+
display: flex;
|
|
608
|
+
align-items: center;
|
|
609
|
+
gap: 8px;
|
|
610
|
+
flex-wrap: wrap;
|
|
611
|
+
margin-bottom: 2px;
|
|
612
|
+
}
|
|
613
|
+
.result-card-query {
|
|
614
|
+
font-size: 14px;
|
|
615
|
+
font-weight: 600;
|
|
616
|
+
color: var(--fg);
|
|
617
|
+
}
|
|
618
|
+
.provider-tag {
|
|
619
|
+
display: inline-flex;
|
|
620
|
+
align-items: center;
|
|
621
|
+
padding: 1px 7px;
|
|
622
|
+
border-radius: 999px;
|
|
623
|
+
font-size: 10px;
|
|
624
|
+
font-weight: 700;
|
|
625
|
+
letter-spacing: 0.03em;
|
|
626
|
+
text-transform: uppercase;
|
|
627
|
+
border: 1px solid transparent;
|
|
628
|
+
}
|
|
629
|
+
.provider-tag.provider-exa {
|
|
630
|
+
color: #8dd3ff;
|
|
631
|
+
background: rgba(141, 211, 255, 0.14);
|
|
632
|
+
border-color: rgba(141, 211, 255, 0.3);
|
|
633
|
+
}
|
|
634
|
+
.provider-tag.provider-perplexity {
|
|
635
|
+
color: #cba6f7;
|
|
636
|
+
background: rgba(203, 166, 247, 0.14);
|
|
637
|
+
border-color: rgba(203, 166, 247, 0.3);
|
|
638
|
+
}
|
|
639
|
+
.provider-tag.provider-gemini {
|
|
640
|
+
color: #f5c27b;
|
|
641
|
+
background: rgba(245, 194, 123, 0.14);
|
|
642
|
+
border-color: rgba(245, 194, 123, 0.3);
|
|
643
|
+
}
|
|
644
|
+
.provider-tag.provider-unknown {
|
|
645
|
+
color: var(--fg-muted);
|
|
646
|
+
background: var(--bg-elevated);
|
|
647
|
+
border-color: var(--border-muted);
|
|
648
|
+
}
|
|
649
|
+
.result-card-meta {
|
|
650
|
+
font-size: 12px;
|
|
651
|
+
color: var(--fg-dim);
|
|
652
|
+
}
|
|
653
|
+
.result-card-preview {
|
|
654
|
+
font-size: 12.5px;
|
|
655
|
+
color: var(--fg-muted);
|
|
656
|
+
margin-top: 6px;
|
|
657
|
+
display: -webkit-box;
|
|
658
|
+
-webkit-line-clamp: 2;
|
|
659
|
+
-webkit-box-orient: vertical;
|
|
660
|
+
overflow: hidden;
|
|
661
|
+
line-height: 1.45;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
.result-card-expand {
|
|
665
|
+
color: var(--fg-dim);
|
|
666
|
+
font-size: 11px;
|
|
667
|
+
margin-top: 2px;
|
|
668
|
+
flex-shrink: 0;
|
|
669
|
+
padding-top: 3px;
|
|
670
|
+
transition: color 0.12s;
|
|
671
|
+
}
|
|
672
|
+
.result-card-header:hover .result-card-expand { color: var(--fg-muted); }
|
|
673
|
+
|
|
674
|
+
.result-card-body {
|
|
675
|
+
display: none;
|
|
676
|
+
border-top: 1px solid var(--border);
|
|
677
|
+
}
|
|
678
|
+
.result-card-body.open { display: block; }
|
|
679
|
+
|
|
680
|
+
.result-card-answer {
|
|
681
|
+
padding: 14px 16px;
|
|
682
|
+
font-size: 13.5px;
|
|
683
|
+
color: var(--fg-muted);
|
|
684
|
+
line-height: 1.6;
|
|
685
|
+
max-height: 400px;
|
|
686
|
+
overflow-y: auto;
|
|
687
|
+
}
|
|
688
|
+
.result-card-answer h1,
|
|
689
|
+
.result-card-answer h2,
|
|
690
|
+
.result-card-answer h3,
|
|
691
|
+
.result-card-answer h4 {
|
|
692
|
+
color: var(--fg);
|
|
693
|
+
font-family: var(--font);
|
|
694
|
+
font-weight: 600;
|
|
695
|
+
margin: 16px 0 6px;
|
|
696
|
+
line-height: 1.3;
|
|
697
|
+
}
|
|
698
|
+
.result-card-answer h1 { font-size: 16px; }
|
|
699
|
+
.result-card-answer h2 { font-size: 14.5px; }
|
|
700
|
+
.result-card-answer h3 { font-size: 13.5px; }
|
|
701
|
+
.result-card-answer h4 { font-size: 13px; color: var(--fg-muted); }
|
|
702
|
+
.result-card-answer p { margin: 0 0 10px; }
|
|
703
|
+
.result-card-answer p:last-child { margin-bottom: 0; }
|
|
704
|
+
.result-card-answer strong { color: var(--fg); font-weight: 600; }
|
|
705
|
+
.result-card-answer a { color: var(--accent); text-decoration: none; }
|
|
706
|
+
.result-card-answer a:hover { text-decoration: underline; }
|
|
707
|
+
.result-card-answer ul, .result-card-answer ol {
|
|
708
|
+
margin: 6px 0 10px;
|
|
709
|
+
padding-left: 20px;
|
|
710
|
+
}
|
|
711
|
+
.result-card-answer li { margin-bottom: 4px; }
|
|
712
|
+
.result-card-answer li::marker { color: var(--fg-dim); }
|
|
713
|
+
.result-card-answer code {
|
|
714
|
+
font-family: var(--font-mono);
|
|
715
|
+
font-size: 12px;
|
|
716
|
+
padding: 1px 5px;
|
|
717
|
+
background: var(--bg-elevated);
|
|
718
|
+
border: 1px solid var(--border);
|
|
719
|
+
border-radius: 3px;
|
|
720
|
+
color: var(--fg);
|
|
721
|
+
}
|
|
722
|
+
.result-card-answer pre {
|
|
723
|
+
margin: 8px 0 12px;
|
|
724
|
+
padding: 12px 14px;
|
|
725
|
+
background: var(--bg);
|
|
726
|
+
border: 1px solid var(--border);
|
|
727
|
+
border-radius: var(--radius-sm);
|
|
728
|
+
overflow-x: auto;
|
|
729
|
+
line-height: 1.45;
|
|
730
|
+
}
|
|
731
|
+
.result-card-answer pre code {
|
|
732
|
+
padding: 0;
|
|
733
|
+
background: none;
|
|
734
|
+
border: none;
|
|
735
|
+
font-size: 12px;
|
|
736
|
+
color: var(--fg-muted);
|
|
737
|
+
}
|
|
738
|
+
.result-card-answer blockquote {
|
|
739
|
+
margin: 8px 0;
|
|
740
|
+
padding: 8px 14px;
|
|
741
|
+
border-left: 3px solid var(--accent);
|
|
742
|
+
color: var(--fg-dim);
|
|
743
|
+
background: var(--accent-subtle);
|
|
744
|
+
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
|
|
745
|
+
}
|
|
746
|
+
.result-card-answer table {
|
|
747
|
+
width: 100%;
|
|
748
|
+
border-collapse: collapse;
|
|
749
|
+
margin: 8px 0 12px;
|
|
750
|
+
font-size: 12.5px;
|
|
751
|
+
}
|
|
752
|
+
.result-card-answer th, .result-card-answer td {
|
|
753
|
+
padding: 6px 10px;
|
|
754
|
+
border: 1px solid var(--border);
|
|
755
|
+
text-align: left;
|
|
756
|
+
}
|
|
757
|
+
.result-card-answer th {
|
|
758
|
+
background: var(--bg-elevated);
|
|
759
|
+
color: var(--fg);
|
|
760
|
+
font-weight: 600;
|
|
761
|
+
font-size: 11.5px;
|
|
762
|
+
text-transform: uppercase;
|
|
763
|
+
letter-spacing: 0.03em;
|
|
764
|
+
}
|
|
765
|
+
.result-card-answer hr {
|
|
766
|
+
border: none;
|
|
767
|
+
border-top: 1px solid var(--border);
|
|
768
|
+
margin: 14px 0;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
.result-card-sources {
|
|
772
|
+
padding: 10px 16px 14px;
|
|
773
|
+
border-top: 1px solid var(--border);
|
|
774
|
+
}
|
|
775
|
+
.result-card-sources-title {
|
|
776
|
+
font-size: 11px;
|
|
777
|
+
font-weight: 600;
|
|
778
|
+
text-transform: uppercase;
|
|
779
|
+
letter-spacing: 0.06em;
|
|
780
|
+
color: var(--fg-dim);
|
|
781
|
+
margin-bottom: 6px;
|
|
782
|
+
}
|
|
783
|
+
.source-link {
|
|
784
|
+
display: block;
|
|
785
|
+
padding: 4px 0;
|
|
786
|
+
font-size: 12.5px;
|
|
787
|
+
color: var(--fg-muted);
|
|
788
|
+
text-decoration: none;
|
|
789
|
+
overflow: hidden;
|
|
790
|
+
text-overflow: ellipsis;
|
|
791
|
+
white-space: nowrap;
|
|
792
|
+
transition: color 0.12s;
|
|
793
|
+
}
|
|
794
|
+
.source-link:hover { color: var(--accent); }
|
|
795
|
+
.source-domain {
|
|
796
|
+
color: var(--fg-dim);
|
|
797
|
+
margin-left: 6px;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
.result-card-error-msg {
|
|
801
|
+
padding: 12px 16px;
|
|
802
|
+
font-size: 13px;
|
|
803
|
+
color: var(--timer-urgent-fg);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
.card-alt-providers {
|
|
807
|
+
display: flex;
|
|
808
|
+
align-items: center;
|
|
809
|
+
gap: 6px;
|
|
810
|
+
padding: 4px 16px 8px 42px;
|
|
811
|
+
font-size: 11px;
|
|
812
|
+
color: var(--fg-dim);
|
|
813
|
+
}
|
|
814
|
+
.card-alt-chip {
|
|
815
|
+
font-family: var(--font);
|
|
816
|
+
font-size: 10px;
|
|
817
|
+
font-weight: 600;
|
|
818
|
+
padding: 2px 8px;
|
|
819
|
+
border-radius: 999px;
|
|
820
|
+
border: 1px solid var(--border-muted);
|
|
821
|
+
background: transparent;
|
|
822
|
+
color: var(--fg-muted);
|
|
823
|
+
cursor: pointer;
|
|
824
|
+
transition: border-color 0.12s, color 0.12s, background 0.12s;
|
|
825
|
+
}
|
|
826
|
+
.card-alt-chip:hover:not(:disabled) {
|
|
827
|
+
color: var(--accent);
|
|
828
|
+
border-color: var(--accent);
|
|
829
|
+
}
|
|
830
|
+
.card-alt-chip:disabled {
|
|
831
|
+
opacity: 0.4;
|
|
832
|
+
cursor: default;
|
|
833
|
+
}
|
|
834
|
+
.card-alt-chip.loading {
|
|
835
|
+
opacity: 0.6;
|
|
836
|
+
pointer-events: none;
|
|
837
|
+
}
|
|
838
|
+
.card-alt-chip.loading::after {
|
|
839
|
+
content: " …";
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
.searching-dots::after {
|
|
843
|
+
content: "";
|
|
844
|
+
animation: dots 1.5s steps(4, end) infinite;
|
|
845
|
+
}
|
|
846
|
+
@keyframes dots {
|
|
847
|
+
0% { content: ""; }
|
|
848
|
+
25% { content: "."; }
|
|
849
|
+
50% { content: ".."; }
|
|
850
|
+
75% { content: "..."; }
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
@keyframes loading-sweep {
|
|
854
|
+
0% { transform: translateX(-130%); }
|
|
855
|
+
100% { transform: translateX(130%); }
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
@keyframes summary-pulse {
|
|
859
|
+
0%, 100% {
|
|
860
|
+
transform: scale(0.9);
|
|
861
|
+
box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 35%, transparent);
|
|
862
|
+
}
|
|
863
|
+
50% {
|
|
864
|
+
transform: scale(1.15);
|
|
865
|
+
box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent) 0%, transparent);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
@keyframes summary-sweep {
|
|
870
|
+
0% { transform: translateX(-100%); }
|
|
871
|
+
100% { transform: translateX(120%); }
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
@keyframes summary-panel-sweep {
|
|
875
|
+
0% { transform: translateX(-115%); }
|
|
876
|
+
100% { transform: translateX(115%); }
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
.add-search {
|
|
880
|
+
display: flex;
|
|
881
|
+
align-items: center;
|
|
882
|
+
gap: 10px;
|
|
883
|
+
margin-top: 12px;
|
|
884
|
+
padding: 11px 14px;
|
|
885
|
+
border: 1px dashed var(--border);
|
|
886
|
+
border-radius: var(--radius);
|
|
887
|
+
cursor: text;
|
|
888
|
+
transition: border-color 0.15s, background 0.15s;
|
|
889
|
+
}
|
|
890
|
+
.add-search:hover {
|
|
891
|
+
border-color: var(--border-muted);
|
|
892
|
+
background: var(--accent-subtle);
|
|
893
|
+
}
|
|
894
|
+
.add-search:focus-within {
|
|
895
|
+
border-color: var(--accent);
|
|
896
|
+
border-style: solid;
|
|
897
|
+
background: var(--accent-subtle);
|
|
898
|
+
}
|
|
899
|
+
.add-search-icon {
|
|
900
|
+
color: var(--fg-dim);
|
|
901
|
+
font-size: 16px;
|
|
902
|
+
font-weight: 300;
|
|
903
|
+
line-height: 1;
|
|
904
|
+
flex-shrink: 0;
|
|
905
|
+
transition: color 0.15s;
|
|
906
|
+
}
|
|
907
|
+
.add-search:focus-within .add-search-icon { color: var(--accent); }
|
|
908
|
+
.add-search input {
|
|
909
|
+
flex: 1;
|
|
910
|
+
background: transparent;
|
|
911
|
+
border: none;
|
|
912
|
+
outline: none;
|
|
913
|
+
color: var(--fg);
|
|
914
|
+
font-family: var(--font);
|
|
915
|
+
font-size: 13.5px;
|
|
916
|
+
font-weight: 500;
|
|
917
|
+
}
|
|
918
|
+
.add-search input::placeholder {
|
|
919
|
+
color: var(--fg-dim);
|
|
920
|
+
font-weight: 400;
|
|
921
|
+
}
|
|
922
|
+
.add-search-wand {
|
|
923
|
+
flex-shrink: 0;
|
|
924
|
+
width: 26px;
|
|
925
|
+
height: 26px;
|
|
926
|
+
display: flex;
|
|
927
|
+
align-items: center;
|
|
928
|
+
justify-content: center;
|
|
929
|
+
border: 1px solid var(--border-muted);
|
|
930
|
+
border-radius: 6px;
|
|
931
|
+
background: transparent;
|
|
932
|
+
color: var(--fg-dim);
|
|
933
|
+
font-size: 14px;
|
|
934
|
+
cursor: pointer;
|
|
935
|
+
transition: color 0.12s, border-color 0.12s, background 0.12s;
|
|
936
|
+
}
|
|
937
|
+
.add-search-wand:hover:not(:disabled) {
|
|
938
|
+
color: var(--accent);
|
|
939
|
+
border-color: var(--accent);
|
|
940
|
+
background: var(--accent-subtle);
|
|
941
|
+
}
|
|
942
|
+
.add-search-wand:disabled {
|
|
943
|
+
opacity: 0.3;
|
|
944
|
+
cursor: default;
|
|
945
|
+
}
|
|
946
|
+
.add-search-wand.rewriting {
|
|
947
|
+
pointer-events: none;
|
|
948
|
+
animation: wand-spin 0.8s linear infinite;
|
|
949
|
+
}
|
|
950
|
+
@keyframes wand-spin {
|
|
951
|
+
0% { transform: rotate(0deg); }
|
|
952
|
+
100% { transform: rotate(360deg); }
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
.summary-panel {
|
|
956
|
+
margin-top: 14px;
|
|
957
|
+
border: 1px solid var(--border);
|
|
958
|
+
border-radius: var(--radius);
|
|
959
|
+
background: var(--bg-card);
|
|
960
|
+
padding: 14px;
|
|
961
|
+
display: flex;
|
|
962
|
+
flex-direction: column;
|
|
963
|
+
gap: 10px;
|
|
964
|
+
}
|
|
965
|
+
.summary-panel.hidden { display: none; }
|
|
966
|
+
.summary-header { display: flex; flex-direction: column; gap: 2px; }
|
|
967
|
+
.summary-header-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
|
|
968
|
+
.summary-title {
|
|
969
|
+
font-size: 14px;
|
|
970
|
+
font-weight: 600;
|
|
971
|
+
color: var(--fg);
|
|
972
|
+
}
|
|
973
|
+
.summary-subtitle {
|
|
974
|
+
font-size: 12px;
|
|
975
|
+
color: var(--fg-dim);
|
|
976
|
+
}
|
|
977
|
+
.summary-generating {
|
|
978
|
+
position: relative;
|
|
979
|
+
isolation: isolate;
|
|
980
|
+
overflow: hidden;
|
|
981
|
+
border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--border));
|
|
982
|
+
border-radius: var(--radius-sm);
|
|
983
|
+
background: linear-gradient(130deg, color-mix(in srgb, var(--accent-subtle) 78%, transparent) 0%, var(--bg-elevated) 70%);
|
|
984
|
+
padding: 12px;
|
|
985
|
+
display: flex;
|
|
986
|
+
flex-direction: column;
|
|
987
|
+
gap: 10px;
|
|
988
|
+
}
|
|
989
|
+
.summary-generating::before {
|
|
990
|
+
content: "";
|
|
991
|
+
position: absolute;
|
|
992
|
+
inset: 0;
|
|
993
|
+
background: linear-gradient(110deg, transparent 0%, color-mix(in srgb, var(--accent) 16%, transparent) 50%, transparent 100%);
|
|
994
|
+
transform: translateX(-115%);
|
|
995
|
+
animation: summary-panel-sweep 2.4s ease-in-out infinite;
|
|
996
|
+
pointer-events: none;
|
|
997
|
+
}
|
|
998
|
+
.summary-generating > * {
|
|
999
|
+
position: relative;
|
|
1000
|
+
z-index: 1;
|
|
1001
|
+
}
|
|
1002
|
+
.summary-generating.hidden { display: none; }
|
|
1003
|
+
.summary-generating-head {
|
|
1004
|
+
display: flex;
|
|
1005
|
+
align-items: center;
|
|
1006
|
+
gap: 8px;
|
|
1007
|
+
font-size: 12px;
|
|
1008
|
+
font-weight: 600;
|
|
1009
|
+
color: var(--accent-hover);
|
|
1010
|
+
}
|
|
1011
|
+
.summary-generating-orb {
|
|
1012
|
+
width: 10px;
|
|
1013
|
+
height: 10px;
|
|
1014
|
+
border-radius: 999px;
|
|
1015
|
+
background: var(--accent);
|
|
1016
|
+
box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 35%, transparent);
|
|
1017
|
+
animation: summary-pulse 1.1s ease-in-out infinite;
|
|
1018
|
+
}
|
|
1019
|
+
.summary-generating-bars {
|
|
1020
|
+
display: grid;
|
|
1021
|
+
gap: 6px;
|
|
1022
|
+
}
|
|
1023
|
+
.summary-generating-bar {
|
|
1024
|
+
position: relative;
|
|
1025
|
+
display: block;
|
|
1026
|
+
height: 8px;
|
|
1027
|
+
border-radius: 999px;
|
|
1028
|
+
background: color-mix(in srgb, var(--bg) 65%, var(--bg-elevated));
|
|
1029
|
+
overflow: hidden;
|
|
1030
|
+
transition: width 220ms ease;
|
|
1031
|
+
}
|
|
1032
|
+
.summary-generating-bar::after {
|
|
1033
|
+
content: "";
|
|
1034
|
+
position: absolute;
|
|
1035
|
+
inset: 0;
|
|
1036
|
+
transform: translateX(-100%);
|
|
1037
|
+
background: linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--accent) 45%, transparent) 50%, transparent 100%);
|
|
1038
|
+
animation: summary-sweep 1.6s ease-in-out infinite;
|
|
1039
|
+
}
|
|
1040
|
+
.summary-generating-bar.b1 { width: 86%; }
|
|
1041
|
+
.summary-generating-bar.b2 { width: 68%; }
|
|
1042
|
+
.summary-generating-bar.b3 { width: 74%; }
|
|
1043
|
+
.summary-generating[data-phase="1"] .summary-generating-bar.b1 { width: 72%; }
|
|
1044
|
+
.summary-generating[data-phase="1"] .summary-generating-bar.b2 { width: 82%; }
|
|
1045
|
+
.summary-generating[data-phase="1"] .summary-generating-bar.b3 { width: 60%; }
|
|
1046
|
+
.summary-generating[data-phase="2"] .summary-generating-bar.b1 { width: 64%; }
|
|
1047
|
+
.summary-generating[data-phase="2"] .summary-generating-bar.b2 { width: 71%; }
|
|
1048
|
+
.summary-generating[data-phase="2"] .summary-generating-bar.b3 { width: 90%; }
|
|
1049
|
+
.summary-generating-bar.b2::after { animation-delay: 0.15s; }
|
|
1050
|
+
.summary-generating-bar.b3::after { animation-delay: 0.3s; }
|
|
1051
|
+
.summary-input {
|
|
1052
|
+
width: 100%;
|
|
1053
|
+
min-height: 180px;
|
|
1054
|
+
resize: vertical;
|
|
1055
|
+
border: 1px solid var(--border-muted);
|
|
1056
|
+
border-radius: var(--radius-sm);
|
|
1057
|
+
padding: 10px 12px;
|
|
1058
|
+
font-family: var(--font);
|
|
1059
|
+
font-size: 13px;
|
|
1060
|
+
line-height: 1.5;
|
|
1061
|
+
color: var(--fg);
|
|
1062
|
+
background: var(--bg-elevated);
|
|
1063
|
+
outline: none;
|
|
1064
|
+
}
|
|
1065
|
+
.summary-input.hidden { display: none; }
|
|
1066
|
+
.summary-input:focus {
|
|
1067
|
+
border-color: var(--accent);
|
|
1068
|
+
}
|
|
1069
|
+
.summary-feedback-row {
|
|
1070
|
+
display: flex;
|
|
1071
|
+
align-items: center;
|
|
1072
|
+
gap: 8px;
|
|
1073
|
+
margin-top: 6px;
|
|
1074
|
+
}
|
|
1075
|
+
.summary-feedback {
|
|
1076
|
+
flex: 1;
|
|
1077
|
+
height: 32px;
|
|
1078
|
+
border: 1px solid var(--border-muted);
|
|
1079
|
+
border-radius: var(--radius-sm);
|
|
1080
|
+
padding: 4px 10px;
|
|
1081
|
+
font-family: var(--font);
|
|
1082
|
+
font-size: 12px;
|
|
1083
|
+
color: var(--fg);
|
|
1084
|
+
background: var(--bg-elevated);
|
|
1085
|
+
outline: none;
|
|
1086
|
+
}
|
|
1087
|
+
.summary-feedback:focus {
|
|
1088
|
+
border-color: var(--accent);
|
|
1089
|
+
}
|
|
1090
|
+
.summary-feedback::placeholder {
|
|
1091
|
+
color: var(--fg-muted);
|
|
1092
|
+
}
|
|
1093
|
+
.summary-actions {
|
|
1094
|
+
display: flex;
|
|
1095
|
+
align-items: center;
|
|
1096
|
+
justify-content: flex-end;
|
|
1097
|
+
gap: 8px;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
.action-bar {
|
|
1101
|
+
position: fixed;
|
|
1102
|
+
bottom: 0;
|
|
1103
|
+
left: 0;
|
|
1104
|
+
right: 0;
|
|
1105
|
+
z-index: 10;
|
|
1106
|
+
display: flex;
|
|
1107
|
+
align-items: center;
|
|
1108
|
+
justify-content: space-between;
|
|
1109
|
+
padding: 12px 24px;
|
|
1110
|
+
background: color-mix(in srgb, var(--bg) 90%, transparent);
|
|
1111
|
+
backdrop-filter: blur(12px);
|
|
1112
|
+
-webkit-backdrop-filter: blur(12px);
|
|
1113
|
+
border-top: 1px solid var(--border);
|
|
1114
|
+
}
|
|
1115
|
+
.action-shortcuts { display: flex; align-items: center; gap: 16px; }
|
|
1116
|
+
.shortcut { display: flex; align-items: center; gap: 5px; font-size: 11px; color: var(--fg-dim); }
|
|
1117
|
+
.shortcut kbd {
|
|
1118
|
+
display: inline-flex;
|
|
1119
|
+
align-items: center;
|
|
1120
|
+
justify-content: center;
|
|
1121
|
+
min-width: 18px;
|
|
1122
|
+
height: 18px;
|
|
1123
|
+
padding: 0 4px;
|
|
1124
|
+
font-family: var(--font-mono);
|
|
1125
|
+
font-size: 10px;
|
|
1126
|
+
font-weight: 500;
|
|
1127
|
+
background: var(--bg-elevated);
|
|
1128
|
+
border: 1px solid var(--border-muted);
|
|
1129
|
+
border-radius: 3px;
|
|
1130
|
+
color: var(--fg-muted);
|
|
1131
|
+
}
|
|
1132
|
+
.action-buttons { display: flex; gap: 8px; }
|
|
1133
|
+
|
|
1134
|
+
.btn {
|
|
1135
|
+
font-family: var(--font);
|
|
1136
|
+
font-size: 13px;
|
|
1137
|
+
font-weight: 500;
|
|
1138
|
+
padding: 7px 16px;
|
|
1139
|
+
border: none;
|
|
1140
|
+
border-radius: var(--radius-sm);
|
|
1141
|
+
cursor: pointer;
|
|
1142
|
+
transition: background 0.12s, opacity 0.12s;
|
|
1143
|
+
}
|
|
1144
|
+
.btn:disabled { opacity: 0.35; cursor: default; }
|
|
1145
|
+
.btn-submit { background: var(--btn-primary); color: var(--btn-primary-fg); }
|
|
1146
|
+
.btn-submit:hover:not(:disabled) { background: var(--btn-primary-hover); }
|
|
1147
|
+
.btn-secondary { background: var(--btn-secondary); color: var(--fg-muted); border: 1px solid var(--border); }
|
|
1148
|
+
.btn-secondary:hover:not(:disabled) { background: var(--btn-secondary-hover); color: var(--fg); }
|
|
1149
|
+
|
|
1150
|
+
.success-overlay {
|
|
1151
|
+
position: fixed; inset: 0; z-index: 200;
|
|
1152
|
+
background: var(--overlay-bg);
|
|
1153
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px;
|
|
1154
|
+
transition: opacity 200ms;
|
|
1155
|
+
}
|
|
1156
|
+
.success-overlay.hidden { display: flex !important; opacity: 0; pointer-events: none; }
|
|
1157
|
+
.success-icon {
|
|
1158
|
+
width: 56px; height: 56px; border-radius: 50%;
|
|
1159
|
+
border: 2px solid var(--success);
|
|
1160
|
+
display: flex; align-items: center; justify-content: center;
|
|
1161
|
+
font-size: 18px; font-weight: 700; color: var(--success);
|
|
1162
|
+
}
|
|
1163
|
+
.success-overlay p { margin: 0; font-size: 13px; font-weight: 600; color: var(--success); letter-spacing: 0.06em; text-transform: uppercase; }
|
|
1164
|
+
|
|
1165
|
+
.expired-overlay {
|
|
1166
|
+
position: fixed; inset: 0;
|
|
1167
|
+
background: var(--overlay-bg);
|
|
1168
|
+
display: flex; align-items: center; justify-content: center;
|
|
1169
|
+
opacity: 0; transition: opacity 400ms; pointer-events: none; z-index: 200;
|
|
1170
|
+
}
|
|
1171
|
+
.expired-overlay.visible { opacity: 1; pointer-events: auto; }
|
|
1172
|
+
.expired-overlay.hidden { display: flex !important; opacity: 0; pointer-events: none; }
|
|
1173
|
+
.expired-content {
|
|
1174
|
+
text-align: center; max-width: 480px; padding: 48px 56px;
|
|
1175
|
+
background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px;
|
|
1176
|
+
}
|
|
1177
|
+
.expired-overlay.visible .expired-content { animation: slide-up 400ms ease-out; }
|
|
1178
|
+
@keyframes slide-up { from { transform: translateY(20px); } to { transform: translateY(0); } }
|
|
1179
|
+
.expired-icon {
|
|
1180
|
+
width: 72px; height: 72px; border-radius: 50%; border: 2px solid var(--warning);
|
|
1181
|
+
display: flex; align-items: center; justify-content: center;
|
|
1182
|
+
font-size: 32px; font-weight: bold; color: var(--warning); margin: 0 auto 24px;
|
|
1183
|
+
}
|
|
1184
|
+
.expired-content h2 { color: var(--fg); margin: 0 0 16px; font-size: 22px; font-weight: 600; }
|
|
1185
|
+
.expired-content p { color: var(--fg-muted); margin: 0 0 24px; font-size: 14px; line-height: 1.6; }
|
|
1186
|
+
.expired-countdown { font-size: 13px; color: var(--fg-dim); font-variant-numeric: tabular-nums; }
|
|
1187
|
+
.expired-countdown span { color: var(--warning); font-weight: 600; }
|
|
1188
|
+
|
|
1189
|
+
.preview-modal {
|
|
1190
|
+
position: fixed; inset: 0; z-index: 250;
|
|
1191
|
+
background: var(--overlay-bg);
|
|
1192
|
+
display: flex; align-items: center; justify-content: center;
|
|
1193
|
+
animation: fade-in 150ms ease-out;
|
|
1194
|
+
}
|
|
1195
|
+
.preview-modal.hidden { display: none; }
|
|
1196
|
+
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
|
|
1197
|
+
.preview-modal-inner {
|
|
1198
|
+
width: min(720px, calc(100% - 48px));
|
|
1199
|
+
max-height: calc(100vh - 80px);
|
|
1200
|
+
background: var(--bg-card);
|
|
1201
|
+
border: 1px solid var(--border);
|
|
1202
|
+
border-radius: 12px;
|
|
1203
|
+
display: flex; flex-direction: column;
|
|
1204
|
+
animation: slide-up 200ms ease-out;
|
|
1205
|
+
}
|
|
1206
|
+
.preview-modal-header {
|
|
1207
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
1208
|
+
padding: 16px 20px;
|
|
1209
|
+
border-bottom: 1px solid var(--border);
|
|
1210
|
+
flex-shrink: 0;
|
|
1211
|
+
}
|
|
1212
|
+
.preview-modal-title { font-size: 14px; font-weight: 600; color: var(--fg); margin: 0; }
|
|
1213
|
+
.preview-modal-close {
|
|
1214
|
+
background: none; border: none; cursor: pointer;
|
|
1215
|
+
font-size: 22px; line-height: 1; color: var(--fg-muted); padding: 0 4px;
|
|
1216
|
+
transition: color 0.12s;
|
|
1217
|
+
}
|
|
1218
|
+
.preview-modal-close:hover { color: var(--fg); }
|
|
1219
|
+
.preview-modal-body {
|
|
1220
|
+
position: relative;
|
|
1221
|
+
padding: 24px 28px;
|
|
1222
|
+
overflow-y: auto;
|
|
1223
|
+
font-size: 14px; line-height: 1.7; color: var(--fg);
|
|
1224
|
+
}
|
|
1225
|
+
.preview-modal-body h1 { font-size: 20px; font-weight: 600; margin: 1.2em 0 0.5em; color: var(--fg); }
|
|
1226
|
+
.preview-modal-body h2 { font-size: 16px; font-weight: 600; margin: 1.2em 0 0.4em; color: var(--fg); }
|
|
1227
|
+
.preview-modal-body h3 { font-size: 14px; font-weight: 600; margin: 1em 0 0.3em; color: var(--fg); }
|
|
1228
|
+
.preview-modal-body p { margin: 0.6em 0; }
|
|
1229
|
+
.preview-modal-body a { color: var(--accent); }
|
|
1230
|
+
.preview-modal-body pre { background: var(--bg-elevated); padding: 14px; border-radius: var(--radius-sm); overflow-x: auto; }
|
|
1231
|
+
.preview-modal-body code { font-size: 0.9em; }
|
|
1232
|
+
.preview-modal-body blockquote { border-left: 3px solid var(--border); padding-left: 14px; color: var(--fg-muted); margin: 0.6em 0; }
|
|
1233
|
+
.preview-modal-body hr { border: none; border-top: 1px solid var(--border); margin: 1.5em 0; }
|
|
1234
|
+
.preview-modal-body ul, .preview-modal-body ol { padding-left: 1.4em; }
|
|
1235
|
+
.preview-modal-body li + li { margin-top: 0.25em; }
|
|
1236
|
+
.preview-modal-body strong { color: var(--fg); }
|
|
1237
|
+
.preview-modal-footer {
|
|
1238
|
+
padding: 12px 20px;
|
|
1239
|
+
border-top: 1px solid var(--border);
|
|
1240
|
+
display: flex; align-items: center; gap: 8px;
|
|
1241
|
+
flex-shrink: 0;
|
|
1242
|
+
}
|
|
1243
|
+
.preview-modal-model {
|
|
1244
|
+
margin-right: auto;
|
|
1245
|
+
font-family: var(--font);
|
|
1246
|
+
font-size: 11px;
|
|
1247
|
+
color: var(--fg-muted);
|
|
1248
|
+
background: var(--bg-elevated);
|
|
1249
|
+
border: 1px solid var(--border-muted);
|
|
1250
|
+
border-radius: var(--radius-sm);
|
|
1251
|
+
padding: 4px 8px;
|
|
1252
|
+
max-width: 220px;
|
|
1253
|
+
outline: none;
|
|
1254
|
+
}
|
|
1255
|
+
.preview-modal-model:focus { border-color: var(--accent); }
|
|
1256
|
+
|
|
1257
|
+
.preview-popover {
|
|
1258
|
+
position: absolute;
|
|
1259
|
+
z-index: 260;
|
|
1260
|
+
width: min(340px, calc(100% - 40px));
|
|
1261
|
+
background: var(--bg-elevated);
|
|
1262
|
+
border: 1px solid var(--accent);
|
|
1263
|
+
border-radius: var(--radius);
|
|
1264
|
+
padding: 10px 12px;
|
|
1265
|
+
display: flex; flex-direction: column; gap: 8px;
|
|
1266
|
+
box-shadow: 0 8px 24px rgba(0,0,0,0.35);
|
|
1267
|
+
animation: fade-in 100ms ease-out;
|
|
1268
|
+
}
|
|
1269
|
+
.preview-popover.hidden { display: none; }
|
|
1270
|
+
.preview-popover-quote {
|
|
1271
|
+
font-size: 12px;
|
|
1272
|
+
color: var(--fg-muted);
|
|
1273
|
+
font-style: italic;
|
|
1274
|
+
border-left: 2px solid var(--accent);
|
|
1275
|
+
padding-left: 8px;
|
|
1276
|
+
max-height: 48px;
|
|
1277
|
+
overflow: hidden;
|
|
1278
|
+
display: -webkit-box;
|
|
1279
|
+
-webkit-line-clamp: 2;
|
|
1280
|
+
-webkit-box-orient: vertical;
|
|
1281
|
+
}
|
|
1282
|
+
.preview-popover-input {
|
|
1283
|
+
font-family: var(--font);
|
|
1284
|
+
font-size: 13px;
|
|
1285
|
+
line-height: 1.4;
|
|
1286
|
+
color: var(--fg);
|
|
1287
|
+
background: var(--bg-card);
|
|
1288
|
+
border: 1px solid var(--border-muted);
|
|
1289
|
+
border-radius: var(--radius-sm);
|
|
1290
|
+
padding: 6px 10px;
|
|
1291
|
+
outline: none;
|
|
1292
|
+
width: 100%;
|
|
1293
|
+
resize: vertical;
|
|
1294
|
+
}
|
|
1295
|
+
.preview-popover-input:focus { border-color: var(--accent); }
|
|
1296
|
+
.preview-popover-btn { align-self: flex-end; font-size: 12px; padding: 5px 14px; }
|
|
1297
|
+
|
|
1298
|
+
.error-banner {
|
|
1299
|
+
position: fixed; bottom: 64px; left: 50%; transform: translateX(-50%); z-index: 50;
|
|
1300
|
+
padding: 10px 20px; background: var(--timer-urgent-bg); color: var(--timer-urgent-fg);
|
|
1301
|
+
border-radius: var(--radius); font-size: 13px; font-weight: 500;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
.summary-panel.updating {
|
|
1305
|
+
border-color: color-mix(in srgb, var(--accent) 35%, var(--border));
|
|
1306
|
+
position: relative;
|
|
1307
|
+
overflow: hidden;
|
|
1308
|
+
}
|
|
1309
|
+
.summary-panel.updating::after {
|
|
1310
|
+
content: "";
|
|
1311
|
+
position: absolute;
|
|
1312
|
+
top: 0;
|
|
1313
|
+
left: 0;
|
|
1314
|
+
width: 30%;
|
|
1315
|
+
height: 2px;
|
|
1316
|
+
border-radius: var(--radius) var(--radius) 0 0;
|
|
1317
|
+
background: linear-gradient(90deg, transparent, var(--accent), transparent);
|
|
1318
|
+
animation: updating-bar 1.8s ease-in-out infinite;
|
|
1319
|
+
pointer-events: none;
|
|
1320
|
+
}
|
|
1321
|
+
.summary-panel.updating .summary-input,
|
|
1322
|
+
.summary-panel.updating .summary-feedback-row {
|
|
1323
|
+
opacity: 0.45;
|
|
1324
|
+
pointer-events: none;
|
|
1325
|
+
}
|
|
1326
|
+
.summary-panel.updating .summary-actions {
|
|
1327
|
+
opacity: 0.72;
|
|
1328
|
+
}
|
|
1329
|
+
@keyframes updating-bar {
|
|
1330
|
+
0% { transform: translateX(-50%); }
|
|
1331
|
+
100% { transform: translateX(430%); }
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1335
|
+
.loading-card::after,
|
|
1336
|
+
.result-card.searching::after,
|
|
1337
|
+
.provider-btn.loading::after,
|
|
1338
|
+
.searching-dots::after,
|
|
1339
|
+
.summary-generating::before,
|
|
1340
|
+
.summary-generating-orb,
|
|
1341
|
+
.summary-generating-bar::after,
|
|
1342
|
+
.summary-panel.updating::after {
|
|
1343
|
+
animation: none !important;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
@media (max-width: 500px) {
|
|
1348
|
+
main { padding: 32px 16px 16px; }
|
|
1349
|
+
.hero-title { font-size: 28px; }
|
|
1350
|
+
.hero-desc { font-size: 13px; }
|
|
1351
|
+
.summary-header-top { flex-direction: column; }
|
|
1352
|
+
.summary-model-controls { flex-wrap: wrap; }
|
|
1353
|
+
.summary-model-dropdown { max-width: 100%; }
|
|
1354
|
+
.action-bar { padding: 10px 14px; }
|
|
1355
|
+
.action-shortcuts { display: none; }
|
|
1356
|
+
.result-card-header { padding: 12px 14px; }
|
|
1357
|
+
.expired-content { padding: 32px 24px; }
|
|
1358
|
+
.timer-badge { top: 12px; right: 16px; }
|
|
1359
|
+
}
|
|
1360
|
+
`;
|
|
1361
|
+
|
|
1362
|
+
const SCRIPT = `(function() {
|
|
1363
|
+
var DATA = __INLINE_DATA__;
|
|
1364
|
+
var token = DATA.sessionToken;
|
|
1365
|
+
var timeoutSec = DATA.timeout;
|
|
1366
|
+
var queries = Array.isArray(DATA.queries) ? DATA.queries : [];
|
|
1367
|
+
var providers = ["perplexity", "exa", "gemini"];
|
|
1368
|
+
var availProviders = DATA.availableProviders && typeof DATA.availableProviders === "object" ? DATA.availableProviders : {};
|
|
1369
|
+
var workflow = "summary-review";
|
|
1370
|
+
var initialDefaultProvider = typeof DATA.defaultProvider === "string" ? DATA.defaultProvider : "exa";
|
|
1371
|
+
if (providers.indexOf(initialDefaultProvider) === -1) initialDefaultProvider = "exa";
|
|
1372
|
+
|
|
1373
|
+
var summaryModels = Array.isArray(DATA.summaryModels)
|
|
1374
|
+
? DATA.summaryModels.filter(function(model) {
|
|
1375
|
+
return model && typeof model === "object" && typeof model.value === "string";
|
|
1376
|
+
})
|
|
1377
|
+
: [];
|
|
1378
|
+
var defaultSummaryModel = typeof DATA.defaultSummaryModel === "string"
|
|
1379
|
+
? DATA.defaultSummaryModel.trim()
|
|
1380
|
+
: "";
|
|
1381
|
+
|
|
1382
|
+
var submitted = false;
|
|
1383
|
+
var timerExpired = false;
|
|
1384
|
+
var submitInFlight = false;
|
|
1385
|
+
var searchesDone = false;
|
|
1386
|
+
var stage = "results";
|
|
1387
|
+
var summaryMeta = null;
|
|
1388
|
+
var summaryRequestSeq = 0;
|
|
1389
|
+
var lastAutoSummarySignature = "";
|
|
1390
|
+
var lastInteraction = Date.now();
|
|
1391
|
+
var completedCount = 0;
|
|
1392
|
+
var es = null;
|
|
1393
|
+
|
|
1394
|
+
var allQueries = queries.map(function(query, slotId) { return { slotId: slotId, query: query }; });
|
|
1395
|
+
var nextSlotId = queries.length;
|
|
1396
|
+
var queryIndexToSlot = new Map();
|
|
1397
|
+
var providerCoverage = new Map();
|
|
1398
|
+
|
|
1399
|
+
var currentProvider = initialDefaultProvider;
|
|
1400
|
+
var initialStreamDone = queries.length === 0;
|
|
1401
|
+
var providerBatchInFlight = false;
|
|
1402
|
+
var batchLoadingProvider = null;
|
|
1403
|
+
var addSearchInFlight = 0;
|
|
1404
|
+
var isRegenerating = false;
|
|
1405
|
+
|
|
1406
|
+
var timerEl = document.getElementById("timer");
|
|
1407
|
+
var timerAdjustEl = document.getElementById("timer-adjust");
|
|
1408
|
+
var timerInput = document.getElementById("timer-input");
|
|
1409
|
+
var timerSetBtn = document.getElementById("timer-set");
|
|
1410
|
+
var heroTitle = document.querySelector(".hero-title");
|
|
1411
|
+
var heroDesc = document.querySelector(".hero-desc");
|
|
1412
|
+
var resultCardsEl = document.getElementById("result-cards");
|
|
1413
|
+
var btnSend = document.getElementById("btn-send");
|
|
1414
|
+
var btnSendRaw = document.getElementById("btn-send-raw");
|
|
1415
|
+
var sendRawRow = document.getElementById("send-raw-row");
|
|
1416
|
+
var summaryPanel = document.getElementById("summary-panel");
|
|
1417
|
+
var summarySubtitle = document.getElementById("summary-subtitle");
|
|
1418
|
+
var summaryGeneratingEl = document.getElementById("summary-generating");
|
|
1419
|
+
var summaryGeneratingCopy = document.getElementById("summary-generating-copy");
|
|
1420
|
+
var summaryInput = document.getElementById("summary-input");
|
|
1421
|
+
var summaryFeedback = document.getElementById("summary-feedback");
|
|
1422
|
+
var btnSummaryBack = document.getElementById("btn-summary-back");
|
|
1423
|
+
var btnSummaryRegenerate = document.getElementById("btn-summary-regenerate");
|
|
1424
|
+
var btnSummaryPreview = document.getElementById("btn-summary-preview");
|
|
1425
|
+
var btnSummaryApprove = document.getElementById("btn-summary-approve");
|
|
1426
|
+
var successOverlay = document.getElementById("success-overlay");
|
|
1427
|
+
var successText = document.getElementById("success-text");
|
|
1428
|
+
var expiredOverlay = document.getElementById("expired-overlay");
|
|
1429
|
+
var expiredText = document.getElementById("expired-text");
|
|
1430
|
+
var closeCountdown = document.getElementById("close-countdown");
|
|
1431
|
+
var errorBanner = document.getElementById("error-banner");
|
|
1432
|
+
var addSearchInput = document.getElementById("add-search-input");
|
|
1433
|
+
var addSearchEl = document.getElementById("add-search");
|
|
1434
|
+
var addSearchWand = document.getElementById("add-search-wand");
|
|
1435
|
+
var heroStatus = document.getElementById("hero-status");
|
|
1436
|
+
var summaryProviderSelect = document.getElementById("summary-provider-select");
|
|
1437
|
+
var summaryModelSelect = document.getElementById("summary-model-select");
|
|
1438
|
+
var previewModal = document.getElementById("preview-modal");
|
|
1439
|
+
var previewModalBody = document.getElementById("preview-modal-body");
|
|
1440
|
+
var previewModalClose = document.getElementById("preview-modal-close");
|
|
1441
|
+
var previewModalModel = document.getElementById("preview-modal-model");
|
|
1442
|
+
var previewModalRegenerate = document.getElementById("preview-modal-regenerate");
|
|
1443
|
+
var previewModalApprove = document.getElementById("preview-modal-approve");
|
|
1444
|
+
var previewPopover = document.getElementById("preview-popover");
|
|
1445
|
+
var previewPopoverQuote = document.getElementById("preview-popover-quote");
|
|
1446
|
+
var previewPopoverInput = document.getElementById("preview-popover-input");
|
|
1447
|
+
var previewPopoverRegen = document.getElementById("preview-popover-regen");
|
|
1448
|
+
var providerButtons = Array.prototype.slice.call(document.querySelectorAll(".provider-btn"));
|
|
1449
|
+
var loadingPanelEl = null;
|
|
1450
|
+
|
|
1451
|
+
var summaryModelsByProvider = Object.create(null);
|
|
1452
|
+
var summaryProviders = [];
|
|
1453
|
+
var currentSummaryProvider = "";
|
|
1454
|
+
var currentSummaryModel = "";
|
|
1455
|
+
var summaryPendingModel = "";
|
|
1456
|
+
var summaryGeneratingStartedAt = 0;
|
|
1457
|
+
var summaryGeneratingPhase = -1;
|
|
1458
|
+
var rewriteInFlight = false;
|
|
1459
|
+
|
|
1460
|
+
function escHtml(s) {
|
|
1461
|
+
return String(s)
|
|
1462
|
+
.replace(/&/g, "&")
|
|
1463
|
+
.replace(/</g, "<")
|
|
1464
|
+
.replace(/>/g, ">")
|
|
1465
|
+
.replace(/\"/g, """);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
function sanitizeHref(url) {
|
|
1469
|
+
var value = typeof url === "string" ? url.trim() : "";
|
|
1470
|
+
return /^https?:\/\//i.test(value) ? value : "#";
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
function sanitizeMarkdownHtml(html) {
|
|
1474
|
+
var container = document.createElement("div");
|
|
1475
|
+
container.innerHTML = html;
|
|
1476
|
+
|
|
1477
|
+
container.querySelectorAll("script, iframe, object, embed, form, style, link, meta, base")
|
|
1478
|
+
.forEach(function(el) { el.remove(); });
|
|
1479
|
+
|
|
1480
|
+
var nodes = container.querySelectorAll("*");
|
|
1481
|
+
nodes.forEach(function(node) {
|
|
1482
|
+
for (var i = node.attributes.length - 1; i >= 0; i--) {
|
|
1483
|
+
var attr = node.attributes[i];
|
|
1484
|
+
if (/^on/i.test(attr.name)) node.removeAttribute(attr.name);
|
|
1485
|
+
}
|
|
1486
|
+
});
|
|
1487
|
+
|
|
1488
|
+
var anchors = container.querySelectorAll("a[href]");
|
|
1489
|
+
anchors.forEach(function(anchor) {
|
|
1490
|
+
var safe = sanitizeHref(anchor.getAttribute("href") || "");
|
|
1491
|
+
anchor.setAttribute("href", safe);
|
|
1492
|
+
anchor.setAttribute("rel", "noopener noreferrer");
|
|
1493
|
+
anchor.setAttribute("target", "_blank");
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
var images = container.querySelectorAll("img[src]");
|
|
1497
|
+
images.forEach(function(img) {
|
|
1498
|
+
var safe = sanitizeHref(img.getAttribute("src") || "");
|
|
1499
|
+
if (safe === "#") {
|
|
1500
|
+
img.remove();
|
|
1501
|
+
} else {
|
|
1502
|
+
img.setAttribute("src", safe);
|
|
1503
|
+
}
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1506
|
+
return container.innerHTML;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
function post(path, body) {
|
|
1510
|
+
return fetch(path, {
|
|
1511
|
+
method: "POST",
|
|
1512
|
+
headers: { "Content-Type": "application/json" },
|
|
1513
|
+
body: JSON.stringify(Object.assign({ token: token }, body)),
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
function extractServerError(data) {
|
|
1518
|
+
if (!data || typeof data !== "object") return "";
|
|
1519
|
+
if (typeof data.error === "string" && data.error.trim()) return data.error.trim();
|
|
1520
|
+
return "";
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
function postJson(path, body) {
|
|
1524
|
+
return post(path, body).then(function(res) {
|
|
1525
|
+
return res.text().then(function(raw) {
|
|
1526
|
+
var data = null;
|
|
1527
|
+
if (raw) {
|
|
1528
|
+
try {
|
|
1529
|
+
data = JSON.parse(raw);
|
|
1530
|
+
} catch (err) {
|
|
1531
|
+
var parseMessage = err instanceof Error ? err.message : String(err);
|
|
1532
|
+
throw new Error("Invalid JSON response from " + path + ": " + parseMessage);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
if (!res.ok) {
|
|
1537
|
+
throw new Error(extractServerError(data) || ("HTTP " + res.status));
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
return data;
|
|
1541
|
+
});
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
function formatTime(sec) {
|
|
1546
|
+
var m = Math.floor(sec / 60);
|
|
1547
|
+
var s = sec % 60;
|
|
1548
|
+
return m + ":" + (s < 10 ? "0" : "") + s;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
function normalizeProvider(provider, fallback) {
|
|
1552
|
+
if (typeof provider === "string") {
|
|
1553
|
+
var normalized = provider.toLowerCase();
|
|
1554
|
+
if (providers.indexOf(normalized) !== -1) return normalized;
|
|
1555
|
+
}
|
|
1556
|
+
if (typeof fallback === "string") {
|
|
1557
|
+
var fallbackNormalized = fallback.toLowerCase();
|
|
1558
|
+
if (providers.indexOf(fallbackNormalized) !== -1) return fallbackNormalized;
|
|
1559
|
+
}
|
|
1560
|
+
return "";
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
function providerLabel(provider) {
|
|
1564
|
+
if (provider === "perplexity") return "Perplexity";
|
|
1565
|
+
if (provider === "exa") return "Exa";
|
|
1566
|
+
if (provider === "gemini") return "Gemini";
|
|
1567
|
+
return "Unknown";
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
function providerTagHtml(provider) {
|
|
1571
|
+
var normalized = normalizeProvider(provider, "");
|
|
1572
|
+
if (!normalized) return "";
|
|
1573
|
+
return '<span class="provider-tag provider-' + normalized + '">' + escHtml(providerLabel(normalized)) + "</span>";
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
function buildAltChipsHtml(provider, queryText) {
|
|
1577
|
+
var normalizedProv = normalizeProvider(provider, "");
|
|
1578
|
+
if (!normalizedProv) return "";
|
|
1579
|
+
var altProviders = providers.filter(function(p) { return p !== normalizedProv && availProviders[p] === true; });
|
|
1580
|
+
if (altProviders.length === 0) return "";
|
|
1581
|
+
var html = '<div class="card-alt-providers"><span>Also try</span>';
|
|
1582
|
+
for (var ap = 0; ap < altProviders.length; ap++) {
|
|
1583
|
+
html += '<button type="button" class="card-alt-chip" data-alt-provider="' + altProviders[ap] + '" data-alt-query="' + escHtml(queryText) + '">' + escHtml(providerLabel(altProviders[ap])) + '</button>';
|
|
1584
|
+
}
|
|
1585
|
+
html += "</div>";
|
|
1586
|
+
return html;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
function getSummaryProvider(modelValue) {
|
|
1590
|
+
if (typeof modelValue !== "string") return "";
|
|
1591
|
+
var trimmed = modelValue.trim();
|
|
1592
|
+
var slash = trimmed.indexOf("/");
|
|
1593
|
+
if (slash <= 0) return "";
|
|
1594
|
+
return trimmed.slice(0, slash);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
function summaryProviderLabel(provider) {
|
|
1598
|
+
if (!provider) return "";
|
|
1599
|
+
if (provider === "openai") return "OpenAI";
|
|
1600
|
+
if (provider === "google") return "Google";
|
|
1601
|
+
if (provider === "anthropic") return "Anthropic";
|
|
1602
|
+
return provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
function buildSummaryModelState() {
|
|
1606
|
+
summaryModelsByProvider = Object.create(null);
|
|
1607
|
+
summaryProviders = [];
|
|
1608
|
+
var seenValues = {};
|
|
1609
|
+
|
|
1610
|
+
for (var i = 0; i < summaryModels.length; i++) {
|
|
1611
|
+
var model = summaryModels[i];
|
|
1612
|
+
if (!model || typeof model.value !== "string") continue;
|
|
1613
|
+
var value = model.value.trim();
|
|
1614
|
+
if (!value || seenValues[value]) continue;
|
|
1615
|
+
var provider = getSummaryProvider(value);
|
|
1616
|
+
if (!provider) continue;
|
|
1617
|
+
seenValues[value] = true;
|
|
1618
|
+
|
|
1619
|
+
if (!summaryModelsByProvider[provider]) {
|
|
1620
|
+
summaryModelsByProvider[provider] = [];
|
|
1621
|
+
summaryProviders.push(provider);
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
var label = typeof model.label === "string" && model.label.trim().length > 0
|
|
1625
|
+
? model.label.trim()
|
|
1626
|
+
: value;
|
|
1627
|
+
summaryModelsByProvider[provider].push({ value: value, label: label });
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
function renderSummaryProviderSelect() {
|
|
1632
|
+
if (!summaryProviderSelect) return;
|
|
1633
|
+
|
|
1634
|
+
summaryProviderSelect.innerHTML = "";
|
|
1635
|
+
for (var i = 0; i < summaryProviders.length; i++) {
|
|
1636
|
+
var provider = summaryProviders[i];
|
|
1637
|
+
var option = document.createElement("option");
|
|
1638
|
+
option.value = provider;
|
|
1639
|
+
option.textContent = summaryProviderLabel(provider);
|
|
1640
|
+
summaryProviderSelect.appendChild(option);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
function populateSummaryModelSelect(provider, preferredModel) {
|
|
1645
|
+
if (!summaryModelSelect) return;
|
|
1646
|
+
|
|
1647
|
+
summaryModelSelect.innerHTML = "";
|
|
1648
|
+
|
|
1649
|
+
var autoOption = document.createElement("option");
|
|
1650
|
+
autoOption.value = "";
|
|
1651
|
+
autoOption.textContent = "Auto";
|
|
1652
|
+
summaryModelSelect.appendChild(autoOption);
|
|
1653
|
+
|
|
1654
|
+
var models = summaryModelsByProvider[provider] || [];
|
|
1655
|
+
for (var i = 0; i < models.length; i++) {
|
|
1656
|
+
var option = document.createElement("option");
|
|
1657
|
+
option.value = models[i].value;
|
|
1658
|
+
var shortLabel = models[i].value;
|
|
1659
|
+
var labelSlash = shortLabel.indexOf("/");
|
|
1660
|
+
if (labelSlash > 0) shortLabel = shortLabel.slice(labelSlash + 1);
|
|
1661
|
+
option.textContent = shortLabel;
|
|
1662
|
+
summaryModelSelect.appendChild(option);
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
var hasPreferred = false;
|
|
1666
|
+
if (preferredModel) {
|
|
1667
|
+
for (var j = 0; j < models.length; j++) {
|
|
1668
|
+
if (models[j].value === preferredModel) {
|
|
1669
|
+
hasPreferred = true;
|
|
1670
|
+
break;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
if (hasPreferred) {
|
|
1676
|
+
summaryModelSelect.value = preferredModel;
|
|
1677
|
+
} else if (models.length > 0) {
|
|
1678
|
+
summaryModelSelect.value = models[0].value;
|
|
1679
|
+
} else {
|
|
1680
|
+
summaryModelSelect.value = "";
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
currentSummaryModel = typeof summaryModelSelect.value === "string"
|
|
1684
|
+
? summaryModelSelect.value.trim()
|
|
1685
|
+
: "";
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
function setSummaryProvider(provider, preferredModel) {
|
|
1689
|
+
if (summaryProviders.indexOf(provider) === -1) return;
|
|
1690
|
+
currentSummaryProvider = provider;
|
|
1691
|
+
|
|
1692
|
+
if (summaryProviderSelect) {
|
|
1693
|
+
summaryProviderSelect.value = provider;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
populateSummaryModelSelect(provider, preferredModel);
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
function initializeSummaryModelControls() {
|
|
1700
|
+
buildSummaryModelState();
|
|
1701
|
+
renderSummaryProviderSelect();
|
|
1702
|
+
|
|
1703
|
+
if (summaryProviders.length === 0) {
|
|
1704
|
+
currentSummaryProvider = "";
|
|
1705
|
+
currentSummaryModel = "";
|
|
1706
|
+
if (summaryProviderSelect) summaryProviderSelect.innerHTML = "";
|
|
1707
|
+
if (summaryModelSelect) {
|
|
1708
|
+
summaryModelSelect.innerHTML = '<option value="">Auto</option>';
|
|
1709
|
+
summaryModelSelect.value = "";
|
|
1710
|
+
}
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
var defaultProvider = getSummaryProvider(defaultSummaryModel);
|
|
1715
|
+
if (defaultProvider && summaryProviders.indexOf(defaultProvider) !== -1) {
|
|
1716
|
+
setSummaryProvider(defaultProvider, defaultSummaryModel);
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
setSummaryProvider(summaryProviders[0], "");
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
function getSelectedSummaryModel() {
|
|
1724
|
+
if (!summaryModelSelect) return currentSummaryModel;
|
|
1725
|
+
if (typeof summaryModelSelect.value !== "string") return currentSummaryModel;
|
|
1726
|
+
currentSummaryModel = summaryModelSelect.value.trim();
|
|
1727
|
+
return currentSummaryModel;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
function getFeedbackText() {
|
|
1731
|
+
if (!summaryFeedback || typeof summaryFeedback.value !== "string") return "";
|
|
1732
|
+
return summaryFeedback.value;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
function getCoverageSet(provider) {
|
|
1736
|
+
var set = providerCoverage.get(provider);
|
|
1737
|
+
if (set) return set;
|
|
1738
|
+
set = new Set();
|
|
1739
|
+
providerCoverage.set(provider, set);
|
|
1740
|
+
return set;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
function markCoverage(provider, slotId) {
|
|
1744
|
+
if (typeof slotId !== "number") return;
|
|
1745
|
+
var normalized = normalizeProvider(provider, "");
|
|
1746
|
+
if (!normalized) return;
|
|
1747
|
+
getCoverageSet(normalized).add(slotId);
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
function removeSlot(slotId) {
|
|
1751
|
+
allQueries = allQueries.filter(function(slot) { return slot.slotId !== slotId; });
|
|
1752
|
+
|
|
1753
|
+
providerCoverage.forEach(function(coveredSlots) {
|
|
1754
|
+
coveredSlots.delete(slotId);
|
|
1755
|
+
});
|
|
1756
|
+
|
|
1757
|
+
queryIndexToSlot.forEach(function(mappedSlotId, qi) {
|
|
1758
|
+
if (mappedSlotId === slotId) queryIndexToSlot.delete(qi);
|
|
1759
|
+
});
|
|
1760
|
+
|
|
1761
|
+
syncLoadingPanel();
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
function isResultMutationLocked() {
|
|
1765
|
+
return submitted || timerExpired || submitInFlight;
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
function applyProviderInterlocks() {
|
|
1769
|
+
var disableProviders = isResultMutationLocked() || providerBatchInFlight || addSearchInFlight;
|
|
1770
|
+
for (var i = 0; i < providerButtons.length; i++) {
|
|
1771
|
+
var btn = providerButtons[i];
|
|
1772
|
+
var state = btn.dataset.state || "idle";
|
|
1773
|
+
btn.disabled = disableProviders || state === "loading";
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
var disableAddSearch = isResultMutationLocked();
|
|
1777
|
+
if (addSearchInput) {
|
|
1778
|
+
addSearchInput.disabled = disableAddSearch;
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
if (addSearchEl) {
|
|
1782
|
+
addSearchEl.style.opacity = disableAddSearch ? "0.6" : "";
|
|
1783
|
+
addSearchEl.style.pointerEvents = disableAddSearch ? "none" : "";
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
var cards = resultCardsEl ? resultCardsEl.querySelectorAll(".result-card") : [];
|
|
1787
|
+
cards.forEach(function(card) {
|
|
1788
|
+
var cb = card.querySelector("input[type=checkbox]");
|
|
1789
|
+
if (!cb) return;
|
|
1790
|
+
var searching = card.classList.contains("searching");
|
|
1791
|
+
var error = card.classList.contains("error");
|
|
1792
|
+
cb.disabled = searching || error || isResultMutationLocked();
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
function recomputeProviderStates() {
|
|
1797
|
+
for (var i = 0; i < providerButtons.length; i++) {
|
|
1798
|
+
var btn = providerButtons[i];
|
|
1799
|
+
var provider = normalizeProvider(btn.dataset.provider, "");
|
|
1800
|
+
if (!provider) continue;
|
|
1801
|
+
|
|
1802
|
+
var state = "idle";
|
|
1803
|
+
if (providerBatchInFlight && batchLoadingProvider === provider) {
|
|
1804
|
+
state = "loading";
|
|
1805
|
+
} else if (!initialStreamDone && queries.length > 0 && provider === initialDefaultProvider) {
|
|
1806
|
+
state = "loading";
|
|
1807
|
+
} else if (allQueries.length > 0) {
|
|
1808
|
+
var coveredSlots = providerCoverage.get(provider);
|
|
1809
|
+
if (coveredSlots && coveredSlots.size >= allQueries.length) {
|
|
1810
|
+
state = "searched";
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
btn.dataset.state = state;
|
|
1815
|
+
btn.classList.remove("idle", "loading", "searched");
|
|
1816
|
+
btn.classList.add(state);
|
|
1817
|
+
btn.classList.toggle("is-default", provider === currentProvider);
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
applyProviderInterlocks();
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
function updateSummaryText() {
|
|
1824
|
+
if (completedCount <= 0) return;
|
|
1825
|
+
var totalCards = resultCardsEl.querySelectorAll(".result-card").length;
|
|
1826
|
+
var searchingCount = totalCards - completedCount;
|
|
1827
|
+
if (searchingCount > 0) {
|
|
1828
|
+
heroTitle.textContent = completedCount + " of " + totalCards + " Searches Complete";
|
|
1829
|
+
} else {
|
|
1830
|
+
heroTitle.textContent = completedCount + " Search" + (completedCount !== 1 ? "es" : "") + " Complete";
|
|
1831
|
+
}
|
|
1832
|
+
heroDesc.textContent = "Check the results to include, then generate and approve a summary.";
|
|
1833
|
+
if (heroStatus) heroStatus.textContent = completedCount + " completed" + (searchingCount > 0 ? ", " + searchingCount + " searching" : "");
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
function getSummaryDraftText() {
|
|
1837
|
+
if (!summaryInput || typeof summaryInput.value !== "string") return "";
|
|
1838
|
+
return summaryInput.value.trim();
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
function clearError() {
|
|
1842
|
+
if (!errorBanner) return;
|
|
1843
|
+
errorBanner.hidden = true;
|
|
1844
|
+
errorBanner.textContent = "";
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
function setError(text) {
|
|
1848
|
+
if (!errorBanner) return;
|
|
1849
|
+
errorBanner.textContent = text;
|
|
1850
|
+
errorBanner.hidden = false;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
function updateSummaryGeneratingIndicator() {
|
|
1854
|
+
if (!summaryGeneratingCopy) return;
|
|
1855
|
+
|
|
1856
|
+
if (stage !== "generating-summary") {
|
|
1857
|
+
summaryGeneratingCopy.textContent = "Generating summary draft…";
|
|
1858
|
+
summaryGeneratingPhase = -1;
|
|
1859
|
+
if (summaryGeneratingEl) {
|
|
1860
|
+
summaryGeneratingEl.removeAttribute("data-phase");
|
|
1861
|
+
}
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
if (summaryGeneratingStartedAt <= 0) {
|
|
1866
|
+
summaryGeneratingStartedAt = Date.now();
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
var elapsedMs = Date.now() - summaryGeneratingStartedAt;
|
|
1870
|
+
var nextPhase = Math.min(2, Math.floor(elapsedMs / 1800));
|
|
1871
|
+
if (nextPhase === summaryGeneratingPhase) return;
|
|
1872
|
+
|
|
1873
|
+
summaryGeneratingPhase = nextPhase;
|
|
1874
|
+
|
|
1875
|
+
var phaseLabel = "Planning summary";
|
|
1876
|
+
if (nextPhase === 1) phaseLabel = "Drafting summary";
|
|
1877
|
+
if (nextPhase === 2) phaseLabel = "Polishing summary";
|
|
1878
|
+
|
|
1879
|
+
summaryGeneratingCopy.textContent = summaryPendingModel
|
|
1880
|
+
? phaseLabel + " with " + summaryPendingModel + "…"
|
|
1881
|
+
: phaseLabel + "…";
|
|
1882
|
+
|
|
1883
|
+
if (summaryGeneratingEl) {
|
|
1884
|
+
summaryGeneratingEl.dataset.phase = String(nextPhase);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
function updateStageUI() {
|
|
1889
|
+
var showSummary = stage === "summary-review" || stage === "generating-summary" || isRegenerating;
|
|
1890
|
+
if (summaryPanel) {
|
|
1891
|
+
summaryPanel.classList.toggle("hidden", !showSummary);
|
|
1892
|
+
summaryPanel.classList.toggle("updating", isRegenerating);
|
|
1893
|
+
}
|
|
1894
|
+
if (summarySubtitle) {
|
|
1895
|
+
var selCount = getSelectedIndices().length;
|
|
1896
|
+
var selLabel = selCount + " selected result" + (selCount !== 1 ? "s" : "");
|
|
1897
|
+
if (isRegenerating && stage === "generating-summary") {
|
|
1898
|
+
summarySubtitle.textContent = "Selection changed — regenerating summary…";
|
|
1899
|
+
} else if (isRegenerating) {
|
|
1900
|
+
summarySubtitle.textContent = "Selection changed — summary will regenerate shortly…";
|
|
1901
|
+
} else if (stage === "generating-summary") {
|
|
1902
|
+
summarySubtitle.textContent = summaryPendingModel
|
|
1903
|
+
? "Summarizing " + selLabel + " with " + summaryPendingModel + "…"
|
|
1904
|
+
: "Summarizing " + selLabel + "…";
|
|
1905
|
+
} else if (summaryMeta && summaryMeta.fallbackUsed) {
|
|
1906
|
+
summarySubtitle.textContent = "Fallback summary of " + selLabel + ".";
|
|
1907
|
+
} else {
|
|
1908
|
+
summarySubtitle.textContent = "Summary of " + selLabel + ". Edit directly, regenerate with feedback, or approve.";
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
if (summaryGeneratingEl) {
|
|
1913
|
+
var showGenerating = stage === "generating-summary" && !isRegenerating;
|
|
1914
|
+
summaryGeneratingEl.classList.toggle("hidden", !showGenerating);
|
|
1915
|
+
}
|
|
1916
|
+
updateSummaryGeneratingIndicator();
|
|
1917
|
+
|
|
1918
|
+
if (summaryInput) {
|
|
1919
|
+
summaryInput.classList.toggle("hidden", stage === "generating-summary" && !isRegenerating);
|
|
1920
|
+
summaryInput.disabled = submitted || timerExpired || stage === "generating-summary" || submitInFlight || isRegenerating;
|
|
1921
|
+
}
|
|
1922
|
+
if (summaryFeedback) {
|
|
1923
|
+
summaryFeedback.disabled = submitted || timerExpired || submitInFlight || stage === "generating-summary" || isRegenerating;
|
|
1924
|
+
}
|
|
1925
|
+
var disableSummaryModelControls = submitted || timerExpired || stage === "generating-summary" || submitInFlight || summaryProviders.length === 0;
|
|
1926
|
+
if (summaryProviderSelect) {
|
|
1927
|
+
summaryProviderSelect.disabled = disableSummaryModelControls;
|
|
1928
|
+
}
|
|
1929
|
+
if (summaryModelSelect) {
|
|
1930
|
+
summaryModelSelect.disabled = disableSummaryModelControls;
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
var inResults = stage === "results";
|
|
1934
|
+
var hasSelection = getSelectedIndices().length > 0;
|
|
1935
|
+
var hasCompleted = getCompletedSelectableIndices().length > 0;
|
|
1936
|
+
var canGenerate = inResults && !submitted && !timerExpired && !submitInFlight && hasCompleted;
|
|
1937
|
+
|
|
1938
|
+
if (btnSend) {
|
|
1939
|
+
if (stage === "generating-summary") {
|
|
1940
|
+
btnSend.textContent = "Generating summary…";
|
|
1941
|
+
btnSend.disabled = true;
|
|
1942
|
+
} else if (!inResults) {
|
|
1943
|
+
btnSend.textContent = "Summary ready";
|
|
1944
|
+
btnSend.disabled = true;
|
|
1945
|
+
} else if (!hasCompleted) {
|
|
1946
|
+
btnSend.textContent = searchesDone ? "No results yet" : "Waiting for results…";
|
|
1947
|
+
btnSend.disabled = true;
|
|
1948
|
+
} else {
|
|
1949
|
+
btnSend.textContent = hasSelection ? "Generate summary" : "Select results to summarize";
|
|
1950
|
+
btnSend.disabled = !canGenerate || !hasSelection;
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
if (sendRawRow) {
|
|
1954
|
+
sendRawRow.classList.toggle("hidden", !hasSelection || submitted || timerExpired);
|
|
1955
|
+
}
|
|
1956
|
+
if (btnSendRaw) {
|
|
1957
|
+
btnSendRaw.disabled = !hasSelection || submitted || timerExpired || submitInFlight;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
if (btnSummaryBack) btnSummaryBack.disabled = submitted || timerExpired || submitInFlight || (stage === "generating-summary" && !isRegenerating);
|
|
1961
|
+
if (btnSummaryRegenerate) btnSummaryRegenerate.disabled = submitted || timerExpired || submitInFlight || stage === "generating-summary" || isRegenerating;
|
|
1962
|
+
var hasDraft = getSummaryDraftText().length > 0;
|
|
1963
|
+
if (btnSummaryPreview) btnSummaryPreview.disabled = !hasDraft || stage === "generating-summary";
|
|
1964
|
+
if (btnSummaryApprove) {
|
|
1965
|
+
btnSummaryApprove.disabled = submitted || timerExpired || submitInFlight || stage === "generating-summary" || isRegenerating || !hasSelection || !hasDraft;
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
applyProviderInterlocks();
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
function shouldShowLoadingPanel() {
|
|
1972
|
+
if (submitted || timerExpired || searchesDone) return false;
|
|
1973
|
+
if (completedCount > 0) return false;
|
|
1974
|
+
return allQueries.length > 0;
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
function ensureLoadingPanel() {
|
|
1978
|
+
if (loadingPanelEl) return loadingPanelEl;
|
|
1979
|
+
if (!resultCardsEl) return null;
|
|
1980
|
+
|
|
1981
|
+
var panel = document.createElement("div");
|
|
1982
|
+
panel.className = "result-loading";
|
|
1983
|
+
panel.innerHTML =
|
|
1984
|
+
'<div class="result-loading-header">' +
|
|
1985
|
+
'<div class="result-loading-title">Searching sources</div>' +
|
|
1986
|
+
'<div class="result-loading-sub">Searching\u2026</div>' +
|
|
1987
|
+
'</div>' +
|
|
1988
|
+
'<div class="result-loading-grid">' +
|
|
1989
|
+
'<div class="loading-card"><div class="loading-card-row long"></div><div class="loading-card-row mid"></div><div class="loading-card-row short"></div></div>' +
|
|
1990
|
+
'<div class="loading-card"><div class="loading-card-row long"></div><div class="loading-card-row mid"></div><div class="loading-card-row short"></div></div>' +
|
|
1991
|
+
'</div>';
|
|
1992
|
+
|
|
1993
|
+
resultCardsEl.prepend(panel);
|
|
1994
|
+
loadingPanelEl = panel;
|
|
1995
|
+
return panel;
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
function updateLoadingPanelSummary() {
|
|
1999
|
+
if (!loadingPanelEl) return;
|
|
2000
|
+
var sub = loadingPanelEl.querySelector(".result-loading-sub");
|
|
2001
|
+
if (!sub) return;
|
|
2002
|
+
|
|
2003
|
+
var total = allQueries.length;
|
|
2004
|
+
if (total <= 0) {
|
|
2005
|
+
sub.textContent = "Searching\u2026";
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
var done = Math.min(completedCount, total);
|
|
2010
|
+
var noun = total === 1 ? "query" : "queries";
|
|
2011
|
+
sub.textContent = "Searching " + done + "/" + total + " " + noun + "\u2026";
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
function syncLoadingPanel() {
|
|
2015
|
+
if (shouldShowLoadingPanel()) {
|
|
2016
|
+
if (!ensureLoadingPanel()) return;
|
|
2017
|
+
updateLoadingPanelSummary();
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
if (loadingPanelEl) {
|
|
2022
|
+
loadingPanelEl.remove();
|
|
2023
|
+
loadingPanelEl = null;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
function renderErrorCard(card, queryText, errorText, provider) {
|
|
2028
|
+
var tag = providerTagHtml(provider);
|
|
2029
|
+
card.innerHTML =
|
|
2030
|
+
'<div class="result-card-header">' +
|
|
2031
|
+
'<input type="checkbox" disabled>' +
|
|
2032
|
+
'<div class="result-card-info">' +
|
|
2033
|
+
'<div class="result-card-query-row">' +
|
|
2034
|
+
'<div class="result-card-query">' + escHtml(queryText) + "</div>" +
|
|
2035
|
+
tag +
|
|
2036
|
+
"</div>" +
|
|
2037
|
+
'<div class="result-card-meta" style="color:var(--timer-urgent-fg)">Failed</div>' +
|
|
2038
|
+
"</div>" +
|
|
2039
|
+
"</div>" +
|
|
2040
|
+
'<div class="result-card-error-msg">' + escHtml(errorText || "Search failed") + "</div>";
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
function populateResultCard(card, data, queryText, provider) {
|
|
2044
|
+
var sourceCount = data.results ? data.results.length : 0;
|
|
2045
|
+
var domains = [];
|
|
2046
|
+
if (data.results) {
|
|
2047
|
+
for (var i = 0; i < Math.min(data.results.length, 3); i++) {
|
|
2048
|
+
domains.push(data.results[i].domain);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
var metaText = sourceCount + " source" + (sourceCount !== 1 ? "s" : "");
|
|
2052
|
+
if (domains.length > 0) metaText += " \u00B7 " + domains.join(", ");
|
|
2053
|
+
if (sourceCount > 3) metaText += ", +" + (sourceCount - 3);
|
|
2054
|
+
|
|
2055
|
+
var preview = "";
|
|
2056
|
+
if (data.answer) {
|
|
2057
|
+
preview = data.answer.substring(0, 200).replace(/\\n+/g, " ").replace(/[#*_\\[\\]]/g, "");
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
var bodyHtml = "";
|
|
2061
|
+
if (data.answer) {
|
|
2062
|
+
var rendered = typeof marked !== "undefined" && marked.parse
|
|
2063
|
+
? marked.parse(data.answer, { breaks: true })
|
|
2064
|
+
: "<p>" + escHtml(data.answer) + "</p>";
|
|
2065
|
+
bodyHtml += '<div class="result-card-answer">' + sanitizeMarkdownHtml(rendered) + "</div>";
|
|
2066
|
+
}
|
|
2067
|
+
if (data.results && data.results.length > 0) {
|
|
2068
|
+
bodyHtml += '<div class="result-card-sources"><div class="result-card-sources-title">Sources</div>';
|
|
2069
|
+
for (var k = 0; k < data.results.length; k++) {
|
|
2070
|
+
var r = data.results[k];
|
|
2071
|
+
var label = r.title && r.title.indexOf("Source ") !== 0 ? r.title : r.url;
|
|
2072
|
+
var href = sanitizeHref(r.url);
|
|
2073
|
+
bodyHtml += '<a class="source-link" href="' + escHtml(href) + '" target="_blank" rel="noopener noreferrer">' + escHtml(label) + '<span class="source-domain">' + escHtml(r.domain) + "</span></a>";
|
|
2074
|
+
}
|
|
2075
|
+
bodyHtml += "</div>";
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
var altChipsHtml = buildAltChipsHtml(provider, queryText);
|
|
2079
|
+
|
|
2080
|
+
card.innerHTML =
|
|
2081
|
+
'<div class="result-card-header">' +
|
|
2082
|
+
'<input type="checkbox" checked>' +
|
|
2083
|
+
'<div class="result-card-info">' +
|
|
2084
|
+
'<div class="result-card-query-row">' +
|
|
2085
|
+
'<div class="result-card-query">' + escHtml(queryText) + "</div>" +
|
|
2086
|
+
providerTagHtml(provider) +
|
|
2087
|
+
"</div>" +
|
|
2088
|
+
'<div class="result-card-meta">' + escHtml(metaText) + "</div>" +
|
|
2089
|
+
(preview ? '<div class="result-card-preview">' + escHtml(preview) + "</div>" : "") +
|
|
2090
|
+
"</div>" +
|
|
2091
|
+
'<div class="result-card-expand">\u25BC</div>' +
|
|
2092
|
+
"</div>" +
|
|
2093
|
+
altChipsHtml +
|
|
2094
|
+
'<div class="result-card-body">' + bodyHtml + "</div>";
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
function applyResponseToCard(card, data, queryText, providerHint, slotHint) {
|
|
2098
|
+
if (!card || !data) return;
|
|
2099
|
+
if (submitted || timerExpired) return;
|
|
2100
|
+
|
|
2101
|
+
var queryIndex = typeof data.queryIndex === "number" ? data.queryIndex : null;
|
|
2102
|
+
if (queryIndex !== null) {
|
|
2103
|
+
card.dataset.qi = String(queryIndex);
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
var slotId = typeof slotHint === "number" ? slotHint : (queryIndex !== null ? queryIndexToSlot.get(queryIndex) : undefined);
|
|
2107
|
+
if (typeof slotId !== "number" && queryIndex !== null) {
|
|
2108
|
+
slotId = queryIndex;
|
|
2109
|
+
}
|
|
2110
|
+
if (queryIndex !== null && typeof slotId === "number") {
|
|
2111
|
+
queryIndexToSlot.set(queryIndex, slotId);
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
var provider = normalizeProvider(data.provider, providerHint);
|
|
2115
|
+
|
|
2116
|
+
card.classList.remove("searching", "checked", "error");
|
|
2117
|
+
|
|
2118
|
+
if (data.error) {
|
|
2119
|
+
card.classList.add("error");
|
|
2120
|
+
renderErrorCard(card, queryText, data.error, provider);
|
|
2121
|
+
} else {
|
|
2122
|
+
card.classList.add("checked");
|
|
2123
|
+
populateResultCard(card, data, queryText, provider);
|
|
2124
|
+
setupCardInteraction(card);
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
if (card.dataset.completed !== "true") {
|
|
2128
|
+
completedCount++;
|
|
2129
|
+
card.dataset.completed = "true";
|
|
2130
|
+
}
|
|
2131
|
+
markCoverage(provider, slotId);
|
|
2132
|
+
updateSummaryText();
|
|
2133
|
+
syncLoadingPanel();
|
|
2134
|
+
recomputeProviderStates();
|
|
2135
|
+
updateStageUI();
|
|
2136
|
+
maybeAutoGenerateSummary();
|
|
2137
|
+
resetTimer();
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
function resetTimer() { lastInteraction = Date.now(); }
|
|
2141
|
+
|
|
2142
|
+
function updateTimer() {
|
|
2143
|
+
var idleSec = Math.floor((Date.now() - lastInteraction) / 1000);
|
|
2144
|
+
var remaining = Math.max(0, timeoutSec - idleSec);
|
|
2145
|
+
timerEl.textContent = formatTime(remaining);
|
|
2146
|
+
|
|
2147
|
+
timerEl.classList.remove("warn", "urgent", "active");
|
|
2148
|
+
if (remaining <= 15) timerEl.classList.add("urgent");
|
|
2149
|
+
else if (remaining <= 30) timerEl.classList.add("warn");
|
|
2150
|
+
else if (remaining < timeoutSec) timerEl.classList.add("active");
|
|
2151
|
+
|
|
2152
|
+
updateSummaryGeneratingIndicator();
|
|
2153
|
+
|
|
2154
|
+
if (remaining <= 0 && !submitted && !timerExpired) onTimeout();
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
setInterval(updateTimer, 1000);
|
|
2158
|
+
updateTimer();
|
|
2159
|
+
|
|
2160
|
+
["click", "keydown", "input", "change"].forEach(function(evt) {
|
|
2161
|
+
document.addEventListener(evt, resetTimer, { passive: true });
|
|
2162
|
+
});
|
|
2163
|
+
document.addEventListener("scroll", resetTimer, { passive: true });
|
|
2164
|
+
document.addEventListener("mousemove", resetTimer, { passive: true });
|
|
2165
|
+
|
|
2166
|
+
timerEl.addEventListener("click", function(e) {
|
|
2167
|
+
e.stopPropagation();
|
|
2168
|
+
timerInput.value = timeoutSec;
|
|
2169
|
+
timerAdjustEl.classList.add("visible");
|
|
2170
|
+
timerEl.style.display = "none";
|
|
2171
|
+
timerInput.focus();
|
|
2172
|
+
timerInput.select();
|
|
2173
|
+
});
|
|
2174
|
+
|
|
2175
|
+
function applyTimerAdjust() {
|
|
2176
|
+
var val = parseInt(timerInput.value, 10);
|
|
2177
|
+
if (val && val > 0) timeoutSec = Math.min(val, 600);
|
|
2178
|
+
timerAdjustEl.classList.remove("visible");
|
|
2179
|
+
timerEl.style.display = "";
|
|
2180
|
+
resetTimer();
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
timerSetBtn.addEventListener("click", function(e) { e.stopPropagation(); applyTimerAdjust(); });
|
|
2184
|
+
timerInput.addEventListener("keydown", function(e) {
|
|
2185
|
+
if (e.key === "Enter") { e.preventDefault(); applyTimerAdjust(); }
|
|
2186
|
+
if (e.key === "Escape") { timerAdjustEl.classList.remove("visible"); timerEl.style.display = ""; }
|
|
2187
|
+
e.stopPropagation();
|
|
2188
|
+
});
|
|
2189
|
+
document.addEventListener("click", function() {
|
|
2190
|
+
if (timerAdjustEl.classList.contains("visible")) {
|
|
2191
|
+
timerAdjustEl.classList.remove("visible");
|
|
2192
|
+
timerEl.style.display = "";
|
|
2193
|
+
}
|
|
2194
|
+
});
|
|
2195
|
+
|
|
2196
|
+
function setDefaultProvider(provider, persist) {
|
|
2197
|
+
var normalized = normalizeProvider(provider, currentProvider);
|
|
2198
|
+
if (!normalized) return;
|
|
2199
|
+
currentProvider = normalized;
|
|
2200
|
+
recomputeProviderStates();
|
|
2201
|
+
if (persist) {
|
|
2202
|
+
postJson("/provider", { provider: normalized }).then(function(data) {
|
|
2203
|
+
if (data && data.ok === false) {
|
|
2204
|
+
throw new Error(extractServerError(data) || "request rejected");
|
|
2205
|
+
}
|
|
2206
|
+
}).catch(function(err) {
|
|
2207
|
+
var message = err instanceof Error ? err.message : String(err);
|
|
2208
|
+
setError("Failed to save provider preference: " + (message || "unknown error"));
|
|
2209
|
+
});
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
providerButtons.forEach(function(btn) {
|
|
2214
|
+
btn.addEventListener("click", function() {
|
|
2215
|
+
if (isResultMutationLocked()) return;
|
|
2216
|
+
if (providerBatchInFlight || addSearchInFlight) return;
|
|
2217
|
+
|
|
2218
|
+
var provider = normalizeProvider(btn.dataset.provider, "");
|
|
2219
|
+
if (!provider) return;
|
|
2220
|
+
|
|
2221
|
+
var state = btn.dataset.state || "idle";
|
|
2222
|
+
if (state === "loading") return;
|
|
2223
|
+
|
|
2224
|
+
if (state === "searched") {
|
|
2225
|
+
if (provider === currentProvider) return;
|
|
2226
|
+
setDefaultProvider(provider, true);
|
|
2227
|
+
resetTimer();
|
|
2228
|
+
return;
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
setDefaultProvider(provider, true);
|
|
2232
|
+
if (allQueries.length === 0) {
|
|
2233
|
+
resetTimer();
|
|
2234
|
+
return;
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
interruptSummaryIfNeeded();
|
|
2238
|
+
providerBatchInFlight = true;
|
|
2239
|
+
batchLoadingProvider = provider;
|
|
2240
|
+
recomputeProviderStates();
|
|
2241
|
+
|
|
2242
|
+
var batchQueries = allQueries.slice();
|
|
2243
|
+
var inflight = batchQueries.length;
|
|
2244
|
+
if (inflight === 0) {
|
|
2245
|
+
providerBatchInFlight = false;
|
|
2246
|
+
batchLoadingProvider = null;
|
|
2247
|
+
recomputeProviderStates();
|
|
2248
|
+
return;
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
var batchCards = [];
|
|
2252
|
+
for (var bi = 0; bi < batchQueries.length; bi++) {
|
|
2253
|
+
var bq = batchQueries[bi];
|
|
2254
|
+
var card = document.createElement("div");
|
|
2255
|
+
card.className = "result-card searching";
|
|
2256
|
+
card.innerHTML =
|
|
2257
|
+
'<div class="result-card-header">' +
|
|
2258
|
+
'<input type="checkbox" checked disabled>' +
|
|
2259
|
+
'<div class="result-card-info">' +
|
|
2260
|
+
'<div class="result-card-query-row">' +
|
|
2261
|
+
'<div class="result-card-query">' + escHtml(bq.query) + "</div>" +
|
|
2262
|
+
providerTagHtml(provider) +
|
|
2263
|
+
"</div>" +
|
|
2264
|
+
'<div class="result-card-meta"><span class="searching-dots">Searching</span></div>' +
|
|
2265
|
+
"</div>" +
|
|
2266
|
+
"</div>" +
|
|
2267
|
+
buildAltChipsHtml(provider, bq.query);
|
|
2268
|
+
resultCardsEl.appendChild(card);
|
|
2269
|
+
batchCards.push(card);
|
|
2270
|
+
}
|
|
2271
|
+
updateSummaryText();
|
|
2272
|
+
|
|
2273
|
+
batchQueries.forEach(function(slot, si) {
|
|
2274
|
+
var searchingCard = batchCards[si];
|
|
2275
|
+
postJson("/search", { query: slot.query, provider: provider })
|
|
2276
|
+
.then(function(data) {
|
|
2277
|
+
if (submitted || timerExpired) return;
|
|
2278
|
+
if (!data || data.ok === false) {
|
|
2279
|
+
applyResponseToCard(searchingCard, {
|
|
2280
|
+
answer: "",
|
|
2281
|
+
results: [],
|
|
2282
|
+
error: extractServerError(data) || "Search failed",
|
|
2283
|
+
provider: provider,
|
|
2284
|
+
}, slot.query, provider, slot.slotId);
|
|
2285
|
+
return;
|
|
2286
|
+
}
|
|
2287
|
+
applyResponseToCard(searchingCard, data, slot.query, provider, slot.slotId);
|
|
2288
|
+
})
|
|
2289
|
+
.catch(function(err) {
|
|
2290
|
+
if (submitted || timerExpired) return;
|
|
2291
|
+
var message = err instanceof Error ? err.message : String(err);
|
|
2292
|
+
applyResponseToCard(searchingCard, {
|
|
2293
|
+
answer: "",
|
|
2294
|
+
results: [],
|
|
2295
|
+
error: message || "Search failed",
|
|
2296
|
+
provider: provider,
|
|
2297
|
+
}, slot.query, provider, slot.slotId);
|
|
2298
|
+
})
|
|
2299
|
+
.finally(function() {
|
|
2300
|
+
inflight -= 1;
|
|
2301
|
+
if (inflight <= 0) {
|
|
2302
|
+
providerBatchInFlight = false;
|
|
2303
|
+
batchLoadingProvider = null;
|
|
2304
|
+
recomputeProviderStates();
|
|
2305
|
+
updateStageUI();
|
|
2306
|
+
maybeAutoGenerateSummary();
|
|
2307
|
+
}
|
|
2308
|
+
});
|
|
2309
|
+
});
|
|
2310
|
+
|
|
2311
|
+
resetTimer();
|
|
2312
|
+
});
|
|
2313
|
+
});
|
|
2314
|
+
|
|
2315
|
+
if (resultCardsEl) {
|
|
2316
|
+
resultCardsEl.addEventListener("click", function(e) {
|
|
2317
|
+
if (!(e.target instanceof Element)) return;
|
|
2318
|
+
var chip = e.target.closest(".card-alt-chip");
|
|
2319
|
+
if (!chip) return;
|
|
2320
|
+
if (isResultMutationLocked()) return;
|
|
2321
|
+
|
|
2322
|
+
var altProvider = chip.dataset.altProvider;
|
|
2323
|
+
var altQuery = chip.dataset.altQuery;
|
|
2324
|
+
if (!altProvider || !altQuery) return;
|
|
2325
|
+
|
|
2326
|
+
interruptSummaryIfNeeded();
|
|
2327
|
+
|
|
2328
|
+
chip.classList.add("loading");
|
|
2329
|
+
chip.disabled = true;
|
|
2330
|
+
resetTimer();
|
|
2331
|
+
|
|
2332
|
+
var slotId = nextSlotId++;
|
|
2333
|
+
allQueries.push({ slotId: slotId, query: altQuery });
|
|
2334
|
+
|
|
2335
|
+
var parentCard = chip.closest(".result-card");
|
|
2336
|
+
var newCard = document.createElement("div");
|
|
2337
|
+
newCard.className = "result-card searching";
|
|
2338
|
+
newCard.innerHTML =
|
|
2339
|
+
'<div class="result-card-header">' +
|
|
2340
|
+
'<input type="checkbox" checked disabled>' +
|
|
2341
|
+
'<div class="result-card-info">' +
|
|
2342
|
+
'<div class="result-card-query-row">' +
|
|
2343
|
+
'<div class="result-card-query">' + escHtml(altQuery) + "</div>" +
|
|
2344
|
+
providerTagHtml(altProvider) +
|
|
2345
|
+
"</div>" +
|
|
2346
|
+
'<div class="result-card-meta"><span class="searching-dots">Searching</span></div>' +
|
|
2347
|
+
"</div>" +
|
|
2348
|
+
"</div>" +
|
|
2349
|
+
buildAltChipsHtml(altProvider, altQuery);
|
|
2350
|
+
if (parentCard && parentCard.nextSibling) {
|
|
2351
|
+
resultCardsEl.insertBefore(newCard, parentCard.nextSibling);
|
|
2352
|
+
} else {
|
|
2353
|
+
resultCardsEl.appendChild(newCard);
|
|
2354
|
+
}
|
|
2355
|
+
updateSummaryText();
|
|
2356
|
+
|
|
2357
|
+
postJson("/search", { query: altQuery, provider: altProvider })
|
|
2358
|
+
.then(function(data) {
|
|
2359
|
+
if (submitted || timerExpired) return;
|
|
2360
|
+
if (!data || data.ok === false) {
|
|
2361
|
+
applyResponseToCard(newCard, {
|
|
2362
|
+
answer: "", results: [],
|
|
2363
|
+
error: extractServerError(data) || "Search failed",
|
|
2364
|
+
provider: altProvider,
|
|
2365
|
+
}, altQuery, altProvider, slotId);
|
|
2366
|
+
return;
|
|
2367
|
+
}
|
|
2368
|
+
applyResponseToCard(newCard, data, altQuery, altProvider, slotId);
|
|
2369
|
+
})
|
|
2370
|
+
.catch(function(err) {
|
|
2371
|
+
removeSlot(slotId);
|
|
2372
|
+
newCard.remove();
|
|
2373
|
+
var message = err instanceof Error ? err.message : String(err);
|
|
2374
|
+
setError("Re-search failed: " + (message || "Search failed"));
|
|
2375
|
+
updateSummaryText();
|
|
2376
|
+
})
|
|
2377
|
+
.finally(function() {
|
|
2378
|
+
chip.classList.remove("loading");
|
|
2379
|
+
chip.disabled = false;
|
|
2380
|
+
recomputeProviderStates();
|
|
2381
|
+
updateStageUI();
|
|
2382
|
+
maybeAutoGenerateSummary();
|
|
2383
|
+
});
|
|
2384
|
+
});
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
if (addSearchInput && addSearchWand) {
|
|
2388
|
+
addSearchInput.addEventListener("input", function() {
|
|
2389
|
+
addSearchWand.disabled = rewriteInFlight || !addSearchInput.value.trim() || isResultMutationLocked();
|
|
2390
|
+
});
|
|
2391
|
+
|
|
2392
|
+
addSearchWand.addEventListener("click", function() {
|
|
2393
|
+
var text = addSearchInput.value.trim();
|
|
2394
|
+
if (!text || rewriteInFlight || isResultMutationLocked()) return;
|
|
2395
|
+
rewriteInFlight = true;
|
|
2396
|
+
addSearchWand.disabled = true;
|
|
2397
|
+
addSearchWand.classList.add("rewriting");
|
|
2398
|
+
resetTimer();
|
|
2399
|
+
|
|
2400
|
+
postJson("/rewrite", { query: text })
|
|
2401
|
+
.then(function(data) {
|
|
2402
|
+
if (!data || data.ok === false) {
|
|
2403
|
+
throw new Error(extractServerError(data) || "Rewrite failed");
|
|
2404
|
+
}
|
|
2405
|
+
var rewritten = typeof data.query === "string" ? data.query.trim() : "";
|
|
2406
|
+
if (rewritten) {
|
|
2407
|
+
addSearchInput.value = rewritten;
|
|
2408
|
+
addSearchInput.focus();
|
|
2409
|
+
}
|
|
2410
|
+
})
|
|
2411
|
+
.catch(function(err) {
|
|
2412
|
+
var message = err instanceof Error ? err.message : String(err);
|
|
2413
|
+
setError("Rewrite failed: " + (message || "unknown error"));
|
|
2414
|
+
})
|
|
2415
|
+
.finally(function() {
|
|
2416
|
+
rewriteInFlight = false;
|
|
2417
|
+
addSearchWand.classList.remove("rewriting");
|
|
2418
|
+
addSearchWand.disabled = !addSearchInput.value.trim() || isResultMutationLocked();
|
|
2419
|
+
});
|
|
2420
|
+
});
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
addSearchInput.addEventListener("keydown", function(e) {
|
|
2424
|
+
if (e.key !== "Enter") return;
|
|
2425
|
+
var text = addSearchInput.value.trim();
|
|
2426
|
+
if (!text || isResultMutationLocked()) return;
|
|
2427
|
+
interruptSummaryIfNeeded();
|
|
2428
|
+
e.preventDefault();
|
|
2429
|
+
e.stopPropagation();
|
|
2430
|
+
|
|
2431
|
+
addSearchInFlight++;
|
|
2432
|
+
applyProviderInterlocks();
|
|
2433
|
+
addSearchInput.value = "";
|
|
2434
|
+
|
|
2435
|
+
var slotId = nextSlotId++;
|
|
2436
|
+
allQueries.push({ slotId: slotId, query: text });
|
|
2437
|
+
syncLoadingPanel();
|
|
2438
|
+
recomputeProviderStates();
|
|
2439
|
+
|
|
2440
|
+
var requestedProvider = currentProvider;
|
|
2441
|
+
|
|
2442
|
+
var card = document.createElement("div");
|
|
2443
|
+
card.className = "result-card searching";
|
|
2444
|
+
card.innerHTML =
|
|
2445
|
+
'<div class="result-card-header">' +
|
|
2446
|
+
'<input type="checkbox" checked disabled>' +
|
|
2447
|
+
'<div class="result-card-info">' +
|
|
2448
|
+
'<div class="result-card-query-row">' +
|
|
2449
|
+
'<div class="result-card-query">' + escHtml(text) + "</div>" +
|
|
2450
|
+
providerTagHtml(requestedProvider) +
|
|
2451
|
+
"</div>" +
|
|
2452
|
+
'<div class="result-card-meta"><span class="searching-dots">Searching</span></div>' +
|
|
2453
|
+
"</div>" +
|
|
2454
|
+
"</div>" +
|
|
2455
|
+
buildAltChipsHtml(requestedProvider, text);
|
|
2456
|
+
resultCardsEl.appendChild(card);
|
|
2457
|
+
updateSummaryText();
|
|
2458
|
+
resetTimer();
|
|
2459
|
+
|
|
2460
|
+
postJson("/search", { query: text, provider: requestedProvider })
|
|
2461
|
+
.then(function(data) {
|
|
2462
|
+
if (!data || data.ok === false) {
|
|
2463
|
+
removeSlot(slotId);
|
|
2464
|
+
card.remove();
|
|
2465
|
+
setError("Failed to add search: " + (extractServerError(data) || "Search failed"));
|
|
2466
|
+
recomputeProviderStates();
|
|
2467
|
+
updateSummaryText();
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
if (submitted || timerExpired) return;
|
|
2472
|
+
|
|
2473
|
+
applyResponseToCard(card, data, text, requestedProvider, slotId);
|
|
2474
|
+
})
|
|
2475
|
+
.catch(function(err) {
|
|
2476
|
+
removeSlot(slotId);
|
|
2477
|
+
card.remove();
|
|
2478
|
+
var message = err instanceof Error ? err.message : String(err);
|
|
2479
|
+
setError("Failed to add search: " + (message || "Search failed"));
|
|
2480
|
+
recomputeProviderStates();
|
|
2481
|
+
updateSummaryText();
|
|
2482
|
+
})
|
|
2483
|
+
.finally(function() {
|
|
2484
|
+
addSearchInFlight--;
|
|
2485
|
+
recomputeProviderStates();
|
|
2486
|
+
updateStageUI();
|
|
2487
|
+
maybeAutoGenerateSummary();
|
|
2488
|
+
});
|
|
2489
|
+
});
|
|
2490
|
+
|
|
2491
|
+
function showSuccess(text) {
|
|
2492
|
+
if (es) { es.close(); es = null; }
|
|
2493
|
+
closePreviewModal();
|
|
2494
|
+
successText.textContent = text;
|
|
2495
|
+
successOverlay.classList.remove("hidden");
|
|
2496
|
+
setTimeout(function() { window.close(); }, 800);
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
function showExpired(text) {
|
|
2500
|
+
if (es) { es.close(); es = null; }
|
|
2501
|
+
closePreviewModal();
|
|
2502
|
+
expiredText.textContent = text;
|
|
2503
|
+
expiredOverlay.classList.remove("hidden");
|
|
2504
|
+
requestAnimationFrame(function() { expiredOverlay.classList.add("visible"); });
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
function startOverlayCloseCountdown(seconds) {
|
|
2508
|
+
var count = seconds;
|
|
2509
|
+
closeCountdown.textContent = count;
|
|
2510
|
+
var iv = setInterval(function() {
|
|
2511
|
+
count--;
|
|
2512
|
+
closeCountdown.textContent = count;
|
|
2513
|
+
if (count <= 0) {
|
|
2514
|
+
clearInterval(iv);
|
|
2515
|
+
window.close();
|
|
2516
|
+
}
|
|
2517
|
+
}, 1000);
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
function submitPayload(payload, successText) {
|
|
2521
|
+
if (submitInFlight) return Promise.reject(new Error("Submit already in progress"));
|
|
2522
|
+
submitInFlight = true;
|
|
2523
|
+
submitted = true;
|
|
2524
|
+
syncLoadingPanel();
|
|
2525
|
+
updateStageUI();
|
|
2526
|
+
clearError();
|
|
2527
|
+
|
|
2528
|
+
return postJson("/submit", payload)
|
|
2529
|
+
.then(function(data) {
|
|
2530
|
+
if (data && data.ok === false) {
|
|
2531
|
+
throw new Error(extractServerError(data) || "submit rejected");
|
|
2532
|
+
}
|
|
2533
|
+
showSuccess(successText);
|
|
2534
|
+
})
|
|
2535
|
+
.catch(function(err) {
|
|
2536
|
+
submitInFlight = false;
|
|
2537
|
+
submitted = false;
|
|
2538
|
+
syncLoadingPanel();
|
|
2539
|
+
updateStageUI();
|
|
2540
|
+
throw err;
|
|
2541
|
+
});
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
function submitWithTimeoutFallback(payload) {
|
|
2545
|
+
if (submitInFlight) return;
|
|
2546
|
+
submitInFlight = true;
|
|
2547
|
+
submitted = true;
|
|
2548
|
+
timerExpired = true;
|
|
2549
|
+
syncLoadingPanel();
|
|
2550
|
+
updateStageUI();
|
|
2551
|
+
clearError();
|
|
2552
|
+
showExpired("Time\u2019s up \u2014 submitting current summary state.");
|
|
2553
|
+
|
|
2554
|
+
function finalizeClose() {
|
|
2555
|
+
submitInFlight = false;
|
|
2556
|
+
startOverlayCloseCountdown(5);
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
function toErrorMessage(err) {
|
|
2560
|
+
return err instanceof Error ? err.message : String(err);
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
function attemptCancelFallback(submitErrorMessage) {
|
|
2564
|
+
return postJson("/cancel", { reason: "timeout" })
|
|
2565
|
+
.catch(function(cancelErr) {
|
|
2566
|
+
console.error("Timeout finalize failed after submit errors:", submitErrorMessage, "| cancel:", toErrorMessage(cancelErr));
|
|
2567
|
+
})
|
|
2568
|
+
.finally(finalizeClose);
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
postJson("/submit", payload)
|
|
2572
|
+
.then(function(data) {
|
|
2573
|
+
if (data && data.ok === false) {
|
|
2574
|
+
throw new Error(extractServerError(data) || "submit rejected");
|
|
2575
|
+
}
|
|
2576
|
+
finalizeClose();
|
|
2577
|
+
})
|
|
2578
|
+
.catch(function(firstErr) {
|
|
2579
|
+
var firstMessage = toErrorMessage(firstErr);
|
|
2580
|
+
setTimeout(function() {
|
|
2581
|
+
postJson("/submit", payload)
|
|
2582
|
+
.then(function(data) {
|
|
2583
|
+
if (data && data.ok === false) {
|
|
2584
|
+
throw new Error(extractServerError(data) || "submit rejected");
|
|
2585
|
+
}
|
|
2586
|
+
finalizeClose();
|
|
2587
|
+
})
|
|
2588
|
+
.catch(function(secondErr) {
|
|
2589
|
+
var secondMessage = toErrorMessage(secondErr);
|
|
2590
|
+
attemptCancelFallback(firstMessage + " | " + secondMessage);
|
|
2591
|
+
});
|
|
2592
|
+
}, 250);
|
|
2593
|
+
});
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
function onTimeout() {
|
|
2597
|
+
if (submitted || timerExpired) return;
|
|
2598
|
+
var timeoutSelected = getTimeoutSelectedIndices();
|
|
2599
|
+
var payload = { selected: timeoutSelected };
|
|
2600
|
+
var draft = getSummaryDraftText();
|
|
2601
|
+
if (stage === "summary-review" && draft.length > 0) {
|
|
2602
|
+
payload.summary = draft;
|
|
2603
|
+
if (summaryMeta) payload.summaryMeta = summaryMeta;
|
|
2604
|
+
}
|
|
2605
|
+
submitWithTimeoutFallback(payload);
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
if (queries.length === 0) {
|
|
2609
|
+
heroTitle.textContent = "What do you need?";
|
|
2610
|
+
heroDesc.textContent = "Search for anything below, then generate and approve a summary.";
|
|
2611
|
+
if (heroStatus) heroStatus.textContent = "";
|
|
2612
|
+
btnSend.textContent = "No results yet";
|
|
2613
|
+
} else {
|
|
2614
|
+
for (var i = 0; i < queries.length; i++) {
|
|
2615
|
+
queryIndexToSlot.set(i, i);
|
|
2616
|
+
var card = document.createElement("div");
|
|
2617
|
+
card.className = "result-card searching";
|
|
2618
|
+
card.dataset.qi = i;
|
|
2619
|
+
card.innerHTML =
|
|
2620
|
+
'<div class="result-card-header">' +
|
|
2621
|
+
'<input type="checkbox" checked disabled>' +
|
|
2622
|
+
'<div class="result-card-info">' +
|
|
2623
|
+
'<div class="result-card-query-row">' +
|
|
2624
|
+
'<div class="result-card-query">' + escHtml(queries[i]) + "</div>" +
|
|
2625
|
+
providerTagHtml(initialDefaultProvider) +
|
|
2626
|
+
"</div>" +
|
|
2627
|
+
'<div class="result-card-meta"><span class="searching-dots">Searching</span></div>' +
|
|
2628
|
+
"</div>" +
|
|
2629
|
+
"</div>" +
|
|
2630
|
+
buildAltChipsHtml(initialDefaultProvider, queries[i]);
|
|
2631
|
+
resultCardsEl.appendChild(card);
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
initializeSummaryModelControls();
|
|
2636
|
+
syncLoadingPanel();
|
|
2637
|
+
recomputeProviderStates();
|
|
2638
|
+
updateStageUI();
|
|
2639
|
+
|
|
2640
|
+
es = new EventSource("/events?session=" + encodeURIComponent(token));
|
|
2641
|
+
|
|
2642
|
+
function parseSseEventData(eventName, e) {
|
|
2643
|
+
try {
|
|
2644
|
+
return JSON.parse(e.data);
|
|
2645
|
+
} catch (err) {
|
|
2646
|
+
var message = err instanceof Error ? err.message : String(err);
|
|
2647
|
+
setError("Invalid " + eventName + " event payload: " + (message || "unknown parse error"));
|
|
2648
|
+
return null;
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
es.addEventListener("result", function(e) {
|
|
2653
|
+
var data = parseSseEventData("result", e);
|
|
2654
|
+
if (!data) return;
|
|
2655
|
+
|
|
2656
|
+
var card = resultCardsEl.querySelector('.result-card[data-qi="' + data.queryIndex + '"]');
|
|
2657
|
+
if (!card) return;
|
|
2658
|
+
|
|
2659
|
+
var slotId = queryIndexToSlot.get(data.queryIndex);
|
|
2660
|
+
if (typeof slotId !== "number") slotId = data.queryIndex;
|
|
2661
|
+
applyResponseToCard(card, data, data.query || queries[data.queryIndex], data.provider, slotId);
|
|
2662
|
+
});
|
|
2663
|
+
|
|
2664
|
+
es.addEventListener("search-error", function(e) {
|
|
2665
|
+
var data = parseSseEventData("search-error", e);
|
|
2666
|
+
if (!data) return;
|
|
2667
|
+
|
|
2668
|
+
var card = resultCardsEl.querySelector('.result-card[data-qi="' + data.queryIndex + '"]');
|
|
2669
|
+
if (!card) return;
|
|
2670
|
+
|
|
2671
|
+
var slotId = queryIndexToSlot.get(data.queryIndex);
|
|
2672
|
+
if (typeof slotId !== "number") slotId = data.queryIndex;
|
|
2673
|
+
applyResponseToCard(card, {
|
|
2674
|
+
queryIndex: data.queryIndex,
|
|
2675
|
+
answer: "",
|
|
2676
|
+
results: [],
|
|
2677
|
+
error: data.error || "Search failed",
|
|
2678
|
+
provider: data.provider,
|
|
2679
|
+
}, data.query || queries[data.queryIndex], data.provider, slotId);
|
|
2680
|
+
});
|
|
2681
|
+
|
|
2682
|
+
es.addEventListener("done", function() {
|
|
2683
|
+
searchesDone = true;
|
|
2684
|
+
initialStreamDone = true;
|
|
2685
|
+
if (completedCount > 0) {
|
|
2686
|
+
updateSummaryText();
|
|
2687
|
+
}
|
|
2688
|
+
syncLoadingPanel();
|
|
2689
|
+
recomputeProviderStates();
|
|
2690
|
+
updateStageUI();
|
|
2691
|
+
maybeAutoGenerateSummary();
|
|
2692
|
+
resetTimer();
|
|
2693
|
+
});
|
|
2694
|
+
|
|
2695
|
+
es.onerror = function() {
|
|
2696
|
+
// EventSource reconnects automatically.
|
|
2697
|
+
};
|
|
2698
|
+
|
|
2699
|
+
function setupCardInteraction(card) {
|
|
2700
|
+
var header = card.querySelector(".result-card-header");
|
|
2701
|
+
var body = card.querySelector(".result-card-body");
|
|
2702
|
+
var cb = card.querySelector("input[type=checkbox]");
|
|
2703
|
+
var expandEl = card.querySelector(".result-card-expand");
|
|
2704
|
+
|
|
2705
|
+
if (!header || !cb) return;
|
|
2706
|
+
|
|
2707
|
+
header.addEventListener("click", function(e) {
|
|
2708
|
+
if (e.target.tagName === "A") return;
|
|
2709
|
+
if (e.target === cb) {
|
|
2710
|
+
if (isResultMutationLocked()) {
|
|
2711
|
+
e.preventDefault();
|
|
2712
|
+
return;
|
|
2713
|
+
}
|
|
2714
|
+
card.classList.toggle("checked", cb.checked);
|
|
2715
|
+
if (stage === "summary-review" || stage === "generating-summary") {
|
|
2716
|
+
interruptSummaryIfNeeded();
|
|
2717
|
+
}
|
|
2718
|
+
updateStageUI();
|
|
2719
|
+
maybeAutoGenerateSummary();
|
|
2720
|
+
return;
|
|
2721
|
+
}
|
|
2722
|
+
var isExpanded = body && body.classList.contains("open");
|
|
2723
|
+
if (body) body.classList.toggle("open");
|
|
2724
|
+
if (expandEl) expandEl.textContent = isExpanded ? "\u25BC" : "\u25B2";
|
|
2725
|
+
});
|
|
2726
|
+
|
|
2727
|
+
if (body) {
|
|
2728
|
+
body.addEventListener("click", function(e) {
|
|
2729
|
+
e.stopPropagation();
|
|
2730
|
+
});
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
function getSelectedIndices() {
|
|
2735
|
+
var indices = [];
|
|
2736
|
+
var cards = resultCardsEl.querySelectorAll(".result-card");
|
|
2737
|
+
cards.forEach(function(card) {
|
|
2738
|
+
if (card.dataset.completed !== "true") return;
|
|
2739
|
+
if (card.classList.contains("error")) return;
|
|
2740
|
+
var cb = card.querySelector("input[type=checkbox]");
|
|
2741
|
+
if (!cb || !cb.checked) return;
|
|
2742
|
+
var qi = parseInt(card.dataset.qi, 10);
|
|
2743
|
+
if (!Number.isNaN(qi)) indices.push(qi);
|
|
2744
|
+
});
|
|
2745
|
+
return indices;
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
function getCompletedSelectableIndices() {
|
|
2749
|
+
var indices = [];
|
|
2750
|
+
var cards = resultCardsEl.querySelectorAll(".result-card");
|
|
2751
|
+
cards.forEach(function(card) {
|
|
2752
|
+
if (card.dataset.completed !== "true") return;
|
|
2753
|
+
if (card.classList.contains("error")) return;
|
|
2754
|
+
var qi = parseInt(card.dataset.qi, 10);
|
|
2755
|
+
if (!Number.isNaN(qi)) indices.push(qi);
|
|
2756
|
+
});
|
|
2757
|
+
return indices;
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
function hasPendingSearchCards() {
|
|
2761
|
+
var cards = resultCardsEl.querySelectorAll(".result-card");
|
|
2762
|
+
for (var i = 0; i < cards.length; i++) {
|
|
2763
|
+
var card = cards[i];
|
|
2764
|
+
if (card.dataset.completed !== "true") return true;
|
|
2765
|
+
}
|
|
2766
|
+
return addSearchInFlight || providerBatchInFlight;
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
function getTimeoutSelectedIndices() {
|
|
2770
|
+
var selected = getSelectedIndices();
|
|
2771
|
+
if (selected.length > 0) return selected;
|
|
2772
|
+
return getCompletedSelectableIndices();
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
function normalizeSummaryMeta(meta, edited) {
|
|
2776
|
+
if (!meta || typeof meta !== "object") {
|
|
2777
|
+
return {
|
|
2778
|
+
model: null,
|
|
2779
|
+
durationMs: 0,
|
|
2780
|
+
tokenEstimate: 0,
|
|
2781
|
+
fallbackUsed: false,
|
|
2782
|
+
edited: !!edited,
|
|
2783
|
+
};
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
return {
|
|
2787
|
+
model: typeof meta.model === "string" || meta.model === null ? meta.model : null,
|
|
2788
|
+
durationMs: typeof meta.durationMs === "number" && Number.isFinite(meta.durationMs) && meta.durationMs >= 0 ? meta.durationMs : 0,
|
|
2789
|
+
tokenEstimate: typeof meta.tokenEstimate === "number" && Number.isFinite(meta.tokenEstimate) && meta.tokenEstimate >= 0 ? meta.tokenEstimate : 0,
|
|
2790
|
+
fallbackUsed: meta.fallbackUsed === true,
|
|
2791
|
+
fallbackReason: typeof meta.fallbackReason === "string" ? meta.fallbackReason : undefined,
|
|
2792
|
+
edited: !!edited,
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
function isSummaryModelSelectionError(message) {
|
|
2797
|
+
if (typeof message !== "string") return false;
|
|
2798
|
+
return message.indexOf("Invalid summary model") !== -1
|
|
2799
|
+
|| message.indexOf("Summary model not found") !== -1
|
|
2800
|
+
|| message.indexOf("No API key available for summary model") !== -1
|
|
2801
|
+
|| message.indexOf("Invalid provider") !== -1;
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
function resetSummaryGeneratingState() {
|
|
2805
|
+
summaryPendingModel = "";
|
|
2806
|
+
summaryGeneratingStartedAt = 0;
|
|
2807
|
+
summaryGeneratingPhase = -1;
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
function cancelInFlightSummaryRequest() {
|
|
2811
|
+
summaryRequestSeq += 1;
|
|
2812
|
+
resetSummaryGeneratingState();
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
function interruptSummaryIfNeeded() {
|
|
2816
|
+
if (stage !== "generating-summary" && stage !== "summary-review") return;
|
|
2817
|
+
if (stage === "generating-summary") {
|
|
2818
|
+
cancelInFlightSummaryRequest();
|
|
2819
|
+
}
|
|
2820
|
+
clearError();
|
|
2821
|
+
isRegenerating = getSummaryDraftText().length > 0;
|
|
2822
|
+
stage = "results";
|
|
2823
|
+
updateStageUI();
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
function exitRegeneratingState() {
|
|
2827
|
+
if (!isRegenerating) return false;
|
|
2828
|
+
if (stage === "generating-summary") {
|
|
2829
|
+
cancelInFlightSummaryRequest();
|
|
2830
|
+
}
|
|
2831
|
+
isRegenerating = false;
|
|
2832
|
+
clearError();
|
|
2833
|
+
stage = "results";
|
|
2834
|
+
updateStageUI();
|
|
2835
|
+
return true;
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
function requestSummary(indices, feedback) {
|
|
2839
|
+
if (submitted || timerExpired || submitInFlight) return;
|
|
2840
|
+
|
|
2841
|
+
if (!Array.isArray(indices) || indices.length === 0) {
|
|
2842
|
+
setError("Select at least one result to summarize");
|
|
2843
|
+
stage = "results";
|
|
2844
|
+
updateStageUI();
|
|
2845
|
+
return;
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
if (hasPendingSearchCards()) {
|
|
2849
|
+
setError("Wait for running searches to finish before generating summary");
|
|
2850
|
+
stage = "results";
|
|
2851
|
+
updateStageUI();
|
|
2852
|
+
return;
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
clearError();
|
|
2856
|
+
var previousStage = stage;
|
|
2857
|
+
var wasRegenerating = isRegenerating;
|
|
2858
|
+
var selectedSummaryModel = getSelectedSummaryModel();
|
|
2859
|
+
summaryPendingModel = selectedSummaryModel;
|
|
2860
|
+
summaryGeneratingStartedAt = Date.now();
|
|
2861
|
+
summaryGeneratingPhase = -1;
|
|
2862
|
+
stage = "generating-summary";
|
|
2863
|
+
updateStageUI();
|
|
2864
|
+
|
|
2865
|
+
var requestId = ++summaryRequestSeq;
|
|
2866
|
+
var feedbackText = typeof feedback === "string" ? feedback.trim() : "";
|
|
2867
|
+
var summarizePayload = { selected: indices };
|
|
2868
|
+
if (selectedSummaryModel.length > 0) {
|
|
2869
|
+
summarizePayload.model = selectedSummaryModel;
|
|
2870
|
+
}
|
|
2871
|
+
if (feedbackText.length > 0) {
|
|
2872
|
+
summarizePayload.feedback = feedbackText;
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
postJson("/summarize", summarizePayload)
|
|
2876
|
+
.then(function(data) {
|
|
2877
|
+
if (requestId !== summaryRequestSeq) return data;
|
|
2878
|
+
if (!data || data.ok === false) {
|
|
2879
|
+
throw new Error(extractServerError(data) || "summary request rejected");
|
|
2880
|
+
}
|
|
2881
|
+
return data;
|
|
2882
|
+
})
|
|
2883
|
+
.catch(function(err) {
|
|
2884
|
+
if (requestId !== summaryRequestSeq) throw err;
|
|
2885
|
+
|
|
2886
|
+
var firstMessage = err instanceof Error ? err.message : String(err);
|
|
2887
|
+
if (selectedSummaryModel.length === 0 || !isSummaryModelSelectionError(firstMessage)) {
|
|
2888
|
+
throw err;
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
summaryPendingModel = "";
|
|
2892
|
+
updateStageUI();
|
|
2893
|
+
|
|
2894
|
+
var retryPayload = { selected: indices };
|
|
2895
|
+
if (feedbackText.length > 0) {
|
|
2896
|
+
retryPayload.feedback = feedbackText;
|
|
2897
|
+
}
|
|
2898
|
+
return postJson("/summarize", retryPayload).then(function(retryData) {
|
|
2899
|
+
if (!retryData || retryData.ok === false) {
|
|
2900
|
+
throw new Error(extractServerError(retryData) || "summary request rejected");
|
|
2901
|
+
}
|
|
2902
|
+
return retryData;
|
|
2903
|
+
}).catch(function(retryErr) {
|
|
2904
|
+
var retryMessage = retryErr instanceof Error ? retryErr.message : String(retryErr);
|
|
2905
|
+
throw new Error(firstMessage + " (auto retry failed: " + (retryMessage || "unknown error") + ")");
|
|
2906
|
+
});
|
|
2907
|
+
})
|
|
2908
|
+
.then(function(data) {
|
|
2909
|
+
if (requestId !== summaryRequestSeq) return;
|
|
2910
|
+
|
|
2911
|
+
var summaryText = typeof data.summary === "string" ? data.summary.trim() : "";
|
|
2912
|
+
if (!summaryText) {
|
|
2913
|
+
throw new Error("Summary response was empty");
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
if (summaryInput) {
|
|
2917
|
+
summaryInput.value = summaryText;
|
|
2918
|
+
}
|
|
2919
|
+
if (summaryFeedback) {
|
|
2920
|
+
summaryFeedback.value = "";
|
|
2921
|
+
}
|
|
2922
|
+
summaryMeta = normalizeSummaryMeta(data.meta || null, false);
|
|
2923
|
+
lastAutoSummarySignature = selectionSignature(indices);
|
|
2924
|
+
resetSummaryGeneratingState();
|
|
2925
|
+
isRegenerating = false;
|
|
2926
|
+
stage = "summary-review";
|
|
2927
|
+
updateStageUI();
|
|
2928
|
+
})
|
|
2929
|
+
.catch(function(err) {
|
|
2930
|
+
if (requestId !== summaryRequestSeq) return;
|
|
2931
|
+
var message = err instanceof Error ? err.message : String(err);
|
|
2932
|
+
setError("Failed to generate summary — " + (message || "unknown error"));
|
|
2933
|
+
resetSummaryGeneratingState();
|
|
2934
|
+
isRegenerating = false;
|
|
2935
|
+
if (wasRegenerating && getSummaryDraftText().length > 0) {
|
|
2936
|
+
stage = "summary-review";
|
|
2937
|
+
} else {
|
|
2938
|
+
stage = previousStage === "summary-review" ? "summary-review" : "results";
|
|
2939
|
+
}
|
|
2940
|
+
updateStageUI();
|
|
2941
|
+
});
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
function selectionSignature(indices) {
|
|
2945
|
+
return indices.slice().sort(function(a, b) { return a - b; }).join(",");
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2948
|
+
function maybeAutoGenerateSummary() {
|
|
2949
|
+
if (workflow !== "summary-review") return;
|
|
2950
|
+
if (!searchesDone) return;
|
|
2951
|
+
if (stage !== "results") return;
|
|
2952
|
+
if (submitted || timerExpired || submitInFlight) return;
|
|
2953
|
+
if (hasPendingSearchCards()) return;
|
|
2954
|
+
|
|
2955
|
+
var selected = getSelectedIndices();
|
|
2956
|
+
if (selected.length === 0) {
|
|
2957
|
+
if (isRegenerating) {
|
|
2958
|
+
isRegenerating = false;
|
|
2959
|
+
updateStageUI();
|
|
2960
|
+
}
|
|
2961
|
+
return;
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
var signature = selectionSignature(selected);
|
|
2965
|
+
if (signature === lastAutoSummarySignature) {
|
|
2966
|
+
if (isRegenerating) {
|
|
2967
|
+
isRegenerating = false;
|
|
2968
|
+
if (getSummaryDraftText().length > 0) {
|
|
2969
|
+
stage = "summary-review";
|
|
2970
|
+
}
|
|
2971
|
+
updateStageUI();
|
|
2972
|
+
}
|
|
2973
|
+
return;
|
|
2974
|
+
}
|
|
2975
|
+
|
|
2976
|
+
lastAutoSummarySignature = signature;
|
|
2977
|
+
requestSummary(selected);
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
function doApprove() {
|
|
2981
|
+
if (submitted || timerExpired || submitInFlight || stage !== "summary-review") return;
|
|
2982
|
+
|
|
2983
|
+
var selected = getSelectedIndices();
|
|
2984
|
+
if (selected.length === 0) {
|
|
2985
|
+
setError("Select at least one result before approving");
|
|
2986
|
+
updateStageUI();
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
var draft = getSummaryDraftText();
|
|
2991
|
+
var payload = { selected: selected };
|
|
2992
|
+
if (draft.length > 0) {
|
|
2993
|
+
payload.summary = draft;
|
|
2994
|
+
payload.summaryMeta = normalizeSummaryMeta(summaryMeta, summaryMeta && summaryMeta.edited === true);
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
submitPayload(payload, "Summary approved")
|
|
2998
|
+
.catch(function(err) {
|
|
2999
|
+
var message = err instanceof Error ? err.message : String(err);
|
|
3000
|
+
setError("Failed to approve summary — " + (message || "the agent may have moved on"));
|
|
3001
|
+
});
|
|
3002
|
+
}
|
|
3003
|
+
|
|
3004
|
+
function doCancel() {
|
|
3005
|
+
if (submitted || timerExpired || submitInFlight) return;
|
|
3006
|
+
submitted = true;
|
|
3007
|
+
submitInFlight = true;
|
|
3008
|
+
syncLoadingPanel();
|
|
3009
|
+
updateStageUI();
|
|
3010
|
+
clearError();
|
|
3011
|
+
|
|
3012
|
+
postJson("/cancel", { reason: "user" })
|
|
3013
|
+
.then(function(data) {
|
|
3014
|
+
if (data && data.ok === false) {
|
|
3015
|
+
throw new Error(extractServerError(data) || "cancel rejected");
|
|
3016
|
+
}
|
|
3017
|
+
showSuccess("Skipped");
|
|
3018
|
+
})
|
|
3019
|
+
.catch(function(err) {
|
|
3020
|
+
submitted = false;
|
|
3021
|
+
submitInFlight = false;
|
|
3022
|
+
syncLoadingPanel();
|
|
3023
|
+
updateStageUI();
|
|
3024
|
+
var message = err instanceof Error ? err.message : String(err);
|
|
3025
|
+
setError("Failed to cancel — " + (message || "the agent may have moved on"));
|
|
3026
|
+
});
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
btnSend.addEventListener("click", function() {
|
|
3030
|
+
if (stage !== "results") return;
|
|
3031
|
+
requestSummary(getSelectedIndices());
|
|
3032
|
+
});
|
|
3033
|
+
|
|
3034
|
+
if (btnSendRaw) {
|
|
3035
|
+
btnSendRaw.addEventListener("click", function() {
|
|
3036
|
+
var selected = getSelectedIndices();
|
|
3037
|
+
if (selected.length === 0) return;
|
|
3038
|
+
submitPayload({ selected: selected, rawResults: true }, "Results sent")
|
|
3039
|
+
.catch(function(err) {
|
|
3040
|
+
var message = err instanceof Error ? err.message : String(err);
|
|
3041
|
+
setError("Failed to send results — " + (message || "the agent may have moved on"));
|
|
3042
|
+
});
|
|
3043
|
+
});
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
if (btnSummaryBack) {
|
|
3047
|
+
btnSummaryBack.addEventListener("click", function() {
|
|
3048
|
+
if (exitRegeneratingState()) {
|
|
3049
|
+
resetTimer();
|
|
3050
|
+
return;
|
|
3051
|
+
}
|
|
3052
|
+
if (stage !== "summary-review") return;
|
|
3053
|
+
clearError();
|
|
3054
|
+
stage = "results";
|
|
3055
|
+
updateStageUI();
|
|
3056
|
+
resetTimer();
|
|
3057
|
+
});
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
if (btnSummaryRegenerate) {
|
|
3061
|
+
btnSummaryRegenerate.addEventListener("click", function() {
|
|
3062
|
+
requestSummary(getSelectedIndices(), getFeedbackText());
|
|
3063
|
+
resetTimer();
|
|
3064
|
+
});
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
function openPreviewModal() {
|
|
3068
|
+
var draft = getSummaryDraftText();
|
|
3069
|
+
if (!draft || !previewModal || !previewModalBody) return;
|
|
3070
|
+
var rendered = typeof marked !== "undefined" && marked.parse
|
|
3071
|
+
? marked.parse(draft, { breaks: true })
|
|
3072
|
+
: "<pre>" + escHtml(draft) + "</pre>";
|
|
3073
|
+
previewModalBody.innerHTML = sanitizeMarkdownHtml(rendered);
|
|
3074
|
+
if (previewModalModel) {
|
|
3075
|
+
previewModalModel.innerHTML = '<option value="">Auto</option>';
|
|
3076
|
+
for (var i = 0; i < summaryModels.length; i++) {
|
|
3077
|
+
var m = summaryModels[i];
|
|
3078
|
+
var opt = document.createElement("option");
|
|
3079
|
+
opt.value = m.value;
|
|
3080
|
+
opt.textContent = m.label;
|
|
3081
|
+
previewModalModel.appendChild(opt);
|
|
3082
|
+
}
|
|
3083
|
+
previewModalModel.value = getSelectedSummaryModel() || "";
|
|
3084
|
+
}
|
|
3085
|
+
previewModal.classList.remove("hidden");
|
|
3086
|
+
resetTimer();
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
function closePreviewModal() {
|
|
3090
|
+
if (previewModal) previewModal.classList.add("hidden");
|
|
3091
|
+
if (previewModalBody) previewModalBody.innerHTML = "";
|
|
3092
|
+
hidePreviewPopover();
|
|
3093
|
+
}
|
|
3094
|
+
|
|
3095
|
+
var popoverSelectedText = "";
|
|
3096
|
+
|
|
3097
|
+
function hidePreviewPopover() {
|
|
3098
|
+
if (previewPopover) previewPopover.classList.add("hidden");
|
|
3099
|
+
if (previewPopoverInput) previewPopoverInput.value = "";
|
|
3100
|
+
popoverSelectedText = "";
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
function showPreviewPopover(text, rect) {
|
|
3104
|
+
if (!previewPopover || !previewPopoverQuote || !previewModalBody) return;
|
|
3105
|
+
popoverSelectedText = text;
|
|
3106
|
+
var display = text.length > 120 ? text.slice(0, 117) + "\u2026" : text;
|
|
3107
|
+
previewPopoverQuote.textContent = "\u201c" + display + "\u201d";
|
|
3108
|
+
if (previewPopoverInput) previewPopoverInput.value = "";
|
|
3109
|
+
previewPopover.classList.remove("hidden");
|
|
3110
|
+
|
|
3111
|
+
var bodyRect = previewModalBody.getBoundingClientRect();
|
|
3112
|
+
var popH = previewPopover.offsetHeight;
|
|
3113
|
+
var top = rect.bottom - bodyRect.top + previewModalBody.scrollTop + 6;
|
|
3114
|
+
if (rect.bottom + popH + 20 > bodyRect.bottom) {
|
|
3115
|
+
top = rect.top - bodyRect.top + previewModalBody.scrollTop - popH - 6;
|
|
3116
|
+
}
|
|
3117
|
+
var left = Math.max(8, Math.min(rect.left - bodyRect.left, bodyRect.width - previewPopover.offsetWidth - 8));
|
|
3118
|
+
previewPopover.style.top = top + "px";
|
|
3119
|
+
previewPopover.style.left = left + "px";
|
|
3120
|
+
|
|
3121
|
+
if (previewPopoverInput) previewPopoverInput.focus();
|
|
3122
|
+
}
|
|
3123
|
+
|
|
3124
|
+
if (btnSummaryPreview) {
|
|
3125
|
+
btnSummaryPreview.addEventListener("click", openPreviewModal);
|
|
3126
|
+
}
|
|
3127
|
+
if (previewModalClose) {
|
|
3128
|
+
previewModalClose.addEventListener("click", closePreviewModal);
|
|
3129
|
+
}
|
|
3130
|
+
if (previewModalRegenerate) {
|
|
3131
|
+
previewModalRegenerate.addEventListener("click", function() {
|
|
3132
|
+
var selectedModel = previewModalModel ? previewModalModel.value.trim() : "";
|
|
3133
|
+
closePreviewModal();
|
|
3134
|
+
var modelProvider = getSummaryProvider(selectedModel);
|
|
3135
|
+
if (modelProvider && modelProvider !== currentSummaryProvider) {
|
|
3136
|
+
setSummaryProvider(modelProvider, selectedModel);
|
|
3137
|
+
} else if (summaryModelSelect) {
|
|
3138
|
+
summaryModelSelect.value = selectedModel;
|
|
3139
|
+
currentSummaryModel = selectedModel;
|
|
3140
|
+
}
|
|
3141
|
+
requestSummary(getSelectedIndices(), getFeedbackText());
|
|
3142
|
+
resetTimer();
|
|
3143
|
+
});
|
|
3144
|
+
}
|
|
3145
|
+
if (previewModalApprove) {
|
|
3146
|
+
previewModalApprove.addEventListener("click", function() {
|
|
3147
|
+
closePreviewModal();
|
|
3148
|
+
doApprove();
|
|
3149
|
+
});
|
|
3150
|
+
}
|
|
3151
|
+
if (previewModalBody) {
|
|
3152
|
+
previewModalBody.addEventListener("mouseup", function() {
|
|
3153
|
+
var sel = window.getSelection();
|
|
3154
|
+
if (!sel || sel.isCollapsed) return;
|
|
3155
|
+
var text = sel.toString().trim();
|
|
3156
|
+
if (!text) return;
|
|
3157
|
+
var range = sel.getRangeAt(0);
|
|
3158
|
+
showPreviewPopover(text, range.getBoundingClientRect());
|
|
3159
|
+
});
|
|
3160
|
+
previewModalBody.addEventListener("mousedown", function(e) {
|
|
3161
|
+
if (previewPopover && !previewPopover.contains(e.target)) {
|
|
3162
|
+
hidePreviewPopover();
|
|
3163
|
+
}
|
|
3164
|
+
});
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
if (previewPopoverRegen) {
|
|
3168
|
+
previewPopoverRegen.addEventListener("click", function() {
|
|
3169
|
+
var note = previewPopoverInput ? previewPopoverInput.value.trim() : "";
|
|
3170
|
+
var quoted = popoverSelectedText;
|
|
3171
|
+
hidePreviewPopover();
|
|
3172
|
+
|
|
3173
|
+
var feedback = 'Regarding: "' + quoted + '"';
|
|
3174
|
+
if (note) feedback += " \u2014 " + note;
|
|
3175
|
+
|
|
3176
|
+
var selectedModel = previewModalModel ? previewModalModel.value.trim() : "";
|
|
3177
|
+
closePreviewModal();
|
|
3178
|
+
var modelProvider = getSummaryProvider(selectedModel);
|
|
3179
|
+
if (modelProvider && modelProvider !== currentSummaryProvider) {
|
|
3180
|
+
setSummaryProvider(modelProvider, selectedModel);
|
|
3181
|
+
} else if (summaryModelSelect) {
|
|
3182
|
+
summaryModelSelect.value = selectedModel;
|
|
3183
|
+
currentSummaryModel = selectedModel;
|
|
3184
|
+
}
|
|
3185
|
+
requestSummary(getSelectedIndices(), feedback);
|
|
3186
|
+
resetTimer();
|
|
3187
|
+
});
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
if (previewPopoverInput) {
|
|
3191
|
+
previewPopoverInput.addEventListener("keydown", function(e) {
|
|
3192
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
3193
|
+
e.preventDefault();
|
|
3194
|
+
if (previewPopoverRegen) previewPopoverRegen.click();
|
|
3195
|
+
}
|
|
3196
|
+
if (e.key === "Escape") {
|
|
3197
|
+
e.preventDefault();
|
|
3198
|
+
e.stopImmediatePropagation();
|
|
3199
|
+
hidePreviewPopover();
|
|
3200
|
+
}
|
|
3201
|
+
});
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
if (previewModal) {
|
|
3205
|
+
previewModal.addEventListener("click", function(e) {
|
|
3206
|
+
if (e.target === previewModal) closePreviewModal();
|
|
3207
|
+
});
|
|
3208
|
+
document.addEventListener("keydown", function(e) {
|
|
3209
|
+
if (e.key === "Escape" && !previewModal.classList.contains("hidden")) {
|
|
3210
|
+
if (previewPopover && !previewPopover.classList.contains("hidden")) {
|
|
3211
|
+
e.preventDefault();
|
|
3212
|
+
e.stopImmediatePropagation();
|
|
3213
|
+
hidePreviewPopover();
|
|
3214
|
+
return;
|
|
3215
|
+
}
|
|
3216
|
+
e.preventDefault();
|
|
3217
|
+
e.stopImmediatePropagation();
|
|
3218
|
+
closePreviewModal();
|
|
3219
|
+
}
|
|
3220
|
+
});
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3223
|
+
if (btnSummaryApprove) {
|
|
3224
|
+
btnSummaryApprove.addEventListener("click", function() {
|
|
3225
|
+
doApprove();
|
|
3226
|
+
resetTimer();
|
|
3227
|
+
});
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
if (summaryInput) {
|
|
3231
|
+
summaryInput.addEventListener("input", function() {
|
|
3232
|
+
if (!summaryMeta || typeof summaryMeta !== "object") {
|
|
3233
|
+
summaryMeta = normalizeSummaryMeta(null, true);
|
|
3234
|
+
}
|
|
3235
|
+
summaryMeta.edited = true;
|
|
3236
|
+
clearError();
|
|
3237
|
+
updateStageUI();
|
|
3238
|
+
resetTimer();
|
|
3239
|
+
});
|
|
3240
|
+
}
|
|
3241
|
+
|
|
3242
|
+
if (summaryProviderSelect) {
|
|
3243
|
+
summaryProviderSelect.addEventListener("change", function() {
|
|
3244
|
+
var provider = typeof summaryProviderSelect.value === "string" ? summaryProviderSelect.value : "";
|
|
3245
|
+
if (!provider || provider === currentSummaryProvider) return;
|
|
3246
|
+
setSummaryProvider(provider, "");
|
|
3247
|
+
clearError();
|
|
3248
|
+
updateStageUI();
|
|
3249
|
+
resetTimer();
|
|
3250
|
+
});
|
|
3251
|
+
}
|
|
3252
|
+
|
|
3253
|
+
if (summaryModelSelect) {
|
|
3254
|
+
summaryModelSelect.addEventListener("change", function() {
|
|
3255
|
+
currentSummaryModel = typeof summaryModelSelect.value === "string"
|
|
3256
|
+
? summaryModelSelect.value.trim()
|
|
3257
|
+
: "";
|
|
3258
|
+
clearError();
|
|
3259
|
+
resetTimer();
|
|
3260
|
+
});
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
function isInteractiveTarget(target) {
|
|
3264
|
+
if (!target || !target.tagName) return false;
|
|
3265
|
+
var tag = target.tagName;
|
|
3266
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || tag === "BUTTON" || tag === "A") return true;
|
|
3267
|
+
if (typeof target.isContentEditable === "boolean" && target.isContentEditable) return true;
|
|
3268
|
+
if (typeof target.closest === "function") {
|
|
3269
|
+
return !!target.closest('[contenteditable=""], [contenteditable="true"]');
|
|
3270
|
+
}
|
|
3271
|
+
return false;
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
document.addEventListener("keydown", function(e) {
|
|
3275
|
+
if (submitted || timerExpired || submitInFlight) return;
|
|
3276
|
+
|
|
3277
|
+
var isSummaryInput = summaryInput && e.target === summaryInput;
|
|
3278
|
+
if (isSummaryInput && (e.metaKey || e.ctrlKey) && e.key === "Enter") {
|
|
3279
|
+
e.preventDefault();
|
|
3280
|
+
if (stage === "summary-review") doApprove();
|
|
3281
|
+
return;
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3284
|
+
if (e.key === "Escape") {
|
|
3285
|
+
e.preventDefault();
|
|
3286
|
+
if (exitRegeneratingState()) {
|
|
3287
|
+
return;
|
|
3288
|
+
}
|
|
3289
|
+
if (stage === "summary-review") {
|
|
3290
|
+
stage = "results";
|
|
3291
|
+
clearError();
|
|
3292
|
+
updateStageUI();
|
|
3293
|
+
} else if (stage === "results") {
|
|
3294
|
+
doCancel();
|
|
3295
|
+
}
|
|
3296
|
+
return;
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
if (isInteractiveTarget(e.target)) return;
|
|
3300
|
+
|
|
3301
|
+
if (e.key === "Enter" && !e.metaKey && !e.ctrlKey) {
|
|
3302
|
+
if (stage !== "results") return;
|
|
3303
|
+
e.preventDefault();
|
|
3304
|
+
requestSummary(getSelectedIndices());
|
|
3305
|
+
return;
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
|
|
3309
|
+
if (stage !== "summary-review") return;
|
|
3310
|
+
e.preventDefault();
|
|
3311
|
+
doApprove();
|
|
3312
|
+
return;
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
if (e.key.toLowerCase() === "a" && !e.metaKey && !e.ctrlKey) {
|
|
3316
|
+
e.preventDefault();
|
|
3317
|
+
if (stage !== "results") return;
|
|
3318
|
+
var boxes = resultCardsEl.querySelectorAll(".result-card input[type=checkbox]");
|
|
3319
|
+
var selectable = [];
|
|
3320
|
+
boxes.forEach(function(cb) {
|
|
3321
|
+
if (cb.disabled) return;
|
|
3322
|
+
selectable.push(cb);
|
|
3323
|
+
});
|
|
3324
|
+
if (selectable.length === 0) return;
|
|
3325
|
+
var allChecked = true;
|
|
3326
|
+
selectable.forEach(function(cb) { if (!cb.checked) allChecked = false; });
|
|
3327
|
+
selectable.forEach(function(cb) {
|
|
3328
|
+
cb.checked = !allChecked;
|
|
3329
|
+
var parentCard = typeof cb.closest === "function" ? cb.closest(".result-card") : null;
|
|
3330
|
+
if (parentCard) parentCard.classList.toggle("checked", cb.checked);
|
|
3331
|
+
});
|
|
3332
|
+
updateStageUI();
|
|
3333
|
+
maybeAutoGenerateSummary();
|
|
3334
|
+
resetTimer();
|
|
3335
|
+
}
|
|
3336
|
+
});
|
|
3337
|
+
|
|
3338
|
+
setInterval(function() {
|
|
3339
|
+
if (submitted) return;
|
|
3340
|
+
postJson("/heartbeat", {}).catch(function() {
|
|
3341
|
+
// Heartbeat is best-effort.
|
|
3342
|
+
});
|
|
3343
|
+
}, 10000);
|
|
3344
|
+
|
|
3345
|
+
var lastResizeHeight = 0;
|
|
3346
|
+
function checkContentHeight() {
|
|
3347
|
+
if (!window.glimpse || typeof window.glimpse.send !== "function") return;
|
|
3348
|
+
var h = document.documentElement.scrollHeight || document.body.scrollHeight;
|
|
3349
|
+
if (h > 0 && Math.abs(h - lastResizeHeight) > 30) {
|
|
3350
|
+
lastResizeHeight = h;
|
|
3351
|
+
window.glimpse.send({ type: "resize", height: h });
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3354
|
+
setInterval(checkContentHeight, 500);
|
|
3355
|
+
|
|
3356
|
+
if (queries.length === 0 && addSearchInput) {
|
|
3357
|
+
addSearchInput.focus();
|
|
3358
|
+
}
|
|
3359
|
+
})();`;
|